iOS - Runtime相關(guān)

一.什么是 runtime ?

    rumtime是運(yùn)行時(shí)庫,基于c語言的api接口,
    作用是動(dòng)態(tài)的創(chuàng)建一個(gè)類 動(dòng)態(tài)的添加屬性和方法 遍歷屬性和方法名 動(dòng)態(tài)修改屬性和方法等等 
    1.能動(dòng)態(tài)產(chǎn)生一個(gè)類,一個(gè)成員變量,一個(gè)方法
    2.能動(dòng)態(tài)修改一個(gè)類,一個(gè)成員變量,一個(gè)方法
    3.能動(dòng)態(tài)刪除一個(gè)類,一個(gè)成員變量,一個(gè)方法

    //類在runtime中的表示
    struct objc_class {
        Class isa;//指針,顧名思義,表示是一個(gè)什么,
        //實(shí)例的isa指向類對(duì)象,類對(duì)象的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;//緩存
        //一種優(yōu)化,調(diào)用過的方法存入緩存列表,下次調(diào)用先找緩存
        struct objc_protocol_list *protocols //協(xié)議列表
        #endif
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */

二.runtime的頭文件

    #import <objc/runtime.h> 包含對(duì)類、成員變量、屬性、方法的操作
     #import <objc> 包含消息機(jī)制

三.消息發(fā)送步驟

  1)受限檢測這個(gè) selector 是不是要忽略。比如Mac OS X 開發(fā),有了垃圾回收就不理會(huì)retain , release這些函數(shù)。
  2)檢測這個(gè) selector 的 target 是不是 nil , Objt 允許我們對(duì)一個(gè) nil 對(duì)象執(zhí)行任何方法不會(huì) Crash ,因?yàn)檫\(yùn)行時(shí)會(huì)被忽略掉。
  3)如果上面兩步都通過了,那么就開始查找這個(gè)類的實(shí)現(xiàn) IMP , 先從 cache 哩查找,如果找到了就運(yùn)行對(duì)應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼。
  4)如果 cache 找不到就找類的方法列表中是否有對(duì)應(yīng)的方法。
  5)如果類的方法列表找不到就到父類的方法列表中查找,一直找到 NSObjiect 類為止。
  6)如果還找不到,進(jìn)入動(dòng)態(tài)方法解析。

四.常用方法

    //動(dòng)態(tài)攔截調(diào)用
    + (BOOL)resolveClassMethod:(SEL)sel;
    + (BOOL)resolveInstanceMethod:(SEL)sel;

    //遍歷相關(guān)
    class_copyMethodList(返回一個(gè)指向類的方法數(shù)組的指針) 
    class_copyIvarList (返回一個(gè)指向類的成員變量數(shù)組的指針)
    class_copyPropertyList(返回一個(gè)指向類的屬性數(shù)組的指針)

    //修改屬性
        objc_getAssociatedObject 
        objc_setAssocaitedObject

    //交換
        class_getInstanceMethod
        method_exchangeImplementation(systemMethod, swizzMethod)

