iOS - Runtime相关

一.什么是 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发送消息是安全的)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容