美文网首页
iOS - Runtime相关

iOS - Runtime相关

作者: Mn_Su | 来源:发表于2017-02-17 11:01 被阅读0次

    一.什么是 runtime ?

        rumtime是运行时库,基于c语言的api接口,
        作用是动态的创建一个类 动态的添加属性和方法 遍历属性和方法名 动态修改属性和方法等等 
        1.能动态产生一个类,一个成员变量,一个方法
        2.能动态修改一个类,一个成员变量,一个方法
        3.能动态删除一个类,一个成员变量,一个方法
    
        //类在runtime中的表示
        struct objc_class {
            Class isa;//指针,顾名思义,表示是一个什么,
            //实例的isa指向类对象,类对象的isa指向元类
        #if !__OBJC2__
            Class super_class;  //指向父类
            const char *name;  //类名
            long version;
            long info;
            long instance_size
            struct objc_ivar_list *ivars //成员变量列表
            struct objc_method_list **methodLists; //方法列表
            struct objc_cache *cache;//缓存
            //一种优化,调用过的方法存入缓存列表,下次调用先找缓存
            struct objc_protocol_list *protocols //协议列表
            #endif
        } OBJC2_UNAVAILABLE;
        /* Use `Class` instead of `struct objc_class *` */
    

    二.runtime的头文件

        #import <objc/runtime.h> 包含对类、成员变量、属性、方法的操作
         #import <objc> 包含消息机制
    

    三.消息发送步骤

      1)受限检测这个 selector 是不是要忽略。比如Mac OS X 开发,有了垃圾回收就不理会retain , release这些函数。
      2)检测这个 selector 的 target 是不是 nil , Objt 允许我们对一个 nil 对象执行任何方法不会 Crash ,因为运行时会被忽略掉。
      3)如果上面两步都通过了,那么就开始查找这个类的实现 IMP , 先从 cache 哩查找,如果找到了就运行对应的函数去执行相应的代码。
      4)如果 cache 找不到就找类的方法列表中是否有对应的方法。
      5)如果类的方法列表找不到就到父类的方法列表中查找,一直找到 NSObjiect 类为止。
      6)如果还找不到,进入动态方法解析。
    

    四.常用方法

        //动态拦截调用
        + (BOOL)resolveClassMethod:(SEL)sel;
        + (BOOL)resolveInstanceMethod:(SEL)sel;
    
        //遍历相关
        class_copyMethodList(返回一个指向类的方法数组的指针) 
        class_copyIvarList (返回一个指向类的成员变量数组的指针)
        class_copyPropertyList(返回一个指向类的属性数组的指针)
    
        //修改属性
            objc_getAssociatedObject 
            objc_setAssocaitedObject
    
        //交换
            class_getInstanceMethod
            method_exchangeImplementation(systemMethod, swizzMethod)
    

    五.应用

        1)动态的遍历一个类的所有成员变量,用于字典转模型,归档解档操作
    
                - (void)viewDidLoad {    
                      [super viewDidLoad];    
                      /** 利用runtime遍历一个类的全部成员变量     
                          1.导入头文件<objc/runtime.h>     */    
                      unsigned int count = 0;   
                     /** Ivar:表示成员变量类型 */    
                      Ivar *ivars = class_copyIvarList([BDPerson class], &count);//获得一个指向该类成员变量的指针   
                     for (int i =0; i < count; i ++) {        
                    //获得Ivar      
                      Ivar ivar = ivars[i];        //根据ivar获得其成员变量的名称--->C语言的字符串      
                      const char *name = ivar_getName(ivar);       
                       NSString *key = [NSString stringWithUTF8String:name];      
                      NSLog(@"%d----%@",i,key);
                    }
                }
    
        2)可以利用遍历类的属性,来快速的进行归档操作;将从网络上下载的json数据进行字典转模型。
    
                注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法
                - (void)encodeWithCoder:(NSCoder *)encoder{    
                    //归档存储自定义对象    
                    unsigned int count = 0;  
                    //获得指向该类所有属性的指针   
                    objc_property_t *properties =     class_copyPropertyList([BDPerson class], &count);   
                    for (int i =0; i < count; i ++) {        
                    //获得        
                    objc_property_t property = properties[i];        //根据objc_property_t获得其属性的名称--->C语言的字符串       
                   const char *name = property_getName(property);   
                   NSString *key = [NSString   stringWithUTF8String:name];       
                   //      编码每个属性,利用kVC取出每个属性对应的数值            
                   [encoder encodeObject:[self valueForKeyPath:key] forKey:key]; 
                 }}
                
                - (instancetype)initWithCoder:(NSCoder *)decoder{    
                      //归档存储自定义对象    
                        unsigned int count = 0;   
                     //获得指向该类所有属性的指针   
                       objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);   
                       for (int i =0; i < count; i ++) {       
                       objc_property_t property = properties[i];        //根据objc_property_t获得其属性的名称--->C语言的字符串       
                       const char *name = property_getName(property); 
                         NSString *key = [NSString stringWithUTF8String:name];        //解码每个属性,利用kVC取出每个属性对应的数值      
                       [self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];  
                }   
                 return self;
                }
            
        3)交换方法
    
          一.例如数组越界问题,防止系统崩溃(NSMutableArray 添加空值会出现崩溃)
    
             ① 新建一个分类,分类中引入头文件,实现下列方法
    
                + (void)load{
                    Method orginalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
                    Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(Mn_addObject:));
                    
                    method_exchangeImplementations(orginalMethod, newMethod);
                }
                
                - (void)Mn_addObject:(id)object{
                    if (object) {
                        [self Mn_addObject:object];
                    }
                }
    
        ②在项目文件中,正常使用,若添加空值,不会崩溃只会出现报警信息
    
             NSMutableArray *arr = [NSMutableArray array];
            [arr addObject:nil];
    
        二.生命周期
    
            ①创建分类
    
                //load方法会在类第一次加载的时候被调用
                //调用的时间比较靠前,适合在这个方法里做方法交换
                + (void)load{
                    //方法交换应该被保证,在程序中只会执行一次
                    static dispatch_once_t onceToken;
                    dispatch_once(&onceToken, ^{
                        //获得viewController的生命周期方法的selector
                        SEL systemSel = @selector(viewWillAppear:);
                        //自己实现的将要被交换的方法的selector
                        SEL swizzSel = @selector(swiz_viewWillAppear:);
                        //两个方法的Method
                        Method systemMethod = class_getInstanceMethod([self class], systemSel);
                        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
                        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
                        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
                        if (isAdd) {
                            //如果成功,说明类中不存在这个方法的实现
                            //将被交换方法的实现替换到这个并不存在的实现
                            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
                        }else{
                            //否则,交换两个方法的实现
                            method_exchangeImplementations(systemMethod, swizzMethod);
                        }
                    });
                }
                - (void)swiz_viewWillAppear:(BOOL)animated{
                    //这时候调用自己,看起来像是死循环
                    //但是其实自己的实现已经被替换了
                    [self swiz_viewWillAppear:animated];
                    NSLog(@"swizzle");
                }
    
            ②在一个自己定义的viewController中重写viewWillAppear,Run起来看看输出吧!
                
                - (void)viewWillAppear:(BOOL)animated{
                    [super viewWillAppear:animated];
                    NSLog(@"viewWillAppear");
                }
    
     4)关联属性
    
      typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
          OBJC_ASSOCIATION_ASSIGN = 0,  //相当于属性中的assign         
          OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,    //retain,monatomic
          OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  //copy,nonatomic
          OBJC_ASSOCIATION_RETAIN = 01401,    //retain 
          OBJC_ASSOCIATION_COPY = 01403     //copy    
      };
    
             //添加关联对象
            - (void)addAssociatedObject:(id)object{
                objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }
            //获取关联对象
            - (id)getAssociatedObject{
                return objc_getAssociatedObject(self, _cmd);
            }
    
            //样例
            - (void)viewDidLoad {
                [super viewDidLoad];
                // Do any additional setup after loading the view, typically from a nib.
                UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
                btn.backgroundColor = [UIColor blackColor];
                btn.frame = CGRectMake(100, 100, 60, 30);
                [self.view addSubview:btn];
                 objc_setAssociatedObject(btn, myBtnKey, @"mybtn", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
                [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];            
            }
    
            - (void)btnClick:(id)sender {
                NSString *str = objc_getAssociatedObject(sender, myBtnKey);
                /**
                 *  CODE
                 */
            }
    
    5)方法拦截
    
           + (BOOL)resolveClassMethod:(SEL)sel;
            + (BOOL)resolveInstanceMethod:(SEL)sel;
    
    图片.png
     _objc_msgForward是 IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
      IMP msgForward = _objc_msgForward;
      如果手动调用objc_msgForward,将跳过查找IMP的过程,而是直接出发“消息转发”,进入如下流程:
        1)+ (BOOL)resolveInstanceMethodL:(SEL)sel 实现方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过class_addMethod函数动态地添加方法,消息得到处理,此流程完毕。
        2)在第一步返回的是NO时,就会进入 -(id)forwardTargetForSelector:(SEL)aSelector 方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三不。若返回某个对象,则会调用该对象的方法。
        3)若第二部返回的是nil,则我们首先要通过 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 指定方法签名,若返回nil,则表示不处理。若返回方法签名,则会进入下一步。
        4)当第三步放回方法签名后,就会调用 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等。
        5)若没有实现 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,那么会进入 -(void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示打不到响应的方法。到此,动态解析的流程就结束了。
    
    6)runtime如何实现weak变量的自动置nil?
          runtime对注册的类会进行布局,对于weak对象会放入一个hash表中。用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc。假如weak指向的对象内存地址是a,那么就会以a为键,在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。
         weak修饰的指针默认值是nil(在Objective-C中向nil发送消息是安全的)
    

    相关文章

      网友评论

          本文标题:iOS - Runtime相关

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