Skip to content

Just some fun experiments to see how this really works.

Notifications You must be signed in to change notification settings

hicapacity/ObjcPlayground

 
 

Repository files navigation

Dynamic Languages - They're not just for Rubyists (and Pythonistas)

ABSTRACT
Objective-C is a popular language as it is a building block of all iOS applications.  It also has powerful dynamic features, while running on C.  This talk will introduce you to the basics of Objective-C, with an eye on how it is similar to other dynamic languages, such as Ruby.

WHY?
http://www.tiobe.com/index.php/paperinfo/tpci/Objective-C.html



Credit for the outline of this talk goes to:
- iOS 5 Programming Pushing the Limits, by Rob Napier, Mugunth Kumar
  - Show a picture and link to book: http://iosptl.com/

Assumptions: 
- Not covering Core Foundation
- 

What We'll Cover:
- The Basics
  - Objective-C w/ source code
    - Mention of ARC
  - objects and classes
  - An intro to C Structs, and how Objective-C uses them
  - instances, classes, superclasses, & meta-classes
    - Categories, this is how they work.
- Messaging
  - Basics: methods, messages, selectors, message sending
  - Sending Messages 
    - What is a method?
    - Disecting objc_msgSend()
    - Dynamic usage
      - @dynamic, dynamic loading, fast forwarding, normal forwarding, forwarding failure
      - Core Data, relies on this
  - A deep(er) dive into objc_msgSend
- Sidenote: The Objective-C Runtime
  - What is it?
  - How you interact with it
  - Message forwarding examples
- Swizzling
  - Method swizzling: swapping method implementations at runtime
  - ISA swizzling : changing classes at runtime 
    - This is how KVO works
- That's cool, but what do I do now?


Basics / Intro:
- Primer? http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/_index.html
- Like C++, no multiple inheritance, no operator overloading
  
  
  
  - A Quick Intro to C Structs
    - http://heather.cs.ucdavis.edu/~matloff/UnixAndC/CLanguage/PointersI.html


  
  
  
