美文网首页
iOS runtime原理以及KVO实现

iOS runtime原理以及KVO实现

作者: 开心的小赵 | 来源:发表于2019-04-01 15:58 被阅读0次

    前言

    做开发做了好几年,前几年写iOS代码,去年写Web端的代码。然后发现学到的东西有限,了解的东西确实很多但是都不够深入。今年重新做iOS开发,痛定思痛,来研究或者说来熟悉下iOS中的各项技术。后面的博客可能会参照各个大佬的博客加上我自己的一些理解。

    技术更新

    后面更新的博客主要为iOS开发内容,当然有空的时候我会去研究下前端和后端以及数据库、大数据、AI相关的东西。
    废话不多说,先看下这一篇博客要说的东西,可能内容有点多。


    runtime

    runtime版本

    OC2.0 之后的版本为Modern版本:运行与64位系统中
    OC2.0以前是 legacy版本:老的32位程序
    runtime 是用c和汇编写的

    原理

    • 消息传递机制

    [obj method]被转化为objc_msgSend(obj,method)
    1.通过obj的isa指针找到它的class
    2.在class的methodList里面找到method
    3.如果没找到,就到它的superclass里面去找
    4.一旦找到这个函数,name就去执行这个函数的IMP

    • objc_cache

    每次去找methodlist然后找一遍会出现效率低的问题
    objc_class的objc_cache来实现对常用方法的缓存。
    意思就是将常用的方法缓存下来,运行时,先去找cache中是否有方法,然后再重复上述步骤。
    那么这个cache是怎么实现的呢,大致就是将一个key和函数的IMP存起来,然后在执行的时候找到key去执行IMP,然后算法更新这个方法是不是常用的。

    • 结构体

    1.objc_object
    isa就是这个结构体的指针

    objc_object
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    2.objc_class

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;
    

    结构体说明:
    isa -》class的isa指针
    name - 》class 的名字
    version - 》class 的版本
    info - 》包含的信息
    instance_size-》 实例大小
    ivars-》属性列表
    methodlists-》方法列表
    cache -》方法cache
    prtocols-》协议列表
    3.obj_mehod

    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    }
    

    结构体说明:
    method_name-》方法的名字
    method_types-》方法的类型
    method_imp-》方法的IMP

    runtime应用

    • 给分类关联属性
    • 给类动态添加方法
    • 方法交换
    • 获取类的详细列表
    • 其他

    1.给分类关联属性
    给分类添加属性主要用到下面三个方法

    • 根据key设置关联对象的value
      objc_setAssociatedObject(id object, void * key, id value, <objc_AssociationPolicy policy);
    • 根据key取出关联对象的value
      objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
    • 移除所有关联对象
      objc_removeAssociatedObjects(id _Nonnull object);

    代码
    给ViewController添加一个分类,命名为Extension
    创建的分类如下

    image0.png

    分类的.h代码:

    #import <UIKit/UIKit.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface UIViewController (Extension)
    
    @property(nonatomic, copy)NSString *contentView;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    分类的.m代码

    #import "UIViewController+Extension.h"
    #import <objc/runtime.h>
    
    static const void *key = &key;
    
    @implementation UIViewController (Extension)
    
    - (void)setContentView:(UIView *)contentView{
        
        objc_setAssociatedObject(self, &key, contentView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    
    - (UIView *)contentView{
        return objc_getAssociatedObject(self, &key);
    }
    @end
    

    在UIViewController中调用

    self.contentView = @"hello";
    NSLog(@"%@",self.contentView);
    

    代码以及相关说明
    这个key值一定要定义全局静态变量 一个key对应一个value
    声明:static void *key = "key";
    用法1:&key作为参数
    用法2:写成@selector(value)作为参数,其中value就是你属性的名字,这样写也获取了一个静态无返回值的地址。

    2.给类动态添加方法

    1.直接给当前类动态添加一个方法
    传过来的方法是不是想要的方法,如果是就添加
    +(BOOL)resolveInstanceMethod:(SEL)sel;
    给类添加一个方法,参数说明 cls:给那个类添加 name:方法名字 IMP:指针(可以根据IMP的定义定义一个IMP的指针)types:”v@:“(编码表示(v)无返回值 (@)对象 (:)选择器)
    class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
    const char * _Nullable types);
    添加成功 return YES;

    代码
    在ViewController.m中

    + (BOOL)resolveInstanceMethod:(SEL)sel{
        if (sel == @selector(work)) {
            class_addMethod([self class], sel,(IMP)work,"v@:");
        }
        return NO;
    }
    
    void work(id objct, SEL sel){
        NSLog(@"i am working");
    }
    

    调用

    [self performSelector:@selector(work)];
    

    2.转给备用接收者
    第一种方案:
    -(id)forwardingTargetForSelector:(SEL)aSelector;
    声明一个对象,return这个对象,让另外的对象去执行这个方法
    代码:

    + (BOOL)resolveInstanceMethod:(SEL)sel{
    //    if (sel == @selector(work)) {
    //        class_addMethod([self class], sel,(IMP)work,"v@:");
    //    }
        return NO;
    }
    void work(id objct, SEL sel){
        NSLog(@"i am working");
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(work)) {
            Person *person = [Person new];
            return person;
        }
        return nil;
    }
    

    调用

    [self performSelector:@selector(work)];
    

    新建Person类,在Person类中添加一个work方法即可

    第二种方案:
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    通过签名发送给anInvocation
    -(void)forwardInvocation:(NSInvocation *)anInvocation;
    [anInvocation invokeWithTarget:target];
    在上面的方法中如果 target类中有anInvocation.selector,name就交给target类去执行这个方法,否则就执行[self doesNotRecognizeSelector:sel];

    上面也称之为方法重定向

    代码

    + (BOOL)resolveInstanceMethod:(SEL)sel{
    //    if (sel == @selector(work)) {
    //        class_addMethod([self class], sel,(IMP)work,"v@:");
    //    }
        return NO;
    }
    void work(id objct, SEL sel){
        NSLog(@"i am working");
    }
    
    - (id)forwardingTargetForSelector:(SEL)aSelector{
    //    if (aSelector == @selector(work)) {
    //        Person *person = [Person new];
    //        return person;
    //    }
        return nil;
    }
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if (aSelector == @selector(work)) {
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [NSMethodSignature methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        SEL sel = anInvocation.selector;
        Person *p = [Person new];
        if ([p respondsToSelector:sel]) {
            [anInvocation invokeWithTarget:p];
        }else{
            [self doesNotRecognizeSelector:sel];
        }
    }
    

    调用

    [self performSelector:@selector(work)];
    

    3.方法交换
    1.直接方法交换
    获取类的方法 返回值为Method
    class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
    交换方法
    method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

    2.截取系统方法交换
    在+(void)load{}中截取系统的方法并且交换 例如不想用系统的某个方法 需要自己实现,那么可以分类然后在分类中自定义一个方法 并且替换系统的方法。单例执行一次。
    class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
    method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

    代码

    - (void)exchageMethod{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method m1 = class_getInstanceMethod([self class], @selector(printA));
            Method m2 = class_getInstanceMethod([self class], @selector(printB));
            method_exchangeImplementations(m1, m2);
        });
    }
    
    - (void)printA{
        NSLog(@"A");
    }
    - (void)printB{
        NSLog(@"B");
    }
    

    调用

    [self exchageMethod];
    [self printA];
    

    结果会打印“B”;

    4.获取类各种详细列表

    • 获取属性名字
      代码:
    - (void)obtainPropertyName{
       unsigned int count = 0;
        objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
        for (NSInteger i = 0; i<count; i++) {
            NSString *name = @(property_getName(propertyList[i]));
            NSString *attribute = @(property_getAttributes(propertyList[i]));
            NSLog(@"%@ %@",name,attribute);
        }
    }
    
    • 获取成员变量名字
    - (void)obtainIvars{
        unsigned int outCount = 0;
        Ivar *ivarList = class_copyIvarList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
            NSLog(@"%@",@(ivar_getName(ivarList[i])));
        }  
    }
    
    • 获取方法名字
    - (void)obtainMthods{
        unsigned int outCount = 0;
        Method *methods = class_copyMethodList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
            NSLog(@"%@", NSStringFromSelector(method_getName(methods[i])));
        }
    }
    
    • 获取遵守的协议名字
    - (void)obtainProtocols{
        unsigned int outCount = 0;
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &outCount);
        for (NSInteger i = 0; i < outCount; i++) {
            Protocol *pro = protocolList[i];
            NSLog(@"%@", @(protocol_getName(pro)));
        }
    }
    

    当然你如果要获取协议名字,那么你首先应该在这个类中遵守某个协议。

    5.其他

    • 字典转模型
      代码(这个代码只是自己简单的实现,具体的字典转模型还需要考虑很多,以后有机会单独再写一篇博客)
    + (instancetype)modelWithDic:(NSDictionary *)dictionary{
        unsigned int outCount = 0;
        Ivar *ivars =  class_copyIvarList([self class], &outCount);
        id p = [[self alloc]init];
        for (int i = 0 ; i < outCount ; i ++ ) {
            NSString *name = @(ivar_getName(ivars[i]));
            if ([dictionary objectForKey:[name substringFromIndex:1]]) {
                [p setValue:[dictionary objectForKey:[name substringFromIndex:1]] forKey:[name substringFromIndex:1]];
            }
        }
        return p;
    }
    

    调用:

    NSDictionary *dic = @{@"name":@"zhaoqian",@"sex":@"man",@"age":@"15"};
    Person * nep = [Person modelWithDic:dic];
    NSLog(@"%@\n%@\n%ld\n",nep.name,nep.sex,nep.age);
    
    • 同一方法高频率调
      代码:
    void (*setter)(id, SEL, BOOL);
    int i;
    setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
    for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);
    
    • 编码解码(只要重写这两个方法就行,当然也必须遵守NSCoding协议)
      代码:
    - (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
        unsigned int outCout = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCout);
        for (int i = 0 ; i < outCout; i++) {
            NSString *name = @(ivar_getName(ivars[i]));
            [aCoder encodeObject:[self valueForKey:name] forKey:name];
        }
        free(ivars);
    }
    
    - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
        self = [super init];
        if (self) {
            unsigned int outCout = 0;
            Ivar *ivars = class_copyIvarList([self class], &outCout);
            for (int i = 0 ; i < outCout; i++) {
                NSString *name = @(ivar_getName(ivars[i]));
                [aDecoder decodeObjectForKey:name];
            }
            free(ivars);
        }
        return self;
    }
    

    KVO

    KVO 一对一
    NSNotificationCenter 一对多
    KVO可以监听单个属性的变化,也可以监听集合对象的变化

    KVO原理

    实际上是创建了一个 NSKVONotifying_XXX的子类,然后用runtime的isa-swizzling技术实现。

    KVO实现

    1.简单理解的kvo
    kvo意思就是当属性的值变化的时候,监听这个变化的值,并且得到监听结果。那么可以在某个类中重写该属性的setter方法,然后回调回来接收,但是我写的不能动态的实现监听 ,要在每个属性的setter方法中去实现回调,实现了简单的kvo。
    代码
    Person.h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    typedef void(^callBack)(id);
    
    @interface Person : NSObject
    
    @property (nonatomic, copy)NSString *name;
    @property (nonatomic, assign)NSInteger age;
    @property (nonatomic, copy)callBack block;
    
    - (void)addObserverForkeyPath:(NSString *)keyPath;
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    Person.m

    #import "Person.h"
    #import <objc/runtime.h>
    @interface Person(){
        NSString *_name;
        NSInteger _age;
    }
    @property (nonatomic, strong)NSString *keyPath;
    @end
    
    @implementation Person
    
    
    - (void)addObserverForkeyPath:(NSString *)keyPath{
        if (!self.keyPath) {
            return;
        }
        self.keyPath = keyPath;
    }
    - (void)setName:(NSString *)name{
        if (_name!=name) {
             _name = name;
            if (self.block) {
                self.block(name);
            }
        }
    }
    
    - (void)setAge:(NSInteger)age{
        if (_age!=age) {
            _age = age;
        }
        if (self.block) {
            self.block(@(age));
        }
    }
    

    调用:

    Person *p = [Person new];
        [p addObserverForkeyPath:@"name"];
        p.block = ^(id info) {
            //TODO
     };
    

    2.利用runtime的方法实现KVO
    我将代码说明全部注释在代码中了,就直接贴代码了
    创建一个NSObect的分类 分类中重新添加代码
    NSObject+Extension.h

    #import <Foundation/Foundation.h>
    
    
    NS_ASSUME_NONNULL_BEGIN
    
    typedef void(^ValueChangeBlock)(id obeserverObject, NSString *key, id oldValue, id newValue);
    
    @interface NSObject (Extension)
    
    // 添加kvo并回调
    -(void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack;
    
    // 移除kvo
    - (void)removeObserver:(NSObject *)observer forKey:(NSString *)key;
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    NSObject+Extension.m

    #import "NSObject+Extension.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    static char const * kObservers= "ZHAOQIAN";
    
    // ObserverInfo Model
    @interface ObserverInfo : NSObject
    
    // 监听的对象
    @property (nonatomic, copy) NSString *observerName;
    // 监听的健
    @property (nonatomic, copy) NSString *key;
    
    // 回调
    @property (nonatomic, copy) ValueChangeBlock block;
    
    @end
    
    @implementation ObserverInfo
    
    // 实例方法 
    - (instancetype)initWithObserver:(NSString *)observerName key:(NSString *)key block:(ValueChangeBlock)block
    {
        self = [super init];
        if (self) {
            _observerName = observerName;
            _key = key;
            _block = block;
        }
        return self;
    }
    
    @end
    
    @implementation NSObject (Extension)
    
    
    // 添加观察者
    - (void)addObserver:(NSObject *)observer forKey:(NSString *)key options:(NSKeyValueObservingOptions)options block:(ValueChangeBlock)callBack{
        // 利用key 返回其setter方法的方法名,例如变为“setNmae”这样类型的
        NSString *str = private_setterForKey(key);
        // 根据方法名字获取方法
        Method setterMethod = class_getInstanceMethod([self class], NSSelectorFromString(str));
        // 获取父类的类名
        NSString *oldClassName = NSStringFromClass([self class]);
        // 创建子类的类名
        NSString *kvoClassName = [@"Notify_" stringByAppendingString:oldClassName];
        
        // 创建子类
        Class kvoClass;
        kvoClass = objc_lookUpClass(kvoClassName.UTF8String);
        if (!kvoClass) {
            kvoClass = objc_allocateClassPair([self class], kvoClassName.UTF8String, 0);
            objc_registerClassPair(kvoClass);
        }
        
        // 在子类中重新实现setter方法
        if (setterMethod) {
            class_addMethod(kvoClass, NSSelectorFromString(str), (IMP)setterIMP, "v@:@");
        }else{
            // 如果没有setter方法,那么方法交换下
            Method method1 = class_getInstanceMethod(self.class, @selector(setValue:forKey:));
            Method method2 = class_getInstanceMethod(self.class, @selector(swizz_setValue:forKey:));
            method_exchangeImplementations(method1, method2);
        }
        
        // 将self设置为它的子类
        object_setClass(self, kvoClass);
        
        // 创建模型
        ObserverInfo *info = [[ObserverInfo alloc]initWithObserver:observer.description key:key block:callBack];
        // 动态获取观察者属性
        NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
        if (!observers) {
            observers = [NSMutableArray array];
            // 添加观察者属性
            objc_setAssociatedObject(self, kObservers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        [observers addObject:info];
    }
    
    // 移除观察者
    - (void)removeObserver:(NSObject *)observer forKey:(NSString *)key{
        NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
        ObserverInfo *info;
        for (ObserverInfo *temp in observers) {
            if ([temp.observerName isEqualToString:observer.description] && [temp.key isEqualToString:key]) {
                info = temp;
                break;
            }
        }
        if (info) {
            [observers removeObject:info];
        }else{
            @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%@ does not register observer for %@",observer.description, key] userInfo:nil];
        }
        
    }
    
    // 回调
    - (void)swizz_setValue:(id)value forKey:(NSString *)key{
        id oldValue = [self valueForKey:key];
        [self swizz_setValue:value forKey:key];
        NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
        for (ObserverInfo *temp  in observers) {
            if ([temp.key isEqualToString:key]) {
                temp.block(self,key,oldValue,value);
            }
        }
    }
    
    // 指针交换的回调
    void setterIMP(id self,SEL _cmd,id newValue){
        NSString *setterName = NSStringFromSelector(_cmd);
        NSString *temp = private_upperTolower([setterName substringFromIndex:@"set".length], 0);//去除set并将大写改成小写
        NSString *key = [temp substringToIndex:temp.length-1];//去除冒号
        id oldValue = [self valueForKey:key];
        // 父类结构体
        struct objc_super superClazz = {
            .receiver = self,
            .super_class = class_getSuperclass(object_getClass(self))
        };
        // 方法发给父类
        ((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
        // 回调
        NSMutableArray *observers = objc_getAssociatedObject(self, kObservers);
        for (ObserverInfo *temp in observers) {
            if ([temp.key isEqualToString:key]) {
                temp.block(self, key, oldValue, newValue);
            }
        }
    }
    
    // 内联函数
    
    // 返回方法名字,例如“setName”这样类型的
    static inline NSString * private_setterForKey(NSString *key){
        key = private_lowerToUpper(key, 0);
        return [NSString stringWithFormat:@"set%@:",key];
    }
    
    // 将某个位置的字母小写转大写
    static inline NSString * private_lowerToUpper(NSString *str,NSInteger location){
        NSRange range = NSMakeRange(location, 1);
        NSString *lowerLetter = [str substringWithRange:range];
        return [str stringByReplacingCharactersInRange:range withString:lowerLetter.uppercaseString];
    }
    // 将某个位置的字母大写转小写
    static inline NSString * private_upperTolower(NSString *str,NSInteger location){
        NSRange range = NSMakeRange(location, 1);
        NSString *lowerLetter = [str substringWithRange:range];
        return [str stringByReplacingCharactersInRange:range withString:lowerLetter.lowercaseString];
    }
    
    @end
    

    写在结尾

    很多内容还是不够深入,后面如果有更深入的研究我会更新在这个博客中。
    如果内容能帮到和我一样的同学,随手点个小星星也是不错的。

    相关文章

      网友评论

          本文标题:iOS runtime原理以及KVO实现

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