The original link



preface

This article is last week when I saw the source code of DZNEmptyDataSet, I saw an interesting code. What I have been reading about Swizzling is the Method Swizzling version of Mattt (AFNetworking uses Swizzling in this way). Just the author of DZNEmptyDataSet posted the Blog address of the source blogger, which is translated here to share with you. DZNEmptyDataSet about Swizzling source code implementation:

 // We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation
    [self swizzleIfPossible:@selector(reloadData)];
    
    // Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates
    if ([self isKindOfClass:[UITableView class]]) {
        [self swizzleIfPossible:@selector(endUpdates)];
    }
Copy the code

- (void)swizzleIfPossible:(SEL)selector
{
    // Create the lookup table

    Class baseClass = dzn_baseClassToSwizzleForTarget(self);
    NSString *key = dzn_implementationKey(baseClass, selector);
    NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
    
    // If the implementation for this class already exist, skip!!
    if(impValue || ! key || ! baseClass) {return;
    }
    
    // Swizzle by injecting additional implementation
    Method method = class_getInstanceMethod(baseClass, selector);
    IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
    
    // Store the new implementation in the lookup table
    NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
                                   DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
                                   DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
    [_impLookupTable setObject:swizzledInfo forKey:key];
}
Copy the code

void dzn_original_implementation(id self, SEL _cmd)
{
    // Fetch original implementation from lookup table
    Class baseClass = dzn_baseClassToSwizzleForTarget(self);
    NSString *key = dzn_implementationKey(baseClass, _cmd);
    
    NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
    NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
    
    IMP impPointer = [impValue pointerValue];
    
    // We then inject the additional implementation for reloading the empty dataset
    // Doing it before calling the original implementation does update the 'isEmptyDataSetVisible' flag on time.
    [self dzn_reloadEmptyDataSet];
    
    // If found, call original implementation
    if(impPointer) { ((void(*)(id,SEL))impPointer)(self,_cmd); }}Copy the code


/ * * * * * * * * * * * * the following is the content of the translation (English is good can be directly click on the title of the original link) * * * * * * * * * * * * * /

Swizzling changes the functionality of a method by replacing one implementation of a method with another, usually at run time. There are many different requirements that Swizzling may need to be used, Now we can look at introspection, overriding default behavior, or maybe even dynamic method loading. I’ve read a lot of blogs discussing Objective-C Swizzling, and many of them recommend some very bad practices. These bad practices aren’t a big deal if you’re writing standalone applications, but if you’re writing frameworks for third-party people, Swizzling can mess up some of the basic execution that works. So what’s the right way to Swizzling in Objective-C?

Let’s start with the basics. When we say Swizzling, we usually mean replacing the behavior of source methods with custom methods, and we usually call source methods inside our own defined methods. Objective-c allows you to do this using methods provided by the Objective-C runtime. At run time, objective-C methods represent a C structure called Method; This structure is defined as follows:

struct objc_method    
         SEL method_name         OBJC2_UNAVAILABLE;  
         char *method_types      OBJC2_UNAVAILABLE;  
         IMP method_imp          OBJC2_UNAVAILABLE;
}
Copy the code

Method_name is a method selector, *method_types is a C string encoded by parameter and return value types, and method_IMP is a function pointer to the actual function (more on IMP later).

We can access this object using the following methods:

Method class_getClassMethod(Class aClass, SEL aSelector);
Method class_getInstanceMethod(Class aClass, SEL aSelector);
Copy the code

You can access and change the underlying implementation of an object by accessing its Method structure. Method_imp is an IMP type, which is defined as id (*IMP) (id, SEL…). Or a function that takes an object pointer, a selector, and a list of additional variables, and returns an object pointer. This can be changed by IMP method_setImplementation (Method Method, IMP IMP). Pass the replacement implementation, IMP, through method_setImplementation (), and pass in the structure of the method you want to change, method, which returns the source IMP implementation associated with the method. That’s the right way to Swizzling.

What is wrong with the Swizzle way?

This is a common recipe. While it may seem simple enough – swapping an implementation of one method for another – there are some non-obvious implications.

void method_exchangeImplementations(Method m1, Method m2)
Copy the code

To understand these effects, let’s look at the structure of M1 and m2 before and after this function is called.

 Method m1 { //this is the original method. we want to switch this one with
             //our replacement method
      SEL method_name = @selector(originalMethodName)
      char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
      IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
 }
