OC底層知識(九) : Runtime

一、Runtime的介紹

  • Objective-C是一門動態(tài)性比較強的編程語言,跟C、C++等語言有著很大的不同
  • Objective-C的動態(tài)性是由Runtime API來支撐的
  • Runtime API提供的接口基本都是C語言的,源碼由C\C++\匯編語言編寫

二、isa詳解

  • 要想學習Runtime,首先要了解它底層的一些常用數據結構,比如isa指針

  • 在arm64架構之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內存地址

  • 從arm64架構開始,對isa進行了優(yōu)化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息

    isa詳解g
  • isa詳解 – 位域

    • nonpointer
      • 0,代表普通的指針,存儲著Class、Meta-Class對象的內存地址
      • 1,代表優(yōu)化過,使用位域存儲更多的信息
    • has_assoc
      • 是否有設置過關聯對象,如果沒有,釋放時會更快
    • has_cxx_dtor
      • 是否有C++的析構函數(.cxx_destruct),如果沒有,釋放時會更快
    • shiftcls
      • 著Class、Meta-Class對象的內存地址信息
    • magic
      • 用于在調試時分辨對象是否未完成初始化
    • weakly_referenced
      • 是否有被弱引用指向過,如果沒有,釋放時會更快
    • deallocating
      • 對象是否正在釋放
    • extra_rc
      • 里面存儲的值是引用計數器減1
    • has_sidetable_rc
      • 引用計數器是否過大無法存儲在isa中
      • 如果為1,那么引用計數會存儲在一個叫SideTable的類的屬性中
  • 什么是isa?
    答:在 arm64之前isa就是一個普通的指針,里面就是直接存儲的類對象元類對象的地址值,但是從arm64位之后,isa經過優(yōu)化,它采用共用體的結構,將一個64位的內存數據分開來存儲了很多的東西,而其中的33位來存儲具體的地址值的。

三、objc_msgSend執(zhí)行流程可以分為3大階段
OC的方法調用:消息機制(objc_msgSend(id receiver, SEL selector)),給方法調用者發(fā)送消息

