Chapter 28: Deep Objective-C

Much of Objective-C is very straightforward in practice. There is no multiple inheritance or operator overloading like in C++. All objects have the same memory-management rules, which rely on a simple set of naming conventions. With the addition of ARC, you don’t even need to worry about memory management in most cases. The Cocoa framework is designed with readability in mind, so most things do exactly what they say they do.

Still, many parts of Objective-C appear mysterious until you dig into them, such as creating new methods and classes at runtime, introspection, and message passing. Most of the time, you don’t need to understand how this works, but for some problems, it’s very useful to harness the full power of Objective-C. For example, the flexibility of Core Data relies heavily on the dynamic nature of Objective-C.

The heart of this power is the Objective-C runtime, provided by libobjc. The Objective-C runtime is a collection of functions that provides the dynamic features of Objective-C. It includes such core functions as objc_msgSend, which is called every time you use the [object message] syntax. It also includes functions to allow you to inspect and modify the class hierarchy at runtime, including creating new classes and methods.

This chapter shows you how to use these features to achieve the same kind of flexibility, power, and speed as Core Data and other Apple frameworks. All code samples in this chapter can be found in the online files for Chapter 28.

Understanding Classes and Objects

The first thing to understand about Objective-C objects is that they’re really C structs. Every Objective-C object has the same layout, as shown in Figure 28-1.

First there is a pointer to your class definition. Then each of your superclasses’ ivars (instance variables) are laid out as struct properties, and then your class’s ivars are laid out as struct properties. This structure is called objc_object, and a pointer to it is called id:

typedef struct objc_object {

    Class isa;

} *id;

The Class structure contains a metaclass pointer (more on that in a moment), a superclass pointer, and data about the class. The data of particular interest are the name, ivars, methods, properties, and protocols. Don’t worry too much about the internal structure of Class. There are public functions to access all the information you need.

9781118449974-fg2801.eps

Figure 28-1 Layout of an Objective-C object

The Objective-C runtime is open source, so you can see exactly how it’s implemented. Go to the Apple Open Source site (www.opensource.apple.com), and look for the package objc in the Mac code. It isn’t included in the iOS packages, but the Mac code is identical or very similar. These particular structures are defined in objc.h and objc-runtime-new.h. There are two definitions of many things in these files because of the switch from Objective-C 1.0 to Objective-C 2.0. Look for things marked “new” when there is a conflict.

Class is itself much like an object. You can send messages to a Class instance—for example, when you call [Foo alloc]—so there has to be a place to store the list of class methods. These are stored in the metaclass, which is the isa pointer for a Class. It’s extremely rare to need to access metaclasses, so I won’t dwell on them here; see the “Further Reading” section at the end of this chapter for links to more information. See the section “How Message Passing Really Works,” later in this chapter, for more information on message passing.

The superclass pointer creates the hierarchy of classes, and the list of ivars, methods, properties, and protocols defines what the class can do. An important point here is that the methods, properties, and protocols are all stored in the writable section of the class definition. These can be changed at runtime, and that’s exactly how categories are implemented (refer to Chapter 3 for more information about categories). Ivars are stored in the read-only section and cannot be modified (because that could impact existing instances). That’s why categories cannot add ivars.

Notice in the definition of objc_object, shown at the beginning of this section, that the isa pointer is not const. That is not an oversight. The class of an object can be changed at runtime. The superclass pointer of Class is also not const. The hierarchy can be modified. This is covered in more detail in the “ISA Swizzling” section later in this chapter.

Now that you’ve seen the data structures underlying Objective-C objects, you next look at the kinds of functions you can use to inspect and manipulate them. These functions are written in C, and they use naming conventions somewhat similar to Core Foundation (see Chapter 27). All the functions shown here are public and are documented in the Objective-C Runtime Reference. The following is the simplest example:

#import <objc/objc-runtime.h>

...

const char *name = class_getName([NSObject class]);

printf(“%s ”, name);

Runtime methods begin with the name of the thing they act upon, which is almost always also their first parameter. Because this example includes get rather than copy, you don’t own the memory that is returned to you and should not call free.

The next example prints a list of the selectors that NSObject responds to. The call to class_copyMethodList returns a copied buffer that you must dispose of with free.

PrintObjectMethods.m (Runtime)

