Study harmoniously! Don’t be impatient!! I’m your old friend, Xiao Qinglong

Method_Swizling

Method_Swizling is a familiar Method_Swizling method that swaps the IMP points of two SEL methods. In general, we use cateogry to swap methods in the +load() method.

Conventional method exchange:

// SSJPerson.h
@interface SSJPerson : NSObject
- (void)person_walk;
@end

// SSJPerson.m
#import "SSJPerson.h"
@implementation SSJPerson
- (void)person_walk{
    NSLog(@"SSJPerson ---> person_walk");
}

// SSJStudent.h
// SSJStudent inherits from SSJPerson
@interface SSJStudent : SSJPerson
- (void)student_sleep;
@end

// SSJStudent.m
#import "SSJStudent.h"
@implementation SSJStudent
- (void)student_sleep{
    NSLog(@"SSJStudent ---> student_sleep");
}
@end


//SSJStudent+category.m
#import "SSJStudent+category.h"
#import <objc/runtime.h>

@implementation SSJStudent (category)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(student_sleep));
        Method swizzlingMethod = class_getInstanceMethod([self class], @selector(student_sleepNew));
        method_exchangeImplementations(originalMethod, swizzlingMethod);
    });
    
}
- (void)student_sleepNew{
    NSLog(@"Replaced method -- > student_sleepNew");
    [self student_sleepNew];
}
@end
Copy the code

When called:

SSJStudent *stu = [SSJStudent new];
[stu student_sleep];
Copy the code

It also works fine:

The parent class is implemented, the child class is not implemented

What about the method that replaces a parent class that is already implemented, but not currently implemented by the Cateogry class? Modify the code:

// SSJStudent+category.m
#import "SSJStudent+category.h"
#import <objc/runtime.h>

@implementation SSJStudent (category)
+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        /// replace the parent method: person_walk
        // the parent class implements person_walk, and the subclass does not implement person_walk
        Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
        Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew));
        method_exchangeImplementations(originalMethod, swizzlingMethod);
    });
    
}

//- (void)student_sleepNew{
// NSLog(@" replaced method -- > student_sleepNew");
// [self student_sleepNew];
/ /}

- (void)person_walkNew{
    NSLog(@"Replaced method -- > person_walkNew");
    [self person_walkNew];
}

@end
Copy the code
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    SSJStudent *student = [SSJStudent new];
    /// change to person_walk
    [student person_walk];
}

@end
Copy the code

Run:

If you look at the print, subclasses can call person_walk. Here we add two lines of code to viewController.m:

// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    SSJStudent *student = [SSJStudent new];
    /// change to person_walk
    [student person_walk];
    NSLog(@"\n");
    /// Add code, the parent class also calls person_walk
    SSJPerson *person = [SSJPerson new];
    [person person_walk];
}

@end
Copy the code

Could not find method:

I’ve drawn a picture here:

  • Because SSJStudent+category implements the +load() method, the program calls the +load() method during the load_images phase.

  • The +load() method replaces the parent class (SSJPerson) ‘s person_walk method, causing the parent class to call its own person_walk method and say that it can’t find the specific implementation of person_walkNew because the parent class doesn’t have the method.

In real multiplayer development, the person providing the parent class may not know that you switched the methods of the parent class. When he calls his own parent class method, he may be confused by the error message: I didn’t call this method, why did I get this error message?

How do you avoid the situation where a subclass replaces a superclass method but doesn’t implement it itself? SSJStudent+category. M +load()

// SSJStudent+category.m
#import "SSJStudent+category.h"
#import <objc/runtime.h>