源碼解讀
  • 3.1、消息發(fā)送(其實也就是isa與superclass尋找方法(對象方法類方法))

    消息發(fā)送(MJ老師做的圖)

    - 如果是從 class_rw_t 中查找方法: 已經排序的二分查找,沒有排序的遍歷查找
    - receiver通過isa指針找到receiverClass
    - receiverClass通過superclass指針找到superClass

  • 3.2、動態(tài)方法解析(在消息發(fā)送機制找不到方法之后進行動態(tài)方法解析)

    動態(tài)方法解析

    • 可以實現以下方法,來動態(tài)添加方法實現
      + (BOOL)resolveInstanceMethod:(SEL)sel; (對象方法的動態(tài)添加)

      + (BOOL)resolveInstanceMethod:(SEL)sel
      {
         if (sel == @selector(test)) {
             // 獲取其他方法
             Method method = class_getInstanceMethod(self, @selector(other));
      
             // 動態(tài)添加test方法的實現
             class_addMethod(self, sel,
                             method_getImplementation(method),
                             method_getTypeEncoding(method));
             // 返回YES代表有動態(tài)添加方法
             return YES;
          }
        return [super resolveInstanceMethod:sel];
      }
      
      -(void)other{
      
         NSLog(@"%s",__func__);
      }
      

      + (BOOL)resolveClassMethod:(SEL)sel; (類方法的動態(tài)添加)

      + (BOOL)resolveClassMethod:(SEL)sel
      {
         if (sel == @selector(classTest)) {
            // 第一個參數是object_getClass(self)
            //class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
            // 獲取其他方法
            Method method = class_getClassMethod(self, @selector(classOther));
            // 動態(tài)添加test方法的實現
            class_addMethod(object_getClass(self), sel,
                     method_getImplementation(method),
                     method_getTypeEncoding(method));
      
            // 返回YES代表有動態(tài)添加方法
            return YES;
          }
        return [super resolveClassMethod:sel];
      }
      
      #pragma mark 類方法的轉換(類方法classTest找不到就轉換classOther方法)
      +(void)classOther{
      
         NSLog(@"類方法classTest找不到就轉換classOther方法");
      }
      

      Runtime動態(tài)轉換方法的demo Person 類

  • 3.3、消息轉發(fā)


    消息轉發(fā)

    解釋上面的圖:

    • 3.3.1、當動態(tài)解析過都找不到一個方法時候就會走消息轉發(fā),首先會調用forwardingTargetForSelector(前面可能是 +(void)或者-(void)),如果不返回nil就會走 返回類的 Selector,如果返回nil就走 3.3.2

      -(id)forwardingTargetForSelector:(SEL)aSelector{
      
           if (aSelector == @selector(test)) {
      
                //return [[People alloc]init];
                return nil;
           }
         return [super forwardingTargetForSelector:aSelector];
      }
      
    • 3.3.2、當forwardingTargetForSelector返回nil或者不寫這個方法時候,就會調用下面的方法

      #pragma mark 2
      /**
        方法簽名:返回值類型、參數類型
      */
      - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
      {
          if (aSelector == @selector(test)) {
      
            // 獲取其他方法(一),會走 #pragma mark 3
            /*
               Method method = class_getInstanceMethod([People class], @selector(test));
               return [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(method)];
             */
      
            // 下面這一句和上面2句一個意思(二) 會走 #pragma mark 3
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
      
            /**
               如果返回nil 就不會再走會走 #pragma mark 3 方法了,會報錯返回(doesNotRecognizeSelector)
               -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
             */
            // return nil;
           }
        return [super methodSignatureForSelector:aSelector];
      }
      
      #pragma mark 3.上面的方法不返回nil才會走
      /**
         NSInvocation封裝了一個方法調用,包括:方法調用者、方法名、方法參數
         anInvocation.target 方法調用者
         anInvocation.selector 方法名
         [anInvocation getArgument:NULL atIndex:0]
      */
      - (void)forwardInvocation:(NSInvocation *)anInvocation
      {
         //    anInvocation.target = [[MJCat alloc] init];
         //    [anInvocation invoke];
      
         [anInvocation invokeWithTarget:[[People alloc] init]];
       }
      

      當上面的 #pragma mark 2 返回nil的時候,就不會走#pragma mark 3,且報錯 doesNotRecognizeSelector

      methodSignatureForSelector 返回nil報錯 doesNotRecognizeSelector

      Runtime消息轉發(fā)的demo,看Student與People類