void PrintObjectMethods() {

  unsigned int count = 0;

  Method *methods = class_copyMethodList([NSObject class],

                                         &count);

  for (unsigned int i = 0; i < count; ++i) {

    SEL sel = method_getName(methods[i]);

    const char *name = sel_getName(sel);

    printf(“%s ”, name);

  }

  free(methods);

}

There is no reference counting (automatic or otherwise) in the runtime, so there is no equivalent to retain or release. If you fetch a value with a function that includes the word copy, you should call free on it. If you use a function that does not include the word copy, you must not call free on it.

Working with Methods and Properties

The Objective-C runtime defines several important types:

Class—Defines an Objective-C class, as described in the previous section, “Understanding Classes and Objects.”

Ivar—Defines an instance variable of an object, including its type and name.

Protocol—Defines a formal protocol.

objc_property_t—Defines a property. Its unusual name is probably to avoid colliding with user types defined in Objective-C 1.0 before properties existed.

Method—Defines an object method or a class method. This provides the name of the method (its selector), the number and types of parameters it takes and its return type (collectively its signature), and a function pointer to its code (its implementation).

SEL—Defines a selector. A selector is a unique identifier for the name of a method.

IMP—Defines a method implementation. It’s just a pointer to a function that takes an object, a selector, and a variable list of other parameters (varargs), and returns an object:

typedef id (*IMP)(id, SEL, ...);

Now you use this knowledge to build your own simplistic message dispatcher. A message dispatcher maps selectors to function pointers and calls the referenced function. The heart of the Objective-C runtime is the message dispatcher objc_msgSend, which you learn much more about in the next section, “How Message Passing Really Works.” The example myMsgSend is how objc_msgSend might be implemented if it needed to handle only the simplest cases.

The following code is written in C just to prove that the Objective-C runtime is really just C. I’ve added comments to demonstrate the equivalent Objective-C.

MyMsgSend.c (Runtime)

static const void *myMsgSend(id receiver, const char *name) {

  SEL selector = sel_registerName(name);

  IMP methodIMP =

  class_getMethodImplementation(object_getClass(receiver),

                                selector);

  return methodIMP(receiver, selector);

}

void RunMyMsgSend() {

  // NSObject *object = [[NSObject alloc] init];

  Class class = (Class)objc_getClass(“NSObject”);

  id object = class_createInstance(class, 0);

  myMsgSend(object, “init”);

  

  // id description = [object description];

  id description = (id)myMsgSend(object, “description”);

  

  // const char *cstr = [description UTF8String];

  const char *cstr = myMsgSend(description, “UTF8String”);

  

  printf(“%s ”, cstr);

}

With previous versions of the LLVM compiler, it was necessary to include a __bridge cast on the id. With the compiler that ships with iOS 6 (LLVM 4.1), many of these extra __bridge casts are unnecessary.

You can use this same technique in Objective-C using methodForSelector: to avoid the complex message dispatch of objc_msgSend. This only makes sense if you’re going to call the same method thousands of times on an iPhone. On a Mac, you won’t see much improvement unless you’re calling the same method millions of times. Apple has highly optimized objc_msgSend. But for very simple methods called many times, you may be able to improve performance five to ten percent this way.

The following example demonstrates how to do this and shows the performance impact.

FastCall.m (Runtime)

const NSUInteger kTotalCount = 10000000;

typedef void (*voidIMP)(id, SEL, ...);

void FastCall() {

  NSMutableString *string = [NSMutableString string];

  NSTimeInterval totalTime = 0;

  NSDate *start = nil;

  NSUInteger count = 0;

  

  // With objc_msgSend

  start = [NSDate date];

  for (count = 0; count < kTotalCount; ++count) {

    [string setString:@”stuff”];

  }

  

  totalTime = -[start timeIntervalSinceNow];

  printf(“w/ objc_msgSend = %f ”, totalTime);

  

  // Skip objc_msgSend.

  start = [NSDate date];

  SEL selector = @selector(setString:);

  voidIMP

  setStringMethod = (voidIMP)[string methodForSelector:selector];

  

  for (count = 0; count < kTotalCount; ++count) {

    setStringMethod(string, selector, @”stuff”);

  }

  

  totalTime = -[start timeIntervalSinceNow];

  printf(“w/o objc_msgSend  = %f ”, totalTime);

}

