Understanding Method Swizzling is a good opportunity to learn the runtime mechanism. Not much to do in this, only translated by Mattt Thompson published in Nshipster's method swizzling article.
Method Swizzling is a technique that changes the actual implementation of a selector. With this technique, we can modify the implementation of the method at run time by selector corresponding function in the class's sub-publication.
For example, we want to track the number of times each view controller in a program is presented to a user: Of course, we can add tracking code to each view controller's viewdidappear, but it's too cumbersome to Write duplicate code in the controller. Creating a subclass may be an implementation, but you need to create Uiviewcontroller, Uitableviewcontroller, Uinavigationcontroller, and other subclasses of the view controller in Uikit , this also produces a lot of duplicated code.
In this case, we can use method swizzling, as shown in the code:
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
Here, we modify the Uiviewcontroller @selector (viewwillappear:) corresponding function pointer by method Swizzling, so that its implementation points to our custom xxx_ The implementation of Viewwillappear. This will print a log message when an object of Uiviewcontroller and its subclasses calls Viewwillappear.
The above example is a good illustration of using method swizzling to inject some of our new operations into a class. Of course, there are many scenarios where you can use method swizzling, not much for example. Here we talk about some of the issues to be aware of using method swizzling:
Swizzling should always be executed in +load.
In Objective-c, the runtime automatically calls two methods for each class. +load is called when the class is initially loaded, and +initialize is called before the class method or instance method of the class is called for the first time. These two methods are optional and are called only if they are implemented. Because method swizzling affects the global state of a class, it is best to avoid competition in concurrent processing. +load is guaranteed to be loaded during the initialization of the class and ensures that this changes the consistency of the application-level behavior. By contrast, +initialize does not provide this assurance when it executes-in fact, it may never be called if a message is not sent to this class in the application.
Swizzling should always be executed in dispatch_once.
The same as above, because swizzling will change the global state, so we need to take some precautions at runtime. Atomicity is a measure that ensures that code is executed only once, regardless of the number of threads. GCD's dispatch_once can ensure this behavior and we should use it as a best practice for method swizzling.
Selectors, methods and implementations
In Objective-c, selectors (selector), methods, and implementations (implementation) are a special point in the runtime, although in general, these terms are more commonly used in the process descriptions of message delivery.
Here are some descriptions of these terms in the Objective-c Runtime reference:
- Selector (typedef struct OBJC_SELECTOR *sel): The name used to represent a method at run time. A method selector is a C string that is registered when the OBJECTIVE-C is run. The selector is generated by the compiler and is automatically mapped by the runtime when the class is loaded.
- Method (typedef struct OBJC_METHOD *method): Represents the type of the methods in a class definition
- Implementation (typedef ID (*IMP) (ID, SEL, ...)) : This is a pointer type that points to where the method implementation function begins. This function uses the standard C call specification implemented for the current CPU architecture. Each parameter is a pointer to the object itself (self), and the second parameter is the method selector. Then is the actual parameter of the method.
The best way to understand the relationship between these terms is to maintain a class that maintains a message distribution that can be received at runtime; each entry in a distribution is a method, where key is a specific name, the selector (SEL), which corresponds to an implementation (IMP), which is a pointer to the underlying C function.
To Swizzle a method, we can map an existing selector of a method to a different implementation in the sub-publication, and associate the original implementation of the selector to a new selector.
Call _cmd
Let's look back at the implementation code for the new approach:
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
}
I seem to be causing an infinite loop. But surprisingly, this is not the case. In the process of swizzling, [self xxx_viewwillappear:animated] in the method has been re-assigned to-viewwillappear: in the Uiviewcontroller class. In this case, no infinite loops are generated. However, if we invoke [self viewwillappear:animated], an infinite loop is generated because the implementation of this method has been re-designated as Xxx_viewwillappear at run time:.
Precautions
Swizzling is often called a black magic, prone to unpredictable behavior and unforeseen consequences. Although it is not the safest, it is safer to comply with the following precautionary measures:
- Always call the original implementation of the method (unless there is a better reason not to do so): The API provides an input and output convention, but its internal implementation is a black box. Swizzle a method without invoking the original implementation may break the underlying operation of the private state, thus affecting the rest of the program.
- Avoid conflicts: Prefix a custom classification method so that it does not have a naming conflict with the code base it depends on.
- Understand what's going on: simply copying and pasting Swizzle code without understanding how it works, is not only dangerous, but also wastes the opportunity to learn objective-c run time. Read the Objective-c Runtime Reference and view the <objc/runtime.h> header file to see how the event occurred.
- Caution: No matter how confident we are with the foundation, Uikit, or other built-in framework to perform swizzle operations, we need to know that many things may be different in the next release.
OBJECTIVE-C runtime Four: Method swizzling (reprint)