四、Runtime的常見API

  • 4.1、API-類

    • 動態(tài)創(chuàng)建一個類 (參數:父類,類名,額外的內存空間)

      /**
        三個參數:
        Class  _Nullable __unsafe_unretained superclass: 父類
        const char * _Nonnull name: 新的類名字
        size_t extraBytes: 需不需要為這個類增加額外的內存空間
       */
      objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes)
      
    • 注冊一個類 (要在類注冊之前添加成員變量)

      objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls)
      
    • 銷毀一個類

      objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls)
      
    • 獲取isa指向的Class

      object_getClass(id  _Nullable obj) 返回值: BOOL
      
    • 設置isa指向的Class

      object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls) 返回值: Class
      
    • 判斷一個OC對象是否為 Class

      object_isClass(id  _Nullable obj) 返回值: BOOL
      
    • 判斷一個Class是否為元類

      class_isMetaClass(Class  _Nullable __unsafe_unretained cls) 返回值: BOOL
      
    • 獲取父類

      class_getSuperclass(Class  _Nullable __unsafe_unretained cls) 返回值: Class
      
  • 4.2、API-成員變量

    • 獲取一個實例變量

      class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name)
      
    • 設置和獲取成員變量的值

      object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value)
      object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar)
      
    • 動態(tài)添加成員變量(已經注冊的類是不能動態(tài)添加成員變量的)

      /**
        Class  _Nullable __unsafe_unretained cls : 創(chuàng)建的新類
        const char * _Nonnull name : 成員變量的名字
        size_t size: 占用多少個字節(jié)
        uint8_t alignment: 對其,寫 1 就好
        const char * _Nullable types: 類型
       */
       class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types)
      
    • 拷貝實例變量列表(最后需要調用free釋放) class_copyIvarList

      unsigned int count;
      Ivar *ivars = class_copyIvarList([JKPerson class], &count);
      for (int i = 0; i < count; i++) {
         // 取出i位置的成員變量
         Ivar ivar = ivars[I];
         NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
      }
      free(ivars);
      

      舉例:上面的應用,我們可以把JKPerson 把它換成 UILabel 、UIButton 、UITextView等等來窺探一個類的成員變量信息,下面以UITextFiled為例,這是 demo

      UITextFiled的成員變量打印

      可以看到上面的打印,_placeholderLabel @"UITextFieldLabel",我們可以看到 UITextFiled的Placeholder的類是UITextFieldLabel,通過打印通過下面的打印我們可以看到UITextFieldLabel UILabel

       UITextField *textFiled = [[UITextField alloc]initWithFrame:CGRectMake(50, 100, 200, 50)];
       textFiled.placeholder = @"請輸入手機號";
       [self.view addSubview:textFiled];
       id placeholderLabel = [textFiled valueForKeyPath:@"_placeholderLabel"];
       NSLog(@"%@ %@",[placeholderLabel class],[placeholderLabel superclass]);
       打印結果是:UITextFieldLabel UILabel
      

      所以我們可以設置UITextFiled的 placeholder 顏色新的方式

      第1種修改顏色
      [textFiled setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
      第2種修改顏色
      UILabel *placeholderLabel = [textFiled valueForKeyPath:@"_placeholderLabel"];
      placeholderLabel.textColor = [UIColor greenColor];
      
    • 獲取成員變量相關信息(使用挨著的上面)

       const char *ivar_getName(Ivar v)
       const char *ivar_getTypeEncoding(Ivar v)
      
  • 4.3、API-屬性

    • 獲取一個屬性

      objc_property_t class_getProperty(Class cls,const char *name)
      
    • 拷貝屬性列表

      objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount)
      
    • 動態(tài)添加屬性

      BOOL class_addProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount)
      
    • 動態(tài)替換屬性

       void class_replaceProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount)
      
    • 獲取一些屬性的信息

      const char *property_getName(objc_property_t property)
      const char *property_getAttributes(objc_property_t property)
      
  • 4.4、API-方法(method_exchangeImplementations是比較常用的,這是demo)

    • 獲得一個實例方法、類方法

      Methon class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name)
      Methon  class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name)
      
    • 方法實現相關操作

      IMP class_getMethodImplementation(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name)
      IMP method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp)
      void method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2)
      
    • 拷貝方法列表(最后需要調用free釋放)

      Methon class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount)
      
    • 動態(tài)添加方法

      BOOL class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
      
    • 動態(tài)替換方法

      IMP class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types)
      
    • 獲取方法的相關信息(帶有copy的需要調用free去釋放)

      SEL method_getName(Method  _Nonnull m)
      IMP method_getImplementation(Method  _Nonnull m)
      const char *method_getTypeEncoding(Method  _Nonnull m)
      unsigned int method_getNumberOfArguments(Method  _Nonnull m)
      char *method_copyReturnType(Method  _Nonnull m)
      char *method_copyArgumentType(Method  _Nonnull m, unsigned int index)
      
    • 選擇器相關

      const char  *sel_getName(SEL  _Nonnull sel)
      SEL sel_registerName(const char * _Nonnull str)
      
    • 用block作為方法實現

      IMP imp_implementationWithBlock(id  _Nonnull block)
      id imp_getBlock(IMP  _Nonnull anImp)
      BOOL imp_removeBlock(IMP  _Nonnull anImp)
      

