RunTime

作者: 万万万万万万一 | 来源:发表于2023-07-13 09:19 被阅读0次

    OC是一门动态语言

    动态语言是指程序可以在运行时可以改变其结构:添加新的函数、属性,删除已有的函数、属性等结构上的变化,在运行时做类型的检查。

    编译时:源代码被编译成机器可以识别的代码的过程。
    运行时:用户可以运行编译过的程序,程序运行的过程。

    OC的动态性由runtime支持的。
    runtime是一个C语言的库,提供API创建类、添加方法、删除方法、交换方法等。

    isa指针(is a what?)

    objc_object *id;
    
    struct objc_object {
      Class isa;
    �}
    
    objc_class *Class;
    
    struct objc_class {
      super_class,
      name,
      version,
      info,
      instance_size,
      ivars,
      methodLists,
      cache,
      protocols
    }
    

    isa指向流程

    实例对象isa指向类对象
    类对象isa指向元类
    类对象superClass指向父类指向的类对象
    所有元类isa指向NSObject对象的元类(根元类)
    根元类isa指向自己
    根元类的superClass指向NSObject的类对象
    元类的superClass指向对应父类的元类

    Runtime 消息发送机制:示例图

    对象方法调用

    IMG_0111.PNG

    类方法调用

    IMG_0112.PNG

    1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, ...),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数。

    2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步;

    3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步;

    4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;

    Person 类:
    @interface Person : NSObject
    -(void)sayHello;
    -(void)sayString:(NSString*)str;
    @end
    @implementation Person
    
    - (instancetype)init{
       if (self = [super init]) {
           NSLog(@"%s",__func__);
       }
        [self copyContent];
       return self;
    }
    
    -(void)sayHello{
        NSLog(@"Hello");
    }
    -(void)sayString:(NSString*)str{
        NSLog(@"sayString---%@",str);
    }
    @end
    
    //Son类
    @interface Son : Person
    
    @end
    @implementation Son
    stancetype)init{
       if (self = [super init]) {
           NSLog(@"%s",__func__);
       }
       return self;
    }
    
    @end
    
    //使用
    Son *son = [[Son alloc] init];
        [son sayString:@"Hello"];    
    objc_msgSend(son, sel_registerName("sayHello"));
        objc_msgSend(son, @selector(sayHello));  
     void (*glt_msgsend)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))objc_msgSend;  
     glt_msgsend(son, @selector(sayString:), @"123");
    
    
    //打印:
    sayString---Hello
    2022-05-13 12:19:48.643281+0800 BasicKnowledge[49582:1277604] Hello
    2022-05-13 12:19:48.643383+0800 BasicKnowledge[49582:1277604] Hello
    2022-05-13 12:19:48.643497+0800 BasicKnowledge[49582:1277604] sayString---123
    
    //Son 没有这个方法时,向它的父类查找
    

    Runtime消息转发机制

    IMG_0113.PNG

    1.消息动态解析

    + (BOOL)resolveInstanceMethod:(SEL)selector;
    + (BOOL)resolveIClassMethod:(SEL)selector;
    
    IMG_0114.PNG

    2消息接受者重定向

    - (id)forwardingTargetForSelector:(SEL)selector;
    
    IMG_0115.PNG

    3消息重定向

    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    
    IMG_0116.PNG IMG_0117.PNG

    4最后消息未能处理的时候,还会调用

    - (void)doesNotRecognizeSelector:(SEL)aSelector抛出异常
    

    1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步

    2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步;

    3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步

    4)报错 unrecognized selector sent to instance。

    怎么在项目里全局解决"unrecognized selector sent to instance"这类crash?

    可以在消息转发(forwardingTargetForSelector)里面处理,防止崩溃,具体处理办法是:

    + (void)defenderSwizzlingClassMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
        swizzlingClassMethod(targetClass, originalSelector, swizzledSelector);
    }
    
    + (void)defenderSwizzlingInstanceMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector withClass:(Class)targetClass {
        swizzlingInstanceMethod(targetClass, originalSelector, swizzledSelector);
    }
    
    // 交换两个类方法的实现
    void swizzlingClassMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
    
        Method originalMethod = class_getClassMethod(class, originalSelector);
        Method swizzledMethod = class_getClassMethod(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);
        }
    }
    
    // 交换两个对象方法的实现
    void swizzlingInstanceMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
        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);
        }
    }
    
    //建立 NSObject catgory
    + (void)load {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            // 拦截 `+forwardingTargetForSelector:` 方法,替换自定义实现
            [NSObject defenderSwizzlingClassMethod:@selector(forwardingTargetForSelector:)
                                           withMethod:@selector(defend_forwardingTargetForSelector:)
                                            withClass:[NSObject class]];
            
            // 拦截 `-forwardingTargetForSelector:` 方法,替换自定义实现
            [NSObject defenderSwizzlingInstanceMethod:@selector(forwardingTargetForSelector:)
                                              withMethod:@selector(defend_forwardingTargetForSelector:)
                                               withClass:[NSObject class]];
            
        });
    }
    
    // 自定义实现 `+defend_forwardingTargetForSelector:` 方法
    + (id)defend_forwardingTargetForSelector:(SEL)aSelector {
        SEL forwarding_sel = @selector(forwardingTargetForSelector:);
        
        // 获取 NSObject 的消息转发方法
        Method root_forwarding_method = class_getClassMethod([NSObject class], forwarding_sel);
        // 获取 当前类 的消息转发方法
        Method current_forwarding_method = class_getClassMethod([self class], forwarding_sel);
        
        // 判断当前类本身是否实现第二步:消息接受者重定向
        BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
        
        // 如果没有实现第二步:消息接受者重定向
        if (!realize) {
            // 判断有没有实现第三步:消息重定向
            SEL methodSignature_sel = @selector(methodSignatureForSelector:);
            Method root_methodSignature_method = class_getClassMethod([NSObject class], methodSignature_sel);
            
            Method current_methodSignature_method = class_getClassMethod([self class], methodSignature_sel);
            realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
            
            // 如果没有实现第三步:消息重定向
            if (!realize) {
                // 创建一个新类
                NSString *errClassName = NSStringFromClass([self class]);
                NSString *errSel = NSStringFromSelector(aSelector);
            
                NSLog(@"*** Crash Message: +[%@ %@]: unrecognized selector sent to class %p ***",errClassName, errSel, self);
                
                
                NSString *className = @"CrachClass";
                Class cls = NSClassFromString(className);
                
                // 如果类不存在 动态创建一个类
                if (!cls) {
                    Class superClsss = [NSObject class];
                    cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                    // 注册类
                    objc_registerClassPair(cls);
                }
                // 如果类没有对应的方法,则动态添加一个
                if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                    class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
                }
                // 把消息转发到当前动态生成类的实例对象上
                return [[cls alloc] init];
            }
        }
        return [self defend_forwardingTargetForSelector:aSelector];
    }
    
    // 自定义实现 `-defend_forwardingTargetForSelector:` 方法
    - (id)defend_forwardingTargetForSelector:(SEL)aSelector {
        
        SEL forwarding_sel = @selector(forwardingTargetForSelector:);
        
        // 获取 NSObject 的消息转发方法
        Method root_forwarding_method = class_getInstanceMethod([NSObject class], forwarding_sel);
        // 获取 当前类 的消息转发方法
        Method current_forwarding_method = class_getInstanceMethod([self class], forwarding_sel);
        
        // 判断当前类本身是否实现第二步:消息接受者重定向
        BOOL realize = method_getImplementation(current_forwarding_method) != method_getImplementation(root_forwarding_method);
        
        // 如果没有实现第二步:消息接受者重定向
        if (!realize) {
            // 判断有没有实现第三步:消息重定向
            SEL methodSignature_sel = @selector(methodSignatureForSelector:);
            Method root_methodSignature_method = class_getInstanceMethod([NSObject class], methodSignature_sel);
            
            Method current_methodSignature_method = class_getInstanceMethod([self class], methodSignature_sel);
            realize = method_getImplementation(current_methodSignature_method) != method_getImplementation(root_methodSignature_method);
            
            // 如果没有实现第三步:消息重定向
            if (!realize) {
                // 创建一个新类
                NSString *errClassName = NSStringFromClass([self class]);
                NSString *errSel = NSStringFromSelector(aSelector);
        
                NSLog(@"*** Crash Message: -[%@ %@]: unrecognized selector sent to instance %p ***",errClassName, errSel, self);
                
                NSString *className = @"CrachClass";
                Class cls = NSClassFromString(className);
                
                // 如果类不存在 动态创建一个类
                if (!cls) {
                    Class superClsss = [NSObject class];
                    cls = objc_allocateClassPair(superClsss, className.UTF8String, 0);
                    // 注册类
                    objc_registerClassPair(cls);
                }
                // 如果类没有对应的方法,则动态添加一个
                if (!class_getInstanceMethod(NSClassFromString(className), aSelector)) {
                    class_addMethod(cls, aSelector, (IMP)Crash, "@@:@");
                }
                // 把消息转发到当前动态生成类的实例对象上
                return [[cls alloc] init];
            }
        }
        return [self defend_forwardingTargetForSelector:aSelector];
    }
    
    // 动态添加的方法实现
    static int Crash(id slf, SEL selector) {
        return 0;
    }
    

    Category

    objc_category *Category;
    struct objc_category {
      category_name,
      class_name,
      instance_methods,
      class_methods,
      protocols
    }
    

    在程序运行时
    实例方法整合到主类中
    类方法整合到元类中
    协议同时整合到主类和元类中

    在类的+laod方法中可以调用category里声明的方法吗?

    可以,因为附加Category到类的工作先于+load方法的执行

    类和category的+load方法调用的顺序

    先类,后category。而各个category的+load方法按照编译的顺序执行

    关联对象存在哪?

    所有的关联对象都由AssociationsManager管理,AssociationsManager里面由一个静态AssociationsHashMap来存储所有的关联对象的。

    在category里可以添加属性吗?

    category中只能添加方法,不能添加实例变量。类的内存大小是在编译时确定的,而category是在运行时被添加的,此时再添加实例变量会破坏内存结构。
    在category中添加属性,通过关联对象实现setter、getter方法。

    类和category的同名方法调用的顺序

    category并不是完全替换掉主类的同名方法,只是类的方法列表中会出现名字一样的方法且category的方法会排在前面,多个category中的同名方法按编译的顺序排。runtime查找方法按照顺序,一旦找到就return。
    遍历类的方法列表,列表里最后一个同名的方法,即是原方法。

    category

    运行时决议
    有单独的.h和.m文件
    可以为系统类添加分类
    看不到源码的类可以添加分类
    只能添加方法,不能添加实例变量

    extension

    编译时决议
    以声明的方式存在,寄生于主类.m文件
    不可以为系统类添加extension
    没有.m源码的类不可以extension
    可以添加方法,可添加实例变量,默认为@private

    KVC

    是一种可以通过key来访问类属性的机制。而不是通过调用Setter、Getter方法访问。

    可以在运行时动态访问和修改对象的属性

    // 赋值
    [person1 setValue:@"jack" forKey:@"name"];
    
    // 取值
    NSString *name = [person1 valueForKey:@"name"];
    
    forKeyPath 是对更“深层”的对象进行访问。如数组的某个元素,对象的某个属性。
    [myModel setValue:@"beijing" forKeyPath:@"address.city"];
    
    // 返回所有对象的name属性值
    NSArray *names = [array valueForKeyPath:@"name"];
    

    (区别)NSMutableDictionary中的setObject:forKey:与 setVale:forKey:方法有什么区别?问?

    答:1.setObject:forKey:中的value是不能够为nil的 ,不然会报错
    2.setVale:forKey:中的value是可以为nil的 ,当value为nil时,会自动调用removeObject:forKey方法。
    3..setVale:forKey: 中的key参数类型只能是NSString类型的,setObject:forKey:中的key可以任何类型。

    setValue:ForKey:(kvc 原理)

    按照setKey、_setKey的顺序查找方法,找到了就传递参数,调用方法
    如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用setValue:forUndefinedKey抛出NSUnknownKeyException异常
    如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接赋值
    如果没找到,则调用setValue:forUndefinedKey抛出异常

    valueForKey:

    按照getKey、_getKey的顺序查找方法,找到了就直接调用方法
    如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用value:forUndefinedKey抛出NSUnknownKeyException异常
    如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接取值
    如果没找到,则调用value:forUndefinedKey抛出异常

    KVO

    KVO:key value observing,键值监听,可以监听对象某个属性值的变化

    给对象添加监听
    通过runtime动态创建一个子类,修改对象的isa指向子类
    子类重写set方法,内部执行顺序

    willChangeValueForKey
    [super setKey]
    didChangeValueForKey
    在didChangeValueForKey中调用KVO的回调方法:observeValueForKeyPath:ofObject:change:context:
    

    归档解档(NSCoding)

    1.归档是指用某种格式来保存一个或多个对象,以便以后还原这些对象的过程。归档是将数据持久化的一种方式(所谓数据持久化,就是指在IOS开发过程中,将数据保存到本地,能够让程序的运行更加流畅)。

    2.归档与解档是iOS中一种序列化与反序列化的方式。想要归档的数据对象,需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法。

    3.归档就是将临时数据保存成本地文件。

    4.归档的缺点:归档的形式来保存数据,只能一次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。

    通过class_copyIvarList获得对象的属性列表
    通过ivar_getName(ivar)获取到属性的C字符串名称

    NSString *key = [NSString stringWithUTF8String:name];转成对应的OC名称
    利用KVC进行归档 [corder encodeObject: [self valueForKey:key] forKey: key];

    解档 id value = [coder decodeObjectForKey];
    利用KVC进行赋值[self setValue:value ForKey:key];

    XML归档

    1.局限:数据类型只支持 NSString、NSDictionary、NSArayy、NSData、NSNumber(如果你想的话,可以将基本数据类型转换为NSNumber再进行归档)。

    2.比较方便,设置好归档路径,一句话归档,一句话解档。

    3.归档文件格式:一般保存.plist文件。

    /**** NSString和NSMutableString XML归解档 ****/
    NSString *str = @"hello world";
    NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"hello.txt"];
     // atomically:这个参数意思是如果为YES,则保证文件的写入原子性。就是说会先创建一个临时文件,直到文件内容写入成功再导入到目标文件里.如果为NO,则直接写入目标文件里.
    [str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    // 这里会覆盖原来的内容
    [@"hello world 2" writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    NSString *str2 = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    /**** NSData和NSMutableData XML归解档 ****/
    // 任何对象都可以转化为NSData
    NSData *data = [@"hello world" dataUsingEncoding:NSUTF8StringEncoding];
    NSString *path2 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"data.txt"];
    //归档
    [data writeToFile:path2 atomically:YES];
    //解档
    [NSData dataWithContentsOfFile:path2];
    /**** NSArray及NSMutableArray XML归解档 ****/
    NSArray *array = @[@"test",@"test2"];
    NSString *path3 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"array.plist"];
    // 归档
    [array writeToFile:path3 atomically:YES];
    // 解档
    NSArray *array = [NSArray arrayWithContentsOfFile:path3];
    /**** NSArray XML归解档 ****/
    NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"one",@"1",@"two",@"2", nil];
    NSString *path4 = [[NSHomeDirectory() stringByAppendingPathComponent:@"Desktop"]stringByAppendingPathComponent:@"dic.plist"];
    // 归档
    [dic writeToFile:path4 atomically:YES];
    // 解档
    NSDictionary *ddic = [NSDictionary dictionaryWithContentsOfFile:path3];
    

    相关文章

      网友评论

          本文标题:RunTime

          本文链接:https://www.haomeiwen.com/subject/kbtpudtx.html