运行时替换方法实现(通常是新增一个方法(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 IMP
s and have the following prototype:
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.
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.
A new selector called newSelector
has been added, the implementation ofcapitalizedString
has been changed, and the implementations of lowercaseString
and 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],
@selector(lowercaseString));
Method swappedMethod =
class_getInstanceMethod([NSString class],
@selector(uppercaseString));
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;
@end
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.
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;
}
@end
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],
@selector(lowercaseString));
Method swappedMethod =
class_getInstanceMethod([NSString class],
@selector(eoc_myLowercaseString));
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
Method implementations for a given selector of a class can be added and replaced at runtime.
Swizzling is the process of swapping one method implementation for another, usually to add functionality to the original implementation.
Meddling with methods through the runtime is usually good only for debugging and should not be used just because it can.