Copy the code

Method m2 { //this is the swizzle method. We want this method executed when [MyClass
             //originalMethodName] is called
       SEL method_name = @selector(swizzle_originalMethodName)
       char *method_types = “v@:”
       IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
 }
Copy the code

These are the method structures before we call the function. The Objective-C code to generate these structures would look like this:

@implementation MyClass - (void) originalMethodName //m1 {//code} - (void) swizzle_originalMethodName //m2 {//... code? [self swizzle_originalMethodName]; / / call the original method / /... code? } @endCopy the code

Then we call the following code:

m1 = class_getInstanceMethod([MyClass class], @selector(originalMethodName));
m2 = class_getInstanceMethod([MyClass class], @selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2)
Copy the code

Now the method structure will look like this:

Method m1 { //this is the original Method struct. we want to switch this one with //our replacement method SEL Method_name = @selector(originalMethodName) char *method_types = "v@:" params id(self),selector(_cmd) IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName]) }Copy the code

Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass //originalMethodName] is Called SEL method_name = @selector(swizzle_originalMethodName) char *method_types = "V @:" IMP method_IMP = 0x000FFFF (MyBundle`[MyClass originalMethodName]) }Copy the code

Note that if we want to execute the original method code, we have to call – [self swizzle_originalMethodName], but this causes the _cmd value to be passed to the original method code, Now it’s @selector (swizzle_originalMethodName), if the method code depends on _cmd being the originalMethodName (originalMethodName). This chaotic approach (the example below) prevents the program from running properly and should be avoided.

- (void) originalMethodName //m1 {assert([NSStringFromSelector(_cmd) isEqualToString:@ "originalMethodNamed"]); //this fails after swizzling //using //method_exchangedImplementations() // }Copy the code

Now let’s look at Swizzling the appropriate way using method_setImplementation().

The right Swizzle way

Create a C function that conforms to IMP’s definition, instead of creating an Objective-C function -[(void) swizzle_originalMethodName].

void __Swizzle_OriginalMethodName(id self, SEL _cmd)
 {
      //code
 }
Copy the code

We can execute this function as an IMP:

IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;
Copy the code

And this allows us to pass this IMP to method_setImplementation() :

method_setImplementation(method, swizzleImp);
Copy the code

And method_setImplementation() returns the original IMP:

IMP originalImp = method_setImplementation(method,swizzleImp);
Copy the code

OriginalImp can now be used to call the original method:

originalImp(self,_cmd);
Copy the code

Here is the full example implementation:

@interface SwizzleExampleClass : NSObject
 - (void) swizzleExample;
 - (int) originalMethod;
 @end
Copy the code

static IMP __original_Method_Imp;
 int _replacement_Method(id self, SEL _cmd)
 {
      assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
      //code
     int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
    return returnValue + 1;
 }
 @implementation SwizzleExampleClass
Copy the code

- (void) swizzleExample //call me to swizzle
 {
     Method m = class_getInstanceMethod([self class],
 @selector(originalMethod));
     __original_Method_Imp = method_setImplementation(m,
 (IMP)_replacement_Method);
 }
Copy the code

- (int) originalMethod
 {
        //code
        assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
        return 1;
 }
@end
Copy the code

Execute the following code to verify:

SwizzleExampleClass* example = [[SwizzleExampleClass alloc] init];
int originalReturn = [example originalMethod];
[example swizzleExample];
int swizzledReturn = [example originalMethod];
assert(originalReturn == 1); //true
assert(swizzledReturn == 2); //true
Copy the code

Anyway, to avoid collisions with other third party SDKS, please don’t use objective-C methods mixed with method_swapImplementations (), but instead use C function with method_setImplementation (), Convert these C functions to IMP. This avoids all the extra information wrappers that come with objective-C methods, such as the new selector name. If you want to adjust, the best result is no trace. † Remember that all Objective-C methods pass two hidden arguments: a reference to self (id self) and the method’s selector (SEL _cmd). If it returns a blank, you may have to deal with the IMP call. This is because ARC assumes that all IMPs return an ID and will try to preserve void and the original type.

IMP anImp; //represents objective-c function
          // -UIViewController viewDidLoad;
 ((void(*)(id,SEL))anImp)(self,_cmd); //call with a cast to prevent
                                     // ARC from retaining void.

Copy the code