The Objective-C Object:
- All Objective-C objects are C structs
  typedef struct objc_object {
    Class isa;
  } *id;
  
    - ISA pointer (defines the object's class)
    - Root class' ivars
    - penultimate superclass' ivars
    - ...
    - superclass' ivars
    - Class' ivars
    
- And the Class:    
  - The old way:
  typedef struct objc_class *Class;
  struct objc_class {
      Class isa;
      ...
  
  
  
      Classes and Metaclasses
      - A class is like an object, you can pass it messages
        - [MyClass alloc] 
        - Class methods are stored in the metaclass, which is where the Class isa pointer goes

        - diagram: http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html
        - Class is instance of metaclass
          - metaclass describes methods of class, just as class describes methods of instance
        - Meta-classes are instances of the root class' metaclass
          - which is also an instance of the root class' metaclass (ending in a cycle)
        - Meta-classes superclasses are the meta-classes of their corresponding class' superclass
          - but, the root meta-class' superclass is the root class
            - so, class objects respond to the root class' instance methods
        - Meta-classes are hidden from you ([NSObject class] #=> [NSObject self]), and are rarely accessed
  
  
  
  - The new way (OS X 64 & iOS)
    http://cocoawithlove.com/2010/01/getting-subclasses-of-objective-c-class.html
    From objc-runtime-new.h
  typedef struct class_ro_t {
    ...
    const char * name;
    const method_list_t * baseMethods;
    const protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    ...
    const property_list_t *baseProperties;
  } class_ro_t;
  
  typedef struct class_rw_t {
      ...
      const class_ro_t *ro;
      
      method_list_t **methods;
      struct chained_property_list *properties;
      const protocol_list_t ** protocols;
      
      struct class_t *firstSubclass;
      struct class_t *nextSiblingClass;
  } class_rw_t;
  
  typedef struct class_t {
      struct class_t *isa;
      struct class_t *superclass;
      Cache cache;
      IMP *vtable;
      uintptr_t data_NEVER_USE;  // class_rw_t * plus flags
      ...
      bool isRootClass() const {
          return superclass == NULL;
      }
      bool isRootMetaclass() const {
          return isa == this;
      }
  } class_t;
  
  
  - Class structure contains a metaclass pointer(?), superclass pointer, data about the class
    - data: name, ivars, methods, properties, protocols

  - Superclass pointer creates the hierarchy of classes
  ( Categories )
  - Methods, properties, and protocols define what the class can do
    - stored in writable section of class definition, which can be changed at runtime
    - this is how categories work (Ruby: Monkey-Patching)
    - Ivars are stored in the read-only section, unmodifiable as this would impact existing instances
      - thus, categories cannot change add ivars
  - objc_object isa pointer is not const --> change class at runtime
  - Class superclass pointer also not const --> change class hierarcy at runtime
    


- Now with ARC


Review
- In general: Messaging
  - Method: An actual piece of code associated with a class, and given a particular name
  - Message: A name and parameters sent to an object
  - Selector: A particular way of representing the name of a message or method
  - Message send: Taking a message and finding and executing the correct method

- SEL, IMP, method
  SEL - Selector, or name of method
  Method - A selector on a class
  IMP - The function itself
      - just a function that accepts an object pointer and selector
  
- It all comes from libobjc, a collection of C functions
  objc_msgSend --> [object message]

Sending Messages
- What is a method?
  Ex: - (int)foo:(NSString *)str { ...
  Is really:
    int SomeClass_method_foo_(SomeClass *self, SEL _cmd, NSString *str) { ...
  
  Ex: int result = [obj foo:@"hello"];
  Is really: 
    int result = ((int (*)(id, SEL, NSString *))objc_msgSend)(obj, @selector(foo:), @"hello");
    
  (Examples from: http://mikeash.com/pyblog/friday-qa-2009-03-20-objective-c-messaging.html)
  
    struct objc_method {
      SEL method_name                                          OBJC2_UNAVAILABLE;
      char *method_types                                       OBJC2_UNAVAILABLE;
      IMP method_imp                                           OBJC2_UNAVAILABLE;
    }                                                          OBJC2_UNAVAILABLE;
  - So, a method is
    - a name (selector, SEL)
    - a string containing argument and return types (created by @encode)
    - an IMP, or function pointer:
      typedef id 			(*IMP)(id, SEL, ...);





<<<== MESSAGING IN DETAIL (IN KEYNOTE, STARTS HERE)

- Messaging:
  objc_msgSend does the following
    - look up class of given object, be dereferencing it and grabbing ISA member
    - look at method list of class, search for selector
    - if not found, move to superclass and do the same
    - when found, jump to IMP
  - There is also a method cache, to speed up this lookup process for future messages
  - What about when no method found for a selector?
  
  
  - Messaging (from The Objective-C Runtime Programming Guide):
    - [receiver message]
      objc_msgSend(receiver, selector, arg1, arg2, ...)
    - dynamic binding:
      - find procedure (method implementation) for selector
      - call procedure, passing receiver and args
      - return the procedure's return value
    - isa / messaging framwork diagram p12 (Figure 3-1)
    - method implementations chosen at runtime == methods dynamically bound to messages
    - Hidden Arguments
      - receiver and selector are called "hidden" because aren't delared in the source code
      - source code can still refer to them:
        - self = receiver
        - _cmd = selector
      - You can use methodForSelector: to bypass dynamic binding... but, it's kind of cheating since this method is provided by Cocoa.
  
  
- See Xcode project examples
- What happens in objc_msgSend?
  - It's written in opcode in objc-msg-arm.s (and -i386.s, -simulator-i386.s, x86_64.s)
    /********************************************************************
     * id		objc_msgSend(id	self,
     *			SEL	op,
     *			...)
     *
     * On entry: a1 is the message receiver,
     *           a2 is the selector
     ********************************************************************/

    	ENTRY objc_msgSend
    # check whether receiver is nil
    	teq     a1, #0
    	itt	eq
    	moveq   a2, #0
    	bxeq    lr
    	...
    	
  - Order of operations:
    - Check if the receiver is nil --> nil-handler
    - If garbage collection available, short-circuit on certain selectors (retain, release, autorelease, retainCount) --> return self
    - Check class' cache for implementation, call it
    - Compare requested selector to selectors defined in class, call it
    - Compare to superclass, and up the chain
    - Lazy method resolution
      - Call resolveInstanceMethod: (or resolveClassMethod:), if returns YES, start over
        - starts over and assumes method has been added
    - Fast forwarding path
      - Call forwardingTargetForSelector:, if returns non-nil, send message to object (other than self)
        - starts over with new target
    - Normal forwarding path
      - Call methodSignatureForSelector:, if returns non-nil, create an NSInvocation and pass to forwardInvocation:
      - Call doesNotRecognizeSelector: --> throws exception by default
    
  - How to use this in a dynamic way?
    - Dynamic Method Resolution:
      - @dynamic synthesis of properites
        - Core Data NSManagedObject does this.
      - uses resolveInstanceMethod: and resolveClassMethod:
      - See ch-20 Person.m/h
    - Dynamic loading (not allowed in iOS)
      - System Preferences modules
      - NSBundle
    - Fast Forwarding
      - forwardingTargetForSelector: useful for proxy objects, such as CacheProxy.h/m
      - See ch-20 CacheProxy.m/h (not an awesome example)
      - The Higher Order Messaging (HOM) example is much more interesting
        (more on this below)
        
    - Normal Forwarding
      - Slower, but more flexible
    - Forwarding Failure w/ doesNotRecognizeSelector:

A (Slightly) Deep(er) Dive into objc_msgSend()
- README: http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/ (all 4 parts!)
  - Now we're getting crazy.
- objc_msgSend() is really a family of functions
  "each written to handle the calling conventions required to deal with various return types under the x86_64 ABI (Application Binary Interface). Oh, and the vtable dispatch stuff (different post)."
  - called 10s of millions of times just launching your app
    - That's why it's carefully crafted in assembly.
- Show objc_msgSend() assemby just to make the point that it's written in assembly?
- Method calls in ObjC are really just C function calls to objc_msgSend(), in which it figures out C function to call
    These are equivalent, generating the functionaly equivalent assembly: 
      - (id) doSomething: (NSUInteger) index;
      id doSomething(id self, SEL _cmd, NSUInteger index) { ... }
    
    
The Objective-C Runtime Programming Guide
- Objective-C runtime is open source (opensource.apple.com)
  - got objc4-493.11 here: http://opensource.apple.com/release/mac-os-x-1073/

- From the introduction:
  "The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work."

  "You should read this document to gain an understanding of how the Objective-C runtime system works and how you can take advantage of it. Typically, though, there should be little reason for you to need to know and understand this material to write a Cocoa application."
  
- Runtime interaction (from The Objective-C Runtime Programming Guide):
  - Through ObjC source
    - write and compile ObjC source
  - NSObject methods
    - allow introspection: 
      class, isMemberOfClass:, isKindOfClass:, respondsToSelector:, conformsToProtocol:, methodForSelector:
  - direct calls to runtime functions
    - Most ObjC boil down to these functions, allowing writing C code to replicate what ObjC compiles down to
    - dynamic shared library with a public interface consisting of a set of functions and data structures
      - Header files located in /usr/include/objc

  
- Message Forwarding (our concrete example?)
  - forwardInvocation: (it's kind of like Ruby's method_missing)
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([someOtherObject respondsToSelector:
                [anInvocation selector]])
            [anInvocation invokeWithTarget:someOtherObject];
        else
    [super forwardInvocation:anInvocation];
    }
  - Forwarding mimics multiple inheritance
    - provides features of multiple inheritance
    - but, multiple inheritance combines capabilities in single object
    - forwarding provides abilities in smaller objects, associated in transparent way to the message sender 
  - Implement "Array#map"
    - HOM Example: http://www.mikeash.com/pyblog/friday-qa-2009-04-24-code-generation-with-llvm-part-2-fast-objective-c-forwarding.html
      - The Complete Friday Q&A p 104
      - Super not-fun to implement, here's the simpler way...
      (This is old code and doesn't work... look at this just to illustrate concept)
      
      @interface ArrayMapProxyNormal : NSProxy
          {
              NSArray *_array;
          }
          - (id)initWithArray:(NSArray *)array;
          @end
          @implementation ArrayMapProxyNormal
          - (id)initWithArray:(NSArray *)array
          {
              _array = array;
              return self;
          }
          - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
          {
              return [[_array lastObject] methodSignatureForSelector:sel];
          }
          - (void)forwardInvocation:(NSInvocation *)inv
          {
              NSMutableArray *newArray = [NSMutableArray array];
              for(id obj in _array)
              {
                  id retval;
                  [inv invokeWithTarget:obj];
                  [inv getReturnValue:&retval;];
                  [newArray addObject:retval];
              }
              [inv setReturnValue:&newArray;];
          }
          @end

          
        OnNSArray Category:
          - (id)mapNormal
          {
              return [[[ArrayMapProxyNormal alloc] initWithArray:self] autorelease];
          }
      
      
      

Method Swizzling
- Swizzling: transparently replacing one thing with another at runtime
    in IOS, usually this is methods
- Warning: May cause App Rejection.
- Allow you to change the behaviors of Apple frameworks

- Why not use a category?
  - Category method replaces original method, with no way to call original method
  - Original method you wish to replace might have been implemented by category
    - There is no way to determine which category method "wins"
- Option 1 (more bad): method_exhangeImplementations
  - modifies selector, can break things
  - pseudo-recursive call can be misleading
  - Use funtion pointer approach in RNSwizzle instead
  - If still interested, read this:
    http://mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html
      - NSNotificationCenter Category Example:
        #import <objc/runtime.h> 
        #import <objc/message.h>
      
        @interface NSNotificationCenter (RNHijack)
        + (void)hijack;
        @end

        @implementation NSNotificationCenter (RNHijack)
        + (void)hijackSelector:(SEL)originalSelector withSelector:(SEL)newSelector
        {
            Class class = [NSNotificationCenter class];
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method overrideMethod = class_getInstanceMethod(class, newSelector);
            // method_exchangeImplementations(originalMethod, overrideMethod); // Can't do this!
            if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)))
            {
                class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
            }     
            else
            {
                method_exchangeImplementations(origMethod, overrideMethod);
            }
        }

        + (void)hijack
        {
            [self hijackSelector:@selector(removeObserver:)
                withSelector:@selector(RNHijack_removeObserver:)];
        }

        - (void)RNHijack_removeObserver:(id)notificationObserver
        {
            NSLog(@"Removing observer: %@", notificationObserver);
            [self RNHijack_removeObserver:notificationObserver];
        }
        
- Option 2 (less bad): NSObject Category RNSwizzle.m/h example (ch-20)
  
ISA Swizzling
- ISA pointer defines the object's class
- Modifying an object's class at runtime
- NSObject Category SetClass (ISASwizzle) 
  - accomplishes same goal as RNSwizzle, but uses ISA swizzling, instead of method swizzling
- Makes sure to check instance size before replacing
  - clobbering ISA pointer of object after this object is difficult to debug
  - hence the NSAssert ensuring both Class' instances are the same size
- This is a good solution for classes that are meant to be subclassed
- KVO is implemented with ISA swizzling
  - Allows frameworks to inject code into your classes
    - Now you can do the reverse

Downsides to Swizzling
- why might this be bad?  
  - tight coupling with implementation details
  - difficult to debug
  - unfamiliar to others
- we're going to talk about it because it reveals the dynamic nature of Objective-C
- Rob Napier suggests using ISA over method swizzling
  - only impacts specific objects, rather than all instances of a class
  - use method swizzling if you actually want to affect every instance of a class
  
  Rob Napier's talbe of Swizzling differences - P387 iOS-PTL
  



WHAT ABOUT...?
- KVO refresher
  - Complete Friday Q&A V1: How Key-Value Observing Works
  - KVO Done Right (Take 2): http://www.mikeash.com/pyblog/friday-qa-2012-03-02-key-value-observing-done-right-take-2.html
- Blocks
  - Complete Friday Q&A V1: 
    - Practical Blocks
    - Blocks in Objective-C

- Implement Clojure/Ruby collection methods with trampolining or blocks?
- Higher order messaging: http://cocoadev.com/index.pl?HigherOrderMessaging

- Complete Friday Q&A V1: Fast Objective-C Forwarding
  - implement HOM: map?
  
Tools:
- F-Script: sort of an Objective-C REPL
  - Smalltalk dialect
  - http://www.fscript.org/
    - syntax tutorial: http://www.fscript.org/documentation/ExploringCocoaWithFScript/index.htm




CREDITS:
- Everything:
  http://iosptl.com/
  The Objective-C Runtime Programming Guide
    https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
- C Basics:
  http://heather.cs.ucdavis.edu/~matloff/UnixAndC/CLanguage/PointersI.html
- Messaging:
  http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/
  http://mikeash.com/pyblog/friday-qa-2009-03-20-objective-c-messaging.html
  http://mikeash.com/book.html
- Swizzling:
  http://robnapier.net/blog/hijacking-methodexchangeimplementations-502

About

Just some fun experiments to see how this really works.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published