Effective Objective-C 2.0: Method Swizzling to Debug Opaque Methods

原创 2013年12月05日 23:53:24

运行时替换方法实现(通常是新增一个方法(category),然后swap); 这里推荐了一个,debug时候有点用

Item 13: Consider Method Swizzling to Debug Opaque Methods

The method to call when a message is sent to an object in Objective-C is resolved at runtime, as explained in Item 11. It might then occur to you that perhaps the method invoked for a given selector name could be changed at runtime. You’d be correct. This feature can be used to great advantage, as it can be used to change functionality in classes for which you don’t have the source code, without having to subclass and override methods. Thus, the new functionality can be used by all instances of the class rather than only instances of the subclass with overridden methods. Such an approach is often referred to as method swizzling.

A class’s method list contains a list of selector names to implementation mappings, telling the dynamic messaging system where to find the implementation of a given method. The implementations are stored as function pointers called IMPs and have the following prototype:

id (*IMP)(idSEL, ...)

The NSString class responds to selectors called lowercaseString,uppercaseString, and capitalizedString, among others. Each selector points to a different IMP, making up a table like the one shown in Figure 2.3.


Figure 2.3 NSString’s selector table

This table can be manipulated by using a few different functions exposed by the Objective-C runtime. You can add selectors to the list, change the implementation pointed to for a given selector, or swap the implementation pointed to by two selectors. After performing a few of these operations, the class’s method table might look something like Figure 2.4.


Figure 2.4 NSString’s selector table after performing a few operations on it

A new selector called newSelector has been added, the implementation ofcapitalizedString has been changed, and the implementations of lowercaseStringand uppercaseString have been swapped. All this can be done without writing a single subclass, and the new method table layout will be used for each instance ofNSString in the application. A very powerful feature, I’m sure you’ll agree!

The topic of this item refers to the process of exchanging implementations. In doing so, additional functionality can be added to a method. However, before explaining how it can be used to add functionality, I will explain how to simply swap two existing method implementations. To exchange implementations, you use the following function:

void method_exchangeImplementations(Method m1, Method m2)

This function takes as its arguments the two implementations to exchange, which can be obtained by using the following function:

Method class_getInstanceMethod(Class aClass, SEL aSelector)

This function retrieves a method from a class for the given selector. To swap the implementations of lowercaseString and uppercaseString as in the preceding example, you would perform the following:

Method originalMethod =
    class_getInstanceMethod([NSString class],
Method swappedMethod =
    class_getInstanceMethod([NSString class],
method_exchangeImplementations(originalMethod, swappedMethod);

From then on, whenever an NSString instance has lowercaseString called on it, the original implementation of uppercaseString will be invoked and vice versa:

NSString *string = @"ThIs iS tHe StRiNg";

NSString *lowercaseString = [string lowercaseString];
NSLog(@"lowercaseString = %@", lowercaseString);
// Output: lowercaseString = THIS IS THE STRING

NSString *uppercaseString = [string uppercaseString];
NSLog(@"uppercaseString = %@", uppercaseString);
// Output: uppercaseString = this is the string

That explains how to exchange method implementations, but in reality, simply swapping two implementations like that is not very useful. After all, there’s a good reason why the implementation for uppercaseString and lowercaseString do what they do! There’s no reason why you’d want to swap them. But the same approach can be used to add functionality to an existing method implementation. What if you wanted to log something every time lowercaseString was called? The same approach can be used to achieve just that. It involves adding another method that implements the additional functionality and then calls through to the original implementation.

The additional method can be added using a category, like so:

@interface NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString;

This method is going to be swapped with the original lowercaseString method, so the method table will end up looking like the one in Figure 2.5.


Figure 2.5 Swapping the implementations of lowercaseString andeoc_myLowercaseString

The implementation of this new method would look like this:

@implementation NSString (EOCMyAdditions)
- (NSString*)eoc_myLowercaseString {
    NSString *lowercase = [self eoc_myLowercaseString];
    NSLog(@"%@ => %@"self, lowercase);
    return lowercase;

This might look like a recursive call, but remember that the implementations are going to be swapped. So at runtime, when the eoc_myLowercaseString selector is looked up, it’s the implementation of lowercaseString that gets called. Finally, to swap the method implementations, the following is used:

Method originalMethod =
    class_getInstanceMethod([NSString class],
Method swappedMethod =
    class_getInstanceMethod([NSString class],
method_exchangeImplementations(originalMethod, swappedMethod);

From then on, whenever any NSString has lowercaseString called on it, the log line will be printed out:

NSString *string = @"ThIs iS tHe StRiNg";
NSString *lowercaseString = [string lowercaseString];
// Output: ThIs iS tHe StRiNg => this is the string

Being able to add in logging like this to methods that are completely opaque to you can be a very useful debugging feature. However, this is usually useful only for debugging. Rarely will you find a need other than debugging to perform method swizzling like this to alter functionality of a class globally. Don’t feel that you should use such a feature just because you can. Overuse can easily lead to code that is difficult to read and unmaintainable.

Things to Remember

Image Method implementations for a given selector of a class can be added and replaced at runtime.

Image Swizzling is the process of swapping one method implementation for another, usually to add functionality to the original implementation.

Image Meddling with methods through the runtime is usually good only for debugging and should not be used just because it can.



Effective Objective-C 2.0: Item 35: Use Zombies to Help Debug Memory-Management Problems

Item 35: Use Zombies to Help Debug Memory-Management Problems Debugging memory-management issues ...

Effective Objective-C 2.0: Item 17: Implement the description Method

如何重载description 和 debugDescription 赞 Item 17: Implement the description Method While debug...

Effective Objective-C 2.0:Item 20: Prefix Private Method Names

Item 20: Prefix Private Method Names It’s extremely common for a class to do much more than it ap...

Effective Objective-C 2.0: Item 30: Use ARC to Make Reference Counting Easier

Item 30: Use ARC to Make Reference Counting Easier Reference counting is a fairly easy concept to...

Effective Objective-C 2.0:Item 15: Use Prefix Names to Avoid Namespace Clashes

可惜没有指出怎么改第三方library名字的具体操作 Item 15: Use Prefix Names to Avoid Namespace Clashes Unlike other lan...

Effective Objective-C 2.0:Item 48: Prefer Block Enumeration to for Loops

Item 48: Prefer Block Enumeration to for Loops Enumerating a collection is a very common task in ...

Effective Objective-C 2.0: Item 4: Prefer Typed Constants to Preprocessor #define

Item 4: Prefer Typed Constants to Preprocessor #define 曾经面试被被问到 Preprocessor #define 有什么缺点, 这篇文章很...

Effective Objective-C 2.0: Item 41: Prefer Dispatch Queues to Locks for Synchronization

Item 41: Prefer Dispatch Queues to Locks for Synchronization Sometimes in Objective-C, you will com...

Effective Objective-C 2.0: Item 39: Use Handler Blocks to Reduce Code Separation

Item 39: Use Handler Blocks to Reduce Code Separation A common paradigm in programming a user int...

Objective-C运行时特性:Method Swizzling魔法

OC运行时特性,为我们提供了一个叫做Method Swizzling的方法魔法利器,利用它我们可以更加随心所欲的在运行时期间对编译器已经的方法再次动手脚,主要包括:交换类中某两个方法的实现、重新添加或...