五.應(yīng)用

    1)動(dòng)態(tài)的遍歷一個(gè)類的所有成員變量,用于字典轉(zhuǎn)模型,歸檔解檔操作

            - (void)viewDidLoad {    
                  [super viewDidLoad];    
                  /** 利用runtime遍歷一個(gè)類的全部成員變量     
                      1.導(dǎo)入頭文件<objc/runtime.h>     */    
                  unsigned int count = 0;   
                 /** Ivar:表示成員變量類型 */    
                  Ivar *ivars = class_copyIvarList([BDPerson class], &count);//獲得一個(gè)指向該類成員變量的指針   
                 for (int i =0; i < count; i ++) {        
                //獲得Ivar      
                  Ivar ivar = ivars[i];        //根據(jù)ivar獲得其成員變量的名稱--->C語言的字符串      
                  const char *name = ivar_getName(ivar);       
                   NSString *key = [NSString stringWithUTF8String:name];      
                  NSLog(@"%d----%@",i,key);
                }
            }

    2)可以利用遍歷類的屬性,來快速的進(jìn)行歸檔操作;將從網(wǎng)絡(luò)上下載的json數(shù)據(jù)進(jìn)行字典轉(zhuǎn)模型。

            注意:歸檔解檔需要遵守<NSCoding>協(xié)議,實(shí)現(xiàn)以下兩個(gè)方法
            - (void)encodeWithCoder:(NSCoder *)encoder{    
                //歸檔存儲(chǔ)自定義對(duì)象    
                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];        //根據(jù)objc_property_t獲得其屬性的名稱--->C語言的字符串       
               const char *name = property_getName(property);   
               NSString *key = [NSString   stringWithUTF8String:name];       
               //      編碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值            
               [encoder encodeObject:[self valueForKeyPath:key] forKey:key]; 
             }}
            
            - (instancetype)initWithCoder:(NSCoder *)decoder{    
                  //歸檔存儲(chǔ)自定義對(duì)象    
                    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];        //根據(jù)objc_property_t獲得其屬性的名稱--->C語言的字符串       
                   const char *name = property_getName(property); 
                     NSString *key = [NSString stringWithUTF8String:name];        //解碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值      
                   [self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];  
            }   
             return self;
            }
        
    3)交換方法

      一.例如數(shù)組越界問題,防止系統(tǒng)崩潰(NSMutableArray 添加空值會(huì)出現(xiàn)崩潰)

         ① 新建一個(gè)分類,分類中引入頭文件,實(shí)現(xiàn)下列方法

            + (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];
                }
            }

    ②在項(xiàng)目文件中,正常使用,若添加空值,不會(huì)崩潰只會(huì)出現(xiàn)報(bào)警信息

         NSMutableArray *arr = [NSMutableArray array];
        [arr addObject:nil];

    二.生命周期

        ①創(chuàng)建分類

            //load方法會(huì)在類第一次加載的時(shí)候被調(diào)用
            //調(diào)用的時(shí)間比較靠前,適合在這個(gè)方法里做方法交換
            + (void)load{
                //方法交換應(yīng)該被保證,在程序中只會(huì)執(zhí)行一次
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    //獲得viewController的生命周期方法的selector
                    SEL systemSel = @selector(viewWillAppear:);
                    //自己實(shí)現(xiàn)的將要被交換的方法的selector
                    SEL swizzSel = @selector(swiz_viewWillAppear:);
                    //兩個(gè)方法的Method
                    Method systemMethod = class_getInstanceMethod([self class], systemSel);
                    Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
                    //首先動(dòng)態(tài)添加方法,實(shí)現(xiàn)是被交換的方法,返回值表示添加成功還是失敗
                    BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
                    if (isAdd) {
                        //如果成功,說明類中不存在這個(gè)方法的實(shí)現(xiàn)
                        //將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
                        class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
                    }else{
                        //否則,交換兩個(gè)方法的實(shí)現(xiàn)
                        method_exchangeImplementations(systemMethod, swizzMethod);
                    }
                });
            }
            - (void)swiz_viewWillAppear:(BOOL)animated{
                //這時(shí)候調(diào)用自己,看起來像是死循環(huán)
                //但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
                [self swiz_viewWillAppear:animated];
                NSLog(@"swizzle");
            }

        ②在一個(gè)自己定義的viewController中重寫viewWillAppear,Run起來看看輸出吧!
            
            - (void)viewWillAppear:(BOOL)animated{
                [super viewWillAppear:animated];
                NSLog(@"viewWillAppear");
            }

 4)關(guān)聯(lián)屬性

  typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
      OBJC_ASSOCIATION_ASSIGN = 0,  //相當(dāng)于屬性中的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    
  };

         //添加關(guān)聯(lián)對(duì)象
        - (void)addAssociatedObject:(id)object{
            objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        //獲取關(guān)聯(lián)對(duì)象
        - (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類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息,但它并沒有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
  IMP msgForward = _objc_msgForward;
  如果手動(dòng)調(diào)用objc_msgForward,將跳過查找IMP的過程,而是直接出發(fā)“消息轉(zhuǎn)發(fā)”,進(jìn)入如下流程:
    1)+ (BOOL)resolveInstanceMethodL:(SEL)sel 實(shí)現(xiàn)方法,指定是否動(dòng)態(tài)添加方法。若返回NO,則進(jìn)入下一步,若返回YES,則通過class_addMethod函數(shù)動(dòng)態(tài)地添加方法,消息得到處理,此流程完畢。
    2)在第一步返回的是NO時(shí),就會(huì)進(jìn)入 -(id)forwardTargetForSelector:(SEL)aSelector 方法,這是運(yùn)行時(shí)給我們的第二次機(jī)會(huì),用于指定哪個(gè)對(duì)象響應(yīng)這個(gè)selector。不能指定為self。若返回nil,表示沒有響應(yīng)者,則會(huì)進(jìn)入第三不。若返回某個(gè)對(duì)象,則會(huì)調(diào)用該對(duì)象的方法。
    3)若第二部返回的是nil,則我們首先要通過 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 指定方法簽名,若返回nil,則表示不處理。若返回方法簽名,則會(huì)進(jìn)入下一步。
    4)當(dāng)?shù)谌椒呕胤椒ê灻螅蜁?huì)調(diào)用 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,我們可以通過anInvocation對(duì)象做很多處理,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對(duì)象等。
    5)若沒有實(shí)現(xiàn) -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,那么會(huì)進(jìn)入 -(void)doesNotRecognizeSelector:(SEL)aSelector方法。若我們沒有實(shí)現(xiàn)這個(gè)方法,那么就會(huì)crash,然后提示打不到響應(yīng)的方法。到此,動(dòng)態(tài)解析的流程就結(jié)束了。

6)runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
      runtime對(duì)注冊(cè)的類會(huì)進(jìn)行布局,對(duì)于weak對(duì)象會(huì)放入一個(gè)hash表中。用weak指向的對(duì)象內(nèi)存地址作為key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc。假如weak指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵,在這個(gè)weak表中搜索,找到所有以a為鍵的weak對(duì)象,從而設(shè)置為nil。
     weak修飾的指針默認(rèn)值是nil(在Objective-C中向nil發(fā)送消息是安全的)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容