@implementation SSJStudent (category)
+ (void)load{
    /// replace the parent method: person_walk
    // the parent class implements person_walk, and the subclass does not implement person_walk
    Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
    Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew));
    // Add a Method (sel-person_walk, im-person_walknew)
    BOOL isAdded = class_addMethod([self class].method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(originalMethod));
    if(isAdded){
        Person_walk IMP method is not implemented by the subclass.
        After the class_addMethod step, the subclass already has a person_walk method, and IMP points to person_walkNew
        // add Method (sel-person_walknew, im-person_walk)
        /// then the subclass implements a Method that has two IMPs exchanging methods with each other, so the result is like method_exchangeImplementations
        class_replaceMethod([self class].method_getName(swizzlingMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        Person_walk IMP = person_walk IMP = person_walk IMPmethod_exchangeImplementations(originalMethod, swizzlingMethod); }}Copy the code

Operation effect:

To put it simply:

  1. Class_addMethod Add Method (sel-person_walk, im-person_walknew)

  2. Method (sel-person_walknew, IMP – person_walk) is added by calling class_replaceMethod.

  3. Failure – calls method_exchangeImplementations IMP pointing of two methods.

I drew a picture just to make it easier to understand

Description:

  • class_addMethod: only inSELThere is noIMPIt can be added successfully only when pointing to.
  • class_replaceMethod: no matterSELIs there anyIMP implementation, can be added successfully;

The parent class is not implemented, and the child class is not implemented

Partially annotate the parent class implementation

Then run again:

The message that the person_walk method implementation cannot be found indicates that there is a problem with the category. We put a breakpoint on the class_getInstanceMethod line:

Because neither parent nor child class implements person_walk, the originalMethod obtained here is null. We changed the category +load() method to add originalMethod null processing:

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        /// replace the parent method: person_walk
        // the parent class implements person_walk, and the subclass does not implement person_walk
        Method originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
        Method swizzlingMethod = class_getInstanceMethod([self class], @selector(person_walkNew));
        if(! originalMethod) {/// No, add a person_walk method and manually add a temporary IMP implementation
           class_addMethod([self class], @selector(person_walk), method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
            /// originalMethod needs to retrieve one side, otherwise it is still empty
            originalMethod = class_getInstanceMethod([self class], @selector(person_walk));
            method_setImplementation(originalMethod, imp_implementationWithBlock(^(id self,SEL _cmd){
                NSLog(@"Temporary method");
            }));
        }
        // Add a Method (sel-person_walk, im-person_walknew)
        BOOL isAdded = class_addMethod([self class].method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(originalMethod));
        if(isAdded){
            Person_walk IMP method is not implemented by the subclass.
            After the class_addMethod step, the subclass already has a person_walk method, and IMP points to person_walkNew
            // add Method (sel-person_walknew, im-person_walk)
            /// then the subclass implements a Method that has two IMPs exchanging methods with each other, so the result is like method_exchangeImplementations
            class_replaceMethod([self class].method_getName(swizzlingMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else{
            Person_walk IMP = person_walk IMP = person_walk IMPmethod_exchangeImplementations(originalMethod, swizzlingMethod); }}); }Copy the code

Run:

KVC analysis

Before I get into KVC, let me show you how to read and write an open property of the SSJPerson class:

// SSJPerson.h
#import <Foundation/Foundation.h>
@interface SSJPerson : NSObject
@end

// SSJPerson.m
#import "SSJPerson.h"
@interface SSJPerson ()
/ / / nickname
@property (nonatomic , strong) NSString *nickName_private;
@end
@implementation SSJPerson
@end
Copy the code

As shown in the figure, we cannot pass an object for a property that is not exposed. Property names are accessed in the usual way. But we can do it using setValue:forKey:.

  • This throughsetValue:forKey:The assignment operation, which we callKVC.

KVC is a design pattern, but how does it work? Why is it possible to directly manipulate an open property? Existence is truth. With an exploratory mind, we decided to take a look at the underlying implementation of setValue:forKey:.

Enter the official Apple documentation: KVC section

NSObject provides the NSKeyValueCoding protocol. The default implementation uses a well-defined set of rules to map key-based accessor calls to the underlying properties of the object. These protocol methods use a key parameter to search their own object instances for accessors, instance variables, and related methods that follow some naming convention.

Setter

Let’s look at the Setter search pattern:

According to the figure, the Setter search pattern is divided into three steps:

  1. Find set

    : or _set

    and call it if found;

  2. If it is not found, it looks for _

    , _is< key>,

    , or is< key>. If it is found, it sets the variable with the input value.

  3. If still haven’t found, is called setValue: forUndefinedKey: and throw an exception.

Let’s put these three steps into practice:

// SSJPerson.h
@interface SSJPerson : NSObject{
    @public
    NSString *boddy;
    NSString *_boddy;
    NSString *isBoddy;
    NSString *_isBoddy;
}
@end

// SSJPerson.m
// (according to Step 2, find one to set to YES)
@implementation SSJPerson
+ (BOOL)accessInstanceVariablesDirectly{
    return true;
}
@end


// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    / / / set the value
    [personA setValue:@"Football" forKey:@"boddy"];
    /// Print the content, we need to see which value is assigned
    NSLog(@"_<key>---%@",personA->_boddy);
    NSLog(@"_is<Key>---%@",personA ->_isBoddy);
    NSLog(@"<key>---%@",personA ->boddy);
    NSLog(@"is<Key>---%@",personA ->isBoddy);
}
Copy the code

Run:

Annotation _boddy:

Annotation _isBoddy:

  • This verifies the second point in the article: when there are multiple similar variables, they will be searched in sequence_<key>._is<Key>.<key>Or,is<Key>If you find it, you assign it, and then you assign it.
Consider: On the first pointset<Key>:Is there a similar relationship with point 2?

Note the setBoddy method:

Comment _setBoddy method:

SetIsBoddy method:

After 4 prints, we find that when we call setValue:forKey: we look for: set

: > _set

> setIs

. When found, it assigns the value to the variable from the input value


Getter

Now look at the Getter:

The general meaning is as follows:

1Get <Key>, <Key>, is<Key>, or _< Key>5; If not, enter the step2;2CountOf <Key> and objectIn<Key>AtIndex and <Key> AtIndexes:, countOf<Key> must be implemented. If one of countOf<Key> is found, create a proxy object3;3CountOf <Key>, Enumeratorf<Key>, memberOf<Key> :3All methods must exist, otherwise the step is entered4;4When the AccessInstanceVariables method returns YES (the default is YES), search for instance variables named _<key>, _is< key>, <key>, or is< key> in sequence. If found, simply get the value of the instance variable and continue with the step5. Otherwise, proceed with the procedure6.5If the retrieved property value is an object pointer, simply return the result. If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return the value. If the result is a scalar type not supported by NSNumber, convert it to an NSValue object and return it.6If none is found, call valueForUndefinedKey: and throw an exception.Copy the code

For point 1, we carry out practical operation:

// SSJPerson.m

- (NSString *)getBoddy{
    NSLog(@"%s -->Getter",__func__);
    return boddy;
}

- (NSString *)boddy{
    NSLog(@"%s -->Getter",__func__);
    return boddy;
}

- (NSString *)isBoddy{
    NSLog(@"%s -->Getter",__func__);
    return isBoddy;
}

- (NSString *)_boddy{
    NSLog(@"%s -->Getter",__func__);
    return _boddy;
}

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    SSJPerson *personA = [SSJPerson new];
    / / / set the value
    [personA setValue:@"Football" forKey:@"boddy"];
    NSLog(@"Print - % @",[personA valueForKey:@"boddy"]);
}