五、常見面試題

  • 5.1、講一下 OC 的消息機制
    答:OC 中的方法調用其實都是轉成了 objc_msgSend 函數的調用,給receiver(方法調用者) 發(fā)送一條消息(selector方法名),objc_msgSend底層有3大階段(會回答這3個階段看是上面的

  • 5.2、消息轉發(fā)機制流程
    答:看上面的 3.3

  • 5.3、說出下面的打印
    題目內容: People 和Student兩個類,Student繼承于People,People繼承于NSObject


    Student繼承于People
    #import "Student.h"
    @implementation Student
    
    -(void)printContent{
    
        NSLog(@"[self class] = %@",[self class]);
        NSLog(@"[self superclass] = %@",[self superclass]);
    
        NSLog(@"[super class] = %@",[super class]);
        NSLog(@"[super superclass] = %@",[super superclass]);
    
    }
    @end
    
    打印結果為:
    [self class] = Student
    [self superclass] = People
    [super class] = Student
    [super superclass] = People
    

    解釋:

    • [self class] 是打印 類,很明顯是 Student;
    • [self superclass] 是通過superclass查找父類,Student繼承于People,所以打印時People
    • [super class] 其實相比[self class]在本質上它們的 receiver 都是 self,所以在打印上都是 Student
    • [super superclass] 其實相比[self superclass]在本質上它們的 receiver 都是 self,所以在打印上都是 People
  • 5.4、說一下下面的打印,Student是繼承于NSObject的類


    Student是繼承于NSObject的類
    id student = [[Student alloc]init];
    
    NSLog(@"%d",[student isMemberOfClass:[Student class]]);
    NSLog(@"%d",[student isMemberOfClass:[NSObject class]]);
    
    NSLog(@"%d",[student isKindOfClass:[Student class]]);
    NSLog(@"%d",[student isKindOfClass:[NSObject class]]);
    
    打印結果是:
     1
     0
     1
     1
    

    解釋:先展示一下OC的源碼

    + (BOOL)isMemberOfClass:(Class)cls {
         return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
         return [self class] == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
          for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
             if (tcls == cls) return YES;
          }
       return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
         for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
             if (tcls == cls) return YES;
         }
      return NO;
    }
    
    • 5.4.1、先說 - 號的或者說實例方法的 - (BOOL)isMemberOfClass:(Class)cls- (BOOL)isKindOfClass:(Class)cls

      • isMemberOfClass: 是判斷一個對象是否是某一個類,看OC源碼是判斷 return [self class] == cls;,可以看出是判斷是否相同類,一對一,如下

        id student = [[Student alloc]init];
        
        NSLog(@"%d",[student isMemberOfClass:[Student class]]);  打印是 1
        NSLog(@"%d",[student isMemberOfClass:[NSObject class]]); 打印是 0
        
      • isKindOfClass: 是判斷一個對象是否是某一個類或者是某一個類的子類,看上面OC源碼是用了for循環(huán)進行查詢

        id student = [[Student alloc]init];
        
        NSLog(@"%d",[student isMemberOfClass:[Student class]]); 打印是 1
        NSLog(@"%d",[student isMemberOfClass:[NSObject class]]); 打印是 0
        
        NSLog(@"%d",[student isKindOfClass:[Student class]]); 打印是 1
        NSLog(@"%d",[student isKindOfClass:[NSObject class]]); 打印是 1
        
    • 5.4.2、再說 + 號的或者說類方法的 +(BOOL)isMemberOfClass:(Class)cls+ (BOOL)isKindOfClass:(Class)cls

      • isMemberOfClass: 是判斷一個對象是否是某一個類,看OC源碼是判斷 return [self class] == cls;,可以看出是判斷是否相同類,一對一,如下

        NSLog(@"%d",[Student isMemberOfClass:object_getClass([Student class])]); 打印是 1
        NSLog(@"%d",[Student isMemberOfClass:object_getClass([NSObject class])]); 打印是 0
        
      • isKindOfClass: 是判斷一個對象是否是某一個類或者是某一個類的子類,看上面OC源碼是用了for循環(huán)進行查詢

        NSLog(@"%d",[Student isKindOfClass:object_getClass([Student class])]); 打印是 1
        NSLog(@"%d",[Student isKindOfClass:object_getClass([NSObject class])]); 打印是 1
        
    • 5.4.3、說一個特殊的情況:一看下面的 [NSObject class] 大多數人都會認為結果是 0,正常情況下是如此的,但是呢,Student 類的 基類 NSObject 的superclass指向的是 自己的類對象 [NSObject class],故下面的結果是 1

      NSLog(@"%d",[Student isKindOfClass:[NSObject class]]); 打印結果為 1
      

      說明:不管上面的 Student 是什么類型,結果都是 1,因為通過superclass都會回到 [NSObject class]

    • 5.4.4、下面是一張有關 isa與superclass的圖


      isa與superclass的圖
  • 5.5、什么是 Runtime ?平時項目中用過嗎?

    答:方法交換,窺探某一個類的成員變量,字典轉model等等

六:另外增加的測試demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容