Be careful with this technique. If you do this incorrectly, it can actually be slower than using normal message dispatch. Since IMP returns an id, ARC will retain and later release the return value, even though this specific method returns nothing (see http://openradar.appspot.com/10002493 for details). That overhead is more expensive than just using the normal messaging system. In some case the extra retain can cause a crash. That’s why you have to add the extra voidIMP type. By declaring that the setStringMethod function pointer returns void, the compiler will skip the retain.

The important take-away is that you need to do testing on anything you do to improve performance. Don’t assume that bypassing the message dispatcher is going to be faster. In most cases, you’ll get much better and more reliable performance improvements by simply rewriting your code as a function rather than a method. And in the vast majority of cases, objc_msgSend is the least of your performance overhead.

How Message Passing Really Works

As demonstrated in the “Working with Methods and Properties” section earlier in this chapter, calling a method in Objective-C eventually translates into calling a method implementation function pointer and passing it an object pointer, a selector, and a set of function parameters. Like the example myMsgSend, every Objective-C message expression is converted into a call to objc_msgSend (or a closely related function; I’ll get to that in “The Flavors of objc_msgSend” later in this chapter). However, objc_msgSend is much more powerful than myMsgSend. Here is how it works:

1. Check whether the receiver is nil. If so, then call the nil-handler. This is really obscure, undocumented, unsupported, and difficult to make useful. The default is to do nothing, and I won’t go into it more here. See the “Further Reading” section for more information.

2. In a garbage-collected environment (which iOS doesn’t support, but I include for completeness), check for one of the short-circuited selectors (retain, release, autorelease, retainCount), and if it matches, return self. Yes, that means retainCount returns self in a garbage-collected environment. You shouldn’t have been calling it anyway.

3. Check the class’s cache to see if it’s already worked out this method implementation. If so, call it.

4. Compare the requested selector to the selectors defined in the class. If the selector is found, call its method implementation.

5. Compare the requested selector to the selectors defined in the superclass, and then its superclass, and so on. If the selector is found, call its method implementation.

6. Call resolveInstanceMethod: (or resolveClassMethod:). If it returns YES, start over. The object is promising that the selector will resolve this time, generally because it has called class_addMethod.

7. Call forwardingTargetForSelector:. If it returns non-nil, send the message to the returned object. Don’t return self here. That would be an infinite loop.

8. Call methodSignatureForSelector:, and if it returns non-nil, create an NSInvocation and pass it to forwardInvocation:.

9. Call doesNotRecognizeSelector:. The default implementation of this just throws an exception.

Dynamic Implementations

The first interesting thing you can do with message dispatch is provide an implementation at runtime using resolveInstanceMethod: and resolveClassMethod:. This is usually how @dynamic synthesis is handled. When you declare a property to be @dynamic, you’re promising the compiler that there will be an implementation available at runtime, even though the compiler can’t find one now. This prevents it from automatically synthesizing an ivar.

Here’s an example of how to use this to dynamically create getters and setters for properties stored in an NSMutableDictionary.

Person.h (Person)

@interface Person : NSObject

@property (copy) NSString *givenName;

@property (copy) NSString *surname;

@end

Person.m (Person)

@interface Person ()

@property (strong) NSMutableDictionary *properties;

@end

@implementation Person

@dynamic givenName, surname;

- (id)init {

  if ((self = [super init])) {

    _properties = [[NSMutableDictionary alloc] init];

  }

  return self;

}

static id propertyIMP(id self, SEL _cmd) {

  return [[self properties] valueForKey:

          NSStringFromSelector(_cmd)];

}

static void setPropertyIMP(id self, SEL _cmd, id aValue) {

  id value = [aValue copy];

  

  NSMutableString *key =

  [NSStringFromSelector(_cmd) mutableCopy];

  

  // Delete “set” and “:” and lowercase first letter

  [key deleteCharactersInRange:NSMakeRange(0, 3)];

  [key deleteCharactersInRange:

                         NSMakeRange([key length] - 1, 1)];

  NSString *firstChar = [key substringToIndex:1];

  [key replaceCharactersInRange:NSMakeRange(0, 1)

                  withString:[firstChar lowercaseString]];

  

  [[self properties] setValue:value forKey:key];

}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL {

  if ([NSStringFromSelector(aSEL) hasPrefix:@”set”]) {

    class_addMethod([self class], aSEL,

                    (IMP)setPropertyIMP, “v@:@”);

  }

  else {

    class_addMethod([self class], aSEL,

                    (IMP)propertyIMP, “@@:”);

  }

  return YES;

}

@end

main.m (Person)

int main(int argc, char *argv[]) {

  @autoreleasepool {

    Person *person = [[Person alloc] init];

    [person setGivenName:@”Bob”];

    [person setSurname:@”Jones”];

    

    NSLog(@”%@ %@”, [person givenName], [person surname]);

  }

}

In this example, you use propertyIMP as the generic getter and setPropertyIMP as the generic setter. Note how these functions make use of the selector to determine the name of the property. Also note that resolveInstanceMethod: assumes that any unrecognized selector is a property setter or getter. In many cases, this is okay. You still get compiler warnings if you pass unknown methods like this:

  [person addObject:@”Bob”];

But if you do it this way, you get a slightly surprising result:

  NSArray *persons = [NSArray arrayWithObject:person];

  id object = [persons objectAtIndex:0];

  [object addObject:@”Bob”];

You get no compiler warning because you can send any message to id. And you won’t get a runtime error either. You just retrieve the key addObject: (including the colon) from the properties dictionary and do nothing with it. This kind of bug can be difficult to track down, and you may want to add additional checking in resolveInstanceMethod: to guard against it. But the approach is extremely powerful. Although dynamic getters and setters are the most common use of resolveInstanceMethod:, it can also be used to dynamically load code in environments that allow dynamic loading. iOS doesn’t allow this approach, but on the Mac you can use resolveInstanceMethod: to avoid loading entire libraries until the first time one of the library’s classes is accessed. This can be useful for large but rarely used classes.

Fast Forwarding

The runtime gives you one more fast option before falling back to the standard forwarding system. You can implement forwardingTargetForSelector: and return another object to pass the message to. This is particularly useful for proxy objects or objects that add functionality to another object. The CacheProxy example demonstrates an object that caches the getters and setters for another object.

CacheProxy.h (Person)

@interface CacheProxy : NSProxy

- (id)initWithObject:(id)anObject

          properties:(NSArray *)properties;

@end

@interface CacheProxy ()

@property (readonly, strong) id object;

@property (readonly, strong)

                    NSMutableDictionary *valueForProperty;

@end

CacheProxy is a subclass of NSProxy rather than NSObject. NSProxy is a very thin root class designed for classes that forward most of their methods, particularly classes that forward their methods to objects hosted on another machine or on another thread. It’s not a subclass of NSObject, but it does conform to the <NSObject> protocol. The NSObject class implements dozens of methods that might be very hard to proxy. For example, methods that require the local run loop, like performSelector:withObject:afterDelay, might not make sense for a proxied object. NSProxy avoids most of these methods.

To implement a subclass of NSProxy, you must override methodSignatureForSelector: and forwardInvocation:. These throw exceptions if they’re called otherwise.

First, you need to create the getter and setter implementations, as in the Person example. In this case, if the value is not found in the local cache dictionary, you’ll forward the request to the proxied object.

CacheProxy.m (Person)

@implementation CacheProxy

// setFoo: => foo

static NSString *propertyNameForSetter(SEL selector) {

  NSMutableString *name =

  [NSStringFromSelector(selector) mutableCopy];

  [name deleteCharactersInRange:NSMakeRange(0, 3)];

  [name deleteCharactersInRange:

                        NSMakeRange([name length] - 1, 1)];

  NSString *firstChar = [name substringToIndex:1];

  [name replaceCharactersInRange:NSMakeRange(0, 1)

                  withString:[firstChar lowercaseString]];

  return name;

}

// foo => setFoo:

static SEL setterForPropertyName(NSString *property) {

  NSMutableString *name = [property mutableCopy];

  NSString *firstChar = [name substringToIndex:1];

  [name replaceCharactersInRange:NSMakeRange(0, 1)

                      withString:[firstChar uppercaseString]];

  [name insertString:@”set” atIndex:0];

  [name appendString:@”:”];

  return NSSelectorFromString(name);

}

// Getter implementation

static id propertyIMP(id self, SEL _cmd) {

  NSString *propertyName = NSStringFromSelector(_cmd);

  id value = [[self valueForProperty] valueForKey:propertyName];

  if (value == [NSNull null]) {

    return nil;

  }

  

  if (value) {

    return value;

  }

  

  value = [[self object] valueForKey:propertyName];

  [[self valueForProperty] setValue:value

                             forKey:propertyName];

  return value;

}

// Setter implementation

static void setPropertyIMP(id self, SEL _cmd, id aValue) {

  id value = [aValue copy];

  NSString *propertyName = propertyNameForSetter(_cmd);

  [[self valueForProperty] setValue:(value != nil ? value :

                                     [NSNull null])

                             forKey:propertyName];

  [[self object] setValue:value forKey:propertyName];

}

Note the use of [NSNull null] to manage nil values. You cannot store nil in an NSDictionary. In the next block of code, you’ll synthesize accessors for the properties requested. All other methods will be forwarded to the proxied object.

- (id)initWithObject:(id)anObject

          properties:(NSArray *)properties {

  _object = anObject;

  _valueForProperty = [[NSMutableDictionary alloc] init];

  for (NSString *property in properties) {

    // Synthesize a getter

    class_addMethod([self class],

                    NSSelectorFromString(property),

                    (IMP)propertyIMP,

                    “@@:”);

    // Synthesize a setter

    class_addMethod([self class],

                    setterForPropertyName(property),

                    (IMP)setPropertyIMP,

                    “v@:@”);

  }

  return self;

}

The next block of code overrides methods that are implemented by NSProxy. Because NSProxy has default implementations for these methods, they won’t be automatically forwarded by forwardingTargetForSelector:.

- (NSString *)description {

  return [NSString stringWithFormat:@”%@ (%@)”,

          [super description], self.object];

}

- (BOOL)isEqual:(id)anObject {

  return [self.object isEqual:anObject];

}

- (NSUInteger)hash {

  return [self.object hash];

}

- (BOOL)respondsToSelector:(SEL)aSelector {

  return [self.object respondsToSelector:aSelector];

}

- (BOOL)isKindOfClass:(Class)aClass {

  return [self.object isKindOfClass:aClass];

}

Finally, you’ll implement the forwarding methods. Each of them simply passes unknown messages to the proxied object. See Chapter 4 for more details on message signatures and invocations.

Whenever an unknown selector is sent to CacheProxy, objc_msgSend will call forwardingTargetForSelector:. If it returns an object, then objc_msgSend will try to send the selector to that object. This is called fast forwarding. In this example, CacheProxy sends all unknown selectors to the proxied object. If the proxied object doesn’t appear to respond to that selector, then objc_msgSend will fall back to normal forwarding by calling methodSignatureForSelector: and forwardInvocation:. This will be covered in the next section, “Normal Forwarding.” CacheProxy forwards these requests to the proxied object as well. Here are the rest of the CacheProxy methods:

- (id)forwardingTargetForSelector:(SEL)selector {

  return self.object;

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel

{

  return [self.object methodSignatureForSelector:sel];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

  [anInvocation setTarget:self.object];

  [anInvocation invoke];

}

@end

Normal Forwarding

After trying everything described in the previous sections, the runtime tries the slowest of the forwarding options: forwardInvocation:. This can be tens to hundreds of times slower than the mechanisms covered in the previous sections, but it’s also the most flexible. You are passed an NSInvocation, which bundles the target, the selector, the method signature, and the arguments. You may then do whatever you want with it. The most common thing to do is to change the target and invoke it, as demonstrated in the CacheProxy example. NSInvocation and NSMethodSignature are explained in Chapter 4.

If you implement forwardInvocation:, you also must implement methodSignatureForSelector:. That’s how the runtime determines the method signature for the NSInvocation it passes to you. Often this is implemented by asking the object you’re forwarding to.

There is a special limitation of forwardInvocation:. It doesn’t support vararg methods. These are methods such as arrayWithObjects: that take a variable number of arguments. There’s no way for the runtime to automatically construct an NSInvocation for this kind of method because it has no way to know how many parameters will be passed. Although many vararg methods terminate their parameter list with a nil, that is not required or universal (stringWithFormat: does not), so determining the length of the parameter list is implementation-dependent. The other forwarding methods, such as Fast Forwarding, do support vararg methods.

Even though forwardInvocation: returns nothing itself, the runtime system will return the result of the NSInvocation to the original caller. It does so by calling getReturnValue: on the NSInvocation after forwardInvocation: returns. Generally, you call invoke, and the NSInvocation stores the return value of the called method, but that isn’t required. You could call setReturnValue: yourself and return. This can be handy for caching expensive calls.

Forwarding Failure

Okay, so you’ve made it through the entire message resolution chain and haven’t found a suitable method. What happens now? Technically, forwardInvocation: is the last link in the chain. If it does nothing, then nothing happens. You can use it to swallow certain methods if you want to. But the default implementation of forwardInvocation: does do something. It calls doesNotRecognizeSelector:. The default implementation of that method just raises an NSInvalidArgumentException, but you could override this behavior. That’s not particularly useful because this method is required to raise NSInvalidArgumentException (either directly or by calling super), but it’s legal.

You can also call doesNotRecongizeSelector: yourself in some situations. For example, if you do not want anyone to call your init, you could override it like this:

- (id)init {

  [self doesNotRecognizeSelector:_cmd];

}

This makes calling init a runtime error. Personally, I often do it this way instead:

- (id)init {

  NSAssert(NO, @”Use -initWithOptions:”);

  return nil;

}

That way it crashes when I’m developing, but not in the field. Which form you prefer is somewhat a matter of taste.

You should, of course, call doesNotRecognizeSelector: in methods like forwardInvocation: when the method is unknown. Don’t just return unless you specifically mean to swallow the error. That can lead to very challenging bugs.

The Flavors of objc_msgSend

In this chapter, I’ve referred generally to objc_msgSend, but there are several related functions: objc_msgSend_fpret, objc_msgSend_stret, objc_msgSendSuper, and objc_msgSendSuper_stret. The SendSuper form is obvious. It sends the message to the superclass. The stret forms handle most cases when you return a struct. This is for processor-specific reasons related to how arguments are passed and returned in registers versus on the stack. I won’t go into all the details here, but if you’re interested in this kind of low-level detail, then you should read Hamster Emporium (see “Further Reading”). Similarly, the fpret form handles the case when you return a floating-point value on an Intel processor. It isn’t used on the ARM-based processors that iOS runs on, but it is used when you compile for the simulator. There is no objc_msgSendSuper_fpret because the floating-point return only matters when the object you’re messaging is nil (on an Intel processor), and that’s not possible when you message super.

The point of all this is not, obviously, to address the processor-specific intricacies of message passing. If you’re interested in that, read Hamster Emporium. The point is that not all message passing is handled by objc_msgSend, and you cannot use objc_msgSend to handle any arbitrary method call. In particular, you cannot return a “large” struct with objc_msgSend on any processor, and you cannot safely return a floating point with objc_msgSend on Intel processors (such as when compiling for the simulator). This generally translates into: Be careful when you try to bypass the compiler by calling objc_msgSend by hand.

Method Swizzling

In Objective-C, swizzling refers to transparently swapping one thing for another. Generally, it means replacing methods at runtime. Using method swizzling, you can modify the behavior of objects that you do not have the code for, including system objects. In practice, swizzling is fairly straightforward, but it can be a little confusing to read. For this example, you add logging every time you add an observer to NSNotificationCenter.

Since iOS 4.0, Apple has rejected some applications from the AppStore for using this technique.

First you add a category on NSObject to simplify swizzling:

RNSwizzle.h (MethodSwizzle)

@interface NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector

               withIMP:(IMP)newIMP;

@end

RNSwizzle.m (MethodSwizzle)

@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector

               withIMP:(IMP)newIMP {

  Class class = [self class];

  Method origMethod = class_getInstanceMethod(class,

                                             origSelector);

  IMP origIMP = method_getImplementation(origMethod);

  

  if(!class_addMethod(self, origSelector, newIMP,

                      method_getTypeEncoding(origMethod))) {

    method_setImplementation(origMethod, newIMP);

  }

  

  return origIMP;

}

@end

Now, look at this in more detail. You pass a selector and a function pointer (IMP) to this method. What you want to do is to swap the current implementation of that method with the new implementation and return a pointer to the old implementation so you can call it later. You have to consider three cases: The class may implement this method directly, the method may be implemented by one of the superclass hierarchy, or the method may not be implemented at all. The call to class_getInstanceMethod returns an IMP if either the class or one of its superclasses implements the method; otherwise, it returns NULL.

If the method was not implemented at all, or if it’s implemented by a superclass, then you need to add the method with class_addMethod. This is identical to overriding the method normally. If class_addMethod fails, you know the class directly implemented the method you’re swizzling. You instead need to replace the old implementation with the new implementation using method_setImplementation.

When you’re done, you return the original IMP, and it’s your caller’s problem to make use of it. You do that in a category on the target class, NSNotificationCenter, as shown in the following code.

NSNotificationCenter+RNSwizzle.h (MethodSwizzle)

@interface NSNotificationCenter (RNSwizzle)

+ (void)swizzleAddObserver;

@end

NSNotificationCenter+RNSwizzle.m (MethodSwizzle)

@implementation NSNotificationCenter (RNSwizzle)

typedef void (*voidIMP)(id, SEL, ...);

static voidIMP sOrigAddObserver = NULL;

static void MYAddObserver(id self, SEL _cmd, id observer,

                          SEL selector,

                          NSString *name,

                          id object) {

  NSLog(@”Adding observer: %@”, observer);

  

  // Call the old implementation

  NSAssert(sOrigAddObserver,

           @”Original addObserver: method not found.”);

  if (sOrigAddObserver) {

    sOrigAddObserver(self, _cmd, observer, selector, name,

                     object);

  }

}

+ (void)swizzleAddObserver {

  NSAssert(!sOrigAddObserver,

           @”Only call swizzleAddObserver once.”);

  SEL sel = @selector(addObserver:selector:name:object:);

  sOrigAddObserver = (void *)[self swizzleSelector:sel

                              withIMP:(IMP)MYAddObserver];

}

@end

You call swizzleSelector:withIMP:, passing a function pointer to your new implementation. Notice that this is a function, not a method, but as covered in “How Message Passing Really Works” earlier in this chapter, a method implementation is just a function that accepts an object pointer and a selector. Notice also the voidIMP type. See the section “Working with Methods and Properties” earlier in this chapter for how this interacts with ARC. Without that, ARC will try to retain the non-existant return value, causing a crash.

You then save off the original implementation in a static variable, sOrigAddObserver. In the new implementation, you add the functionality you want, and then call the original function directly.

Finally, you need to actually perform the swizzle somewhere near the beginning of your program:

  [NSNotificationCenter swizzleAddObserver];

Some people suggest doing the swizzle in a +load method in the category. That makes it much more transparent, which is why I don’t recommend it. Method swizzling can lead to very surprising behaviors. Using +load means that just linking the category implementation will cause it to be applied. I’ve personally encountered this when bringing old code into a new project. One of the debugging assistants from the old project had this kind of auto-load trick. It wasn’t being compiled in the old project; it just happened to be in the sources directory. When I used “add folder” in Xcode, even though I didn’t make any other changes to the project, the debug code started running. Suddenly the new project had massive debug files showing up on customer machines, and it was very difficult to figure out where they were coming from. So my experience is that using +load for this can be dangerous. On the other hand, it’s very convenient and automatically ensures that it’s only called once. Use your best judgment here.

Method swizzling is a very powerful technique and can lead to bugs that are very hard to track down. It allows you to modify the behaviors of Apple-provided frameworks, but that can make your code much more dependent on implementation details. It always makes the code more difficult to understand. I typically do not recommend it for production code except as a last resort, but it’s extremely useful for debugging, performance profiling, and exploring Apple’s frameworks.

There are several other method swizzling techniques. The most common is to use method_exchangeImplementations to swap one implementation for another. That approach modifies the selector, which can sometimes break things. It also creates an awkward pseudo-recursive call in the source code that is very misleading to the reader. This is why I recommend using the function pointer approach detailed here. For more information on swizzling techniques, see the “Further Reading” section.

ISA Swizzling

As discussed in the “Understanding Classes and Objects” section earlier in this chapter, an object’s ISA pointer defines its class. And, as discussed in “How Message Passing Really Works” (also earlier in this chapter), message dispatch is determined at runtime by consulting the list of methods defined on that class. So far, you’ve learned ways of modifying the list of methods, but it’s also possible to modify an object’s class (ISA swizzling). The next example demonstrates ISA swizzing to achieve the same NSNotificationCenter logging you did in the previous section, “Method Swizzling.”

First, you create a normal subclass of NSNotificationCenter, which you’ll use to replace the default NSNotificationCenter.

MYNotificationCenter.h (ISASwizzle)

@interface MYNotificationCenter : NSNotificationCenter

// You MUST NOT define any ivars or synthesized properties here.

@end

@implementation MYNotificationCenter

- (void)addObserver:(id)observer selector:(SEL)aSelector

               name:(NSString *)aName object:(id)anObject

{

  NSLog(@”Adding observer: %@”, observer);

  [super addObserver:observer selector:aSelector name:aName

              object:anObject];

}

@end

There’s nothing really special about this subclass. You could +alloc it normally and use it, but you want to replace the default NSNotificationCenter with your class.

Next, you create a category on NSObject to simplify changing the class:

NSObject+SetClass.h (ISASwizzle)

@interface NSObject (SetClass)

- (void)setClass:(Class)aClass;

@end

NSObject+SetClass.m (ISASwizzle)

@implementation NSObject (SetClass)

- (void)setClass:(Class)aClass {

  NSAssert(

    class_getInstanceSize([self class]) ==

      class_getInstanceSize(aClass),

    @”Classes must be the same size to swizzle.”);

  object_setClass(self, aClass);

}

@end

Now, you can change the class of the default NSNotificationCenter:

  id nc = [NSNotificationCenter defaultCenter];

  [nc setClass:[MYNotificationCenter class]];

The most important thing to note here is that the size of MYNotificationCenter must be the same as the size of NSNotificationCenter. In other words, you can’t declare any ivars or synthesized properties (synthesized properties are just ivars in disguise). Remember, the object you’re swizzling has already been allocated. If you added ivars, then they would point to offsets beyond the end of that allocated memory. This has a pretty good chance of overwriting the isa pointer of some other object that just happens to be after this object in memory. In all likelihood, when you finally do crash, the other (innocent) object will appear to be the problem. This is an incredibly difficult bug to track down, which is why I take the trouble of building a category to wrap object_setClass. I believe it’s worth it to include the NSAssert ensuring the two classes are the same size.

After you’ve performed the swizzle, the impacted object is identical to a normally created subclass. This means that it’s very low-risk for classes that are designed to be subclassed. As discussed in Chapter 22, key-value observing (KVO) is implemented with ISA swizzling. This allows the system frameworks to inject notification code into your classes, just as you can inject code into the system frameworks.

Method Swizzling Versus ISA Swizzling

Both method and ISA swizzling are powerful techniques that can cause a lot of problems if used incorrectly. In my experience, ISA swizzling is a better technique and should be used when possible because it impacts only the specific objects you target, rather than all instances of the class. However, sometimes your goal is to impact every instance of the class, so method swizzling is the only option. The following list defines the differences between method swizzling and ISA swizzling:

Method Swizzling

Impacts every instance of the class.

Highly transparent. All objects retain their class.

Requires unusual implementations of override methods.

ISA Swizzling

Only impacts the targeted instance.

Objects change class (though this can be hidden by overriding class).

Override methods are written with standard subclass techniques.

Summary

The Objective-C runtime can be an incredibly powerful tool once you understand it. With it you can modify classes and instances at runtime, injecting new methods and even whole new classes. Used recklessly, these techniques can lead to incredibly difficult bugs, but used carefully and in isolation, the Objective-C runtime is an important part of advanced iOS development.

Further Reading

Apple Documentation

The following document is available in the iOS Developer Library at developer.apple.com or through the Xcode Documentation and API Reference.

Objective-C Runtime Programming Guide

Other Resources

Ash, Mike. NSBlog. A very insightful blog covering all kinds of low-level topics.www.mikeash.com/pyblog

• Friday Q&A 2009-03-20: Objective-C Messaging

Friday Q&A 2010-01-29: Method Replacement for Fun and Profit. The method-swizzling approach in this chapter is a refinement of Mike Ash’s approach.

bbum. weblog-o-mat. bbum is a prolific contributor to Stackoverflow, and his blog has some of my favorite low-level articles, particularly his four-part opcode-by-opcode analysis of objc_msgSend.friday.com/bbum

“Objective-C: Logging Messages to Nil”

“objc_msgSend() Tour”

CocoaDev, “MethodSwizzling.” CocoaDev is an invaluable wiki of all-things-Cocoa. The MethodSwizzling page covers the major implementations out there.www.cocoadev.com/index.pl?MethodSwizzling

Parker, Greg. Hamster Emporium. Although there aren’t a lot of posts here, this blog provides incredibly useful insights into the Objective-C runtime.www.sealiesoftware.com/blog/archive

“[objc explain]: Classes and metaclasses”

“[objc explain]: objc_msgSend_fpret”

“[objc explain]: objc_msgSend_stret”

“[objc explain]: So you crashed in objc_msgSend()”

“[objc explain]: Non-fragile ivars”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.185.42