Copy the code

Run:

Annotation getBoddy:

Annotation boddy:

Annotation isBoddy:

The reason why valueForKey prints only after the _boddy method is because, by default, setters and getters are one-to-one, with setter mode taking precedence over _

and getter mode corresponding to _boddy.

For point 2Let’s do some real operations
CountOf <Key> and objectIn<Key>AtIndex and <Key> AtIndexes:, countOf<Key> must be implemented. If one of the other two objects is found, create a collection proxy objectCopy the code

You can’t use boddyArray here, otherwise the Getter method will take the first step:

Change key to myBoddyArray

Comment out objectInMyBoddyArrayAtIndex: methods:

For point 3Let’s do some real operations
CountOf <Key>, Enumeratorf<Key>, memberOf<Key> :,3All methods must exist, otherwise the step is entered4;Copy the code

For point 4Let’s do some real operations
When sure, the AccessInstanceVariables method returns YES (the default is YES), search sequentially for instance variables named _<key>, _is< key>, <key>, or is< key>. If found, simply get the value of the instance variable and continue with the step5. Otherwise, proceed with the procedure6.Copy the code

annotation_boddy:

Annotation _isBoddy:

Annotation boddy:

Point 6Let’s do some real operations
If none is found, call valueForUndefinedKey: and throw an exceptionCopy the code

An error will be reported if a non-existent key is used for access without processing:

Let’s implement valueForUndefinedKey: and then:

So let’s summarize the Setter and Getter for KVC

Setter:

  1. Set

    : or _set

    ; If no, go to Step 2.

  2. Make sure that AccessInstanceVariables returns YES, then look for _

    , _is< key>,

    , or is< key> in sequence, and set the variable with the input value when found. If no, go to Step 3.

_is< key> = _is< key>

  1. callsetValue:forUndefinedKey:And causeabnormal.

Getter:

  1. Find get

    ,

    , is

    , or _< Key>. If no, go to Step 2.


  2. Search for countOf

    and objectIn

    AtIndex and

    AtIndexes:, where countOf

    must be implemented. If countOf

    is found, create a collection proxy object. If no, go to Step 3.




  3. CountOf

    , enumeratorf

    , memberOf

    :, if all three methods exist, go to Step 4.


  4. When sure, the AccessInstanceVariables method returns YES (the default is YES), search sequentially for instance variables named _

    , _is< key>,

    , or is< key>. If found, simply get the value of the instance variable and continue with Step 5. Otherwise, go to Step 6.

  5. If the retrieved property value is an object pointer, simply return the result. If the value is a scalar type supported by NSNumber, store it in an NSNumber instance and return the value. If the result is a scalar type not supported by NSNumber, convert it to an NSValue object and return it.

  6. If none is found, call valueForUndefinedKey: and throw an exception.

At this point, we are done exploring KVC.

code

Link: pan.baidu.com/s/1Ww — iLGj… Password: B3jF — from Baidu network disk super member V2 share

trailer

We already know how KVC works. In the next article, I will try to customize a KVC. Stay tuned ~