runtime講解及實踐

一:基本概念

Runtime基本是用C和匯編寫的色难,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護的開源代碼棒掠。蘋果和GNU各自維護一個開源的runtime版本凸主,這兩個版本之間都在努力的保持一致鸥拧。Objective-C 從三種不同的層級上與 Runtime 系統(tǒng)進行交互,分別是通過 Objective-C 源代碼累榜,通過 Foundation 框架的NSObject類定義的方法营勤,通過對 runtime 函數(shù)的直接調(diào)用。大部分情況下你就只管寫你的Objc代碼就行壹罚,runtime 系統(tǒng)自動在幕后辛勤勞作著葛作。

  • RunTime簡稱運行時,就是系統(tǒng)在運行的時候的一些機制,其中最主要的是消息機制渔嚷。
  • 對于C語言进鸠,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù),編譯完成之后直接順序執(zhí)行形病,無任何二義性客年。
  • OC的函數(shù)調(diào)用成為消息發(fā)送。屬于動態(tài)調(diào)用過程漠吻。在編譯的時候并不能決定真正調(diào)用哪個函數(shù)(事實證明量瓜,在編 譯階段,OC可以調(diào)用任何函數(shù)途乃,即使這個函數(shù)并未實現(xiàn)绍傲,只要申明過就不會報錯。而C語言在編譯階段就會報錯)耍共。
  • 只有在真正運行的時候才會根據(jù)函數(shù)的名稱找 到對應(yīng)的函數(shù)來調(diào)用烫饼。

二:runtime的具體實現(xiàn)

我們寫的oc代碼,它在運行的時候也是轉(zhuǎn)換成了runtime方式運行的试读,更好的理解runtime杠纵,也能幫我們更深的掌握oc語言。
每一個oc的方法钩骇,底層必然有一個與之對應(yīng)的runtime方法比藻。

  • 當(dāng)我們用OC寫下這樣一段代碼
    [tableView cellForRowAtIndexPath:indexPath];

  • 在編譯時RunTime會將上述代碼轉(zhuǎn)化成[發(fā)送消息]
    objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常見方法

unsigned int count;

  • 獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
   for (unsigned int i=0; i<count; i++) {
       const char *propertyName = property_getName(propertyList[I]);
       NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
   }

  • 獲取方法列表
   Method *methodList = class_copyMethodList([self class], &count);
   for (unsigned int i; i<count; i++) {
       Method method = methodList[I];
       NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
   }

  • 獲取成員變量列表

    Ivar *ivarList = class_copyIvarList([self class], &count);
      for (unsigned int i; i<count; i++) {
          Ivar myIvar = ivarList[I];
          const char *ivarName = ivar_getName(myIvar);
          NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
      } 
    
    
  • 獲取協(xié)議列表

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
   for (unsigned int i; i<count; i++) {
       Protocol *myProtocal = protocolList[I];
       const char *protocolName = protocol_getName(myProtocal);
       NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
   }

現(xiàn)在有一個Person類,和person創(chuàng)建的xiaoming對象,有test1和test2兩個方法

  • 獲得類方法
Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

  • 獲得實例方法
Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);

  • 添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

  • 替換原方法實現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

  • 交換兩個方法
method_exchangeImplementations(oriMethod, cusMethod);

四:常見作用

  • 動態(tài)的添加對象的成員變量和方法
  • 動態(tài)交換兩個方法的實現(xiàn)
  • 攔截并替換方法
  • 在方法上增加額外功能
  • 實現(xiàn)NSCoding的自動歸檔和解檔
  • 實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換

五:代碼實現(xiàn)

要使用runtime倘屹,要先引入頭文件#import <objc/runtime.h>
這些代碼的實例有淺入深逐步講解银亲,最后附上一個我在公司項目中遇到的一個實際問題。

1. 動態(tài)變量控制

在程序中纽匙,xiaoming的age是10务蝠,后來被runtime變成了20,來看看runtime是怎么做到的烛缔。

1.動態(tài)獲取XiaoMing類中的所有屬性[當(dāng)然包括私有]

Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);

2.遍歷屬性找到對應(yīng)name字段

const char *varName = ivar_getName(var);

3.修改對應(yīng)的字段值成20

object_setIvar(self.xiaoMing, var, @"20");

4.代碼參考

-(void)answer{
     unsigned int count = 0;
     Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);
     for (int i = 0; i<count; i++) {
         Ivar var = ivar[I];
         const char *varName = ivar_getName(var);
         NSString *name = [NSString stringWithUTF8String:varName];
         if ([name isEqualToString:@"_age"]) {
             object_setIvar(self.xiaoMing, var, @"20");
             break;
         }
     }
     NSLog(@"XiaoMing's age is %@",self.xiaoMing.age);
 }

2.動態(tài)添加方法

在程序當(dāng)中请梢,假設(shè)XiaoMing的中沒有guess這個方法赠尾,后來被Runtime添加一個名字叫g(shù)uess的方法力穗,最終再調(diào)用guess方法做出相應(yīng)毅弧。那么,Runtime是如何做到的呢当窗?

1.動態(tài)給XiaoMing類中添加guess方法:

 class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");

這里參數(shù)地方說明一下:

(IMP)guessAnswer 意思是guessAnswer的地址指針;
"v@:" 意思是够坐,v代表無返回值void,如果是i則代表int崖面;@代表 id sel; : 代表 SEL _cmd;
“v@:@@” 意思是元咙,兩個參數(shù)的沒有返回值。

2.調(diào)用guess方法響應(yīng)事件:

[self.xiaoMing performSelector:@selector(guess)];

3.編寫guessAnswer的實現(xiàn):

void guessAnswer(id self,SEL _cmd){
NSLog(@"i am from beijing");
}

這個有兩個地方留意一下:

  • void的前面沒有+巫员、-號庶香,因為只是C的代碼。
  • 必須有兩個指定參數(shù)(id self,SEL _cmd)

4.代碼參考

 -(void)answer{
     class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
     if ([self.xiaoMing respondsToSelector:@selector(guess)]) {

         [self.xiaoMing performSelector:@selector(guess)];

     } else{
         NSLog(@"Sorry,I don't know");
     }
 }

 void guessAnswer(id self,SEL _cmd){

     NSLog(@"i am from beijing");

 }

3:動態(tài)交換兩個方法的實現(xiàn)

在程序當(dāng)中简识,假設(shè)XiaoMing的中有test1test2這兩個方法赶掖,后來被Runtime交換方法后,每次調(diào)動test1 的時候就會去執(zhí)行test2七扰,調(diào)動test2 的時候就會去執(zhí)行test1奢赂, 。那么颈走,Runtime是如何做到的呢膳灶?

  1. 獲取這個類中的兩個方法并交換
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1));
    Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2));
    method_exchangeImplementations(m1, m2);

交換方法之后,以后每次調(diào)用這兩個方法都會交換方法的實現(xiàn)

4:攔截并替換方法

在程序當(dāng)中立由,假設(shè)XiaoMing的中有test1這個方法轧钓,但是由于某種原因,我們要改變這個方法的實現(xiàn)锐膜,但是又不能去動它的源代碼(正如一些開源庫出現(xiàn)問題的時候)毕箍,這個時候runtime就派上用場了。

我們先增加一個tool類枣耀,然后寫一個我們自己實現(xiàn)的方法-change霉晕,
通過runtime把test1替換成change。

Class PersionClass = object_getClass([Person class]);
Class toolClass = object_getClass([tool class]);

    ////源方法的SEL和Method

    SEL oriSEL = @selector(test1);
    Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);

    ////交換方法的SEL和Method

    SEL cusSEL = @selector(change);
    Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);

    ////先嘗試給源方法添加實現(xiàn)捞奕,這里是為了避免源方法沒有實現(xiàn)的情況

    BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
    if (addSucc) {
          // 添加成功:將源方法的實現(xiàn)替換到交換方法的實現(xiàn)     
        class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

    }else {
    //添加失斘摺:說明源方法已經(jīng)有實現(xiàn),直接將兩個方法的實現(xiàn)交換即
method_exchangeImplementations(oriMethod, cusMethod);  
  }

5:在方法上增加額外功能

有這樣一個場景颅围,出于某些需求伟葫,我們需要跟蹤記錄APP中按鈕的點擊次數(shù)和頻率等數(shù)據(jù),怎么解決院促?當(dāng)然通過繼承按鈕類或者通過類別實現(xiàn)是一個辦法筏养,但是帶來其他問題比如別人不一定會去實例化你寫的子類斧抱,或者其他類別也實現(xiàn)了點擊方法導(dǎo)致不確定會調(diào)用哪一個,runtime可以這樣解決:

@implementation UIButton (Hook)

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        Class selfClass = [self class];

        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);

        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }

    });
}

- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [CountTool addClickCount];
    [self mySendAction:action to:target forEvent:event];
}

@end

load方法會在類第一次加載的時候被調(diào)用,調(diào)用的時間比較靠前渐溶,適合在這個方法里做方法交換,方法交換應(yīng)該被保證辉浦,在程序中只會執(zhí)行一次。

6.實現(xiàn)NSCoding的自動歸檔和解檔

如果你實現(xiàn)過自定義模型數(shù)據(jù)持久化的過程茎辐,那么你也肯定明白宪郊,如果一個模型有許多個屬性,那么我們需要對每個屬性都實現(xiàn)一遍encodeObjectdecodeObjectForKey方法拖陆,如果這樣的模型又有很多個弛槐,這還真的是一個十分麻煩的事情。下面來看看簡單的實現(xiàn)方式依啰。
假設(shè)現(xiàn)在有一個Movie類乎串,有3個屬性,它的h文件這這樣的

#import <Foundation/Foundation.h>

//1\. 如果想要當(dāng)前類可以實現(xiàn)歸檔與反歸檔速警,需要遵守一個協(xié)議NSCoding
@interface Movie : NSObject<NSCoding>

@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;

@end

如果是正常寫法叹誉, m文件應(yīng)該是這樣的:

#import "Movie.h"
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:_movieId forKey:@"id"];
    [aCoder encodeObject:_movieName forKey:@"name"];
    [aCoder encodeObject:_pic_url forKey:@"url"];

}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        self.movieId = [aDecoder decodeObjectForKey:@"id"];
        self.movieName = [aDecoder decodeObjectForKey:@"name"];
        self.pic_url = [aDecoder decodeObjectForKey:@"url"];
    }
    return self;
}
@end

如果這里有100個屬性,那么我們也只能把100個屬性都給寫一遍坏瞄。
不過你會使用runtime后桂对,這里就有更簡便的方法。
下面看看runtime的實現(xiàn)方式:

#import "Movie.h"
#import <objc/runtime.h>
@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);

    for (int i = 0; i<count; i++) {
        // 取出i位置對應(yīng)的成員變量
        Ivar ivar = ivars[I];
        // 查看成員變量
        const char *name = ivar_getName(ivar);
        // 歸檔
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
        // 取出i位置對應(yīng)的成員變量
        Ivar ivar = ivars[I];
        // 查看成員變量
        const char *name = ivar_getName(ivar);
       // 歸檔
       NSString *key = [NSString stringWithUTF8String:name];
      id value = [decoder decodeObjectForKey:key];
       // 設(shè)置到成員變量身上
        [self setValue:value forKey:key];

        }
        free(ivars);
    } 
    return self;
}
@end

這樣的方式實現(xiàn)鸠匀,不管有多少個屬性蕉斜,寫這幾行代碼就搞定了。怎么缀棍,還嫌麻煩宅此,下面看看更加簡便的方法:兩句代碼搞定。
我們把encodeWithCoderinitWithCoder這兩個方法抽成宏

#import "Movie.h"
#import <objc/runtime.h>

#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[I];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\

#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[I];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder

{
    encodeRuntime(Movie)
}

- (id)initWithCoder:(NSCoder *)decoder
{
    initCoderRuntime(Movie)
}
@end

我們可以把這兩個宏單獨放到一個文件里面爬范,這里以后需要進行數(shù)據(jù)持久化的模型都可以直接使用這兩個宏父腕。

7.實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換

字典轉(zhuǎn)模型的應(yīng)用可以說是每個app必然會使用的場景,雖然實現(xiàn)的方式略有不同,但是原理都是一致的:遍歷模型中所有屬性青瀑,根據(jù)模型的屬性名璧亮,去字典中查找key,取出對應(yīng)的值斥难,給模型的屬性賦值枝嘶。
像幾個出名的開源庫:JSONModel,MJExtension等都是通過這種方式實現(xiàn)的。

  • 先實現(xiàn)最外層的屬性轉(zhuǎn)換
   // 創(chuàng)建對應(yīng)模型對象
    id objc = [[self alloc] init];

    unsigned int count = 0;

    // 1.獲取成員屬性數(shù)組
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 2.遍歷所有的成員屬性名,一個一個去字典中取出對應(yīng)的value給模型屬性賦值
    for (int i = 0; i < count; i++) {

        // 2.1 獲取成員屬性
        Ivar ivar = ivarList[I];

        // 2.2 獲取成員屬性名 C -> OC 字符串
       NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 2.3 _成員屬性名 => 字典key
        NSString *key = [ivarName substringFromIndex:1];

        // 2.4 去字典中取出對應(yīng)value給模型屬性賦值
        id value = dict[key];

        // 獲取成員屬性類型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        }

如果模型比較簡單哑诊,只有NSString群扶,NSNumber等,這樣就可以搞定了。但是如果模型含有NSArray竞阐,或者NSDictionary等缴饭,那么我們還需要進行第二步轉(zhuǎn)換。

  • 內(nèi)層數(shù)組骆莹,字典的轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { 

             //  是字典對象,并且屬性名對應(yīng)類型是自定義類型
            // 處理類型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 自定義對象,并且值是字典
            // value:user字典 -> User模型
            // 獲取模型(user)類對象
            Class modalClass = NSClassFromString(ivarType);

            // 字典轉(zhuǎn)模型
            if (modalClass) {
                // 字典轉(zhuǎn)模型 user
                value = [modalClass objectWithDict:value];
            }

        }

        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應(yīng)類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 轉(zhuǎn)換成id類型颗搂,就能調(diào)用任何對象的方法
                id idSelf = self;

                // 獲取數(shù)組中字典對應(yīng)的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                    id model =  [classModel objectWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型數(shù)組賦值給value
                value = arrM;

            }
        }

我自己覺得系統(tǒng)自帶的KVC模式字典轉(zhuǎn)模型就挺好的汪疮,假設(shè)movie是一個模型對象峭火,dict 是一個需要轉(zhuǎn)化的 [movie setValuesForKeysWithDictionary:dict]; 這個是系統(tǒng)自帶的字典轉(zhuǎn)模型方法,個人感覺也還是挺好用的智嚷,不過使用這個方法的時候需要在模型里面再實現(xiàn)一個方法才行,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key 重寫這個方法為了實現(xiàn)兩個目的:1. 模型中的屬性和字典中的key不一致的情況纺且,比如字典中有個id,我們需要把它賦值給uid屬性盏道;2. 字典中屬性比模型的屬性還多的情況。
如果出現(xiàn)以上兩種情況而沒有實現(xiàn)這個方法的話载碌,程序就會崩潰猜嘱。
這個方法的實現(xiàn):

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"id"]) {
        self.uid = value;
    }
}

六.幾個參數(shù)概念

以上的幾種方法應(yīng)該算是runtime在實際場景中所應(yīng)用的大部分的情況了,平常的編碼中差不多足夠用了嫁艇。
如果從頭仔細看到尾朗伶,相信你基本的用法應(yīng)該會了,雖然會用是主要的目的步咪,有幾個基本的參數(shù)概念還是要了解一下的论皆。

1.objc_msgSend

/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type 
* before being called. 
*/

這是官方的聲明,從這個函數(shù)的注釋可以看出來了猾漫,這是個最基本的用于發(fā)送消息的函數(shù)点晴。另外,這個函數(shù)并不能發(fā)送所有類型的消息悯周,只能發(fā)送基本的消息粒督。比如,在一些處理器上禽翼,我們必須使用objc_msgSend_stret來發(fā)送返回值類型為結(jié)構(gòu)體的消息屠橄,使用objc_msgSend_fpret來發(fā)送返回值類型為浮點類型的消息,而又在一些處理器上闰挡,還得使用objc_msgSend_fp2ret來發(fā)送返回值類型為浮點類型的消息锐墙。
最關(guān)鍵的一點:無論何時,要調(diào)用objc_msgSend函數(shù)解总,必須要將函數(shù)強制轉(zhuǎn)換成合適的函數(shù)指針類型才能調(diào)用贮匕。
objc_msgSend函數(shù)的聲明來看,它應(yīng)該是不帶返回值的花枫,但是我們在使用中卻可以強制轉(zhuǎn)換類型刻盐,以便接收返回值掏膏。另外携御,它的參數(shù)列表是可以任意多個的弥喉,前提也是要強制函數(shù)指針類型。
其實編譯器會根據(jù)情況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四個方法中選擇一個來調(diào)用滔韵。如果消息是傳遞給超類乙墙,那么會調(diào)用名字帶有”Super”的函數(shù)颖变;如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時,那么會調(diào)用名字帶有”stret”的函數(shù)听想。

2.SEL

objc_msgSend函數(shù)第二個參數(shù)類型為SEL腥刹,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器汉买,可以理解為區(qū)分方法的 ID衔峰,而這個 ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器蛙粘。
不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的垫卤,即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時會帶上參數(shù)類型(NSNumber一堆抽象工廠方法)出牧,Cocoa 中有好多長長的方法哦穴肘。

3.id

objc_msgSend第一個參數(shù)類型為id,大家對它都不陌生舔痕,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個isa指針评抚,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。
PS:isa指針不總是指向?qū)嵗龑ο笏鶎俚念愓匝叮荒芤揽克鼇泶_定類型盈咳,而是應(yīng)該用class方法來確定實例對象的類。因為KVO的實現(xiàn)機理就是將被觀察對象的isa指針指向一個中間類而不是真實的類边翼,這是一種叫做 isa-swizzling 的技術(shù)鱼响,詳見官方文檔.

4.Class

之所以說isa是指針是因為Class其實是一個指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
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;

可以看到運行時一個類還關(guān)聯(lián)了它的超類指針,類名组底,成員變量丈积,方法,緩存债鸡,還有附屬的協(xié)議江滨。
objc_class結(jié)構(gòu)體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針厌均。也就是說可以動態(tài)修改 *methodLists 的值來添加成員方法唬滑,這也是Category實現(xiàn)的原理.

/********面試題*******/
<article class="_2rhmJa">

OC中方法的調(diào)用 其實都是轉(zhuǎn)化為 objc_msgSend函數(shù)的調(diào)用

objc_msgSend函數(shù)的執(zhí)行流程可以分為3大階段

一、消息發(fā)送階段

首先判斷消息接收者是否為nil 如果為空直接退出
1、去自己類的方法緩存列表中查找該方法  如果找到則執(zhí)行該方法晶密,否則執(zhí)行下一步
2擒悬、去自己類的方法列表中查找該方法 如果找到先緩存該方法然后再執(zhí)行,否則執(zhí)行下一步
3稻艰、去自己父類的方法緩存列表中查找該方法  如果找到先緩存該方法到自己類中懂牧,然后執(zhí)行該方法,否則執(zhí)行下一步
4尊勿、去自己父類的方法列表中查找該方法 如果找到先緩存該方法到自己類中僧凤,然后執(zhí)行該方法,否則 到第二階段 

第一階段流程圖如下所示

image

二元扔、動態(tài)方法解析階段

1躯保、首先判斷曾經(jīng)有過動態(tài)解析  如果沒有則調(diào)用 +resolveInstanceMethod:或則+resoveClassMethod:方法來動態(tài)解析 然后標(biāo)記為已經(jīng)動態(tài)解析 最后重新走“消息發(fā)送”的流程

2、如果曾經(jīng)有過動態(tài)解析 則直接到第三個階段  消息轉(zhuǎn)發(fā)階段

第二階段流程圖如下所示:

image

三摇展、消息轉(zhuǎn)發(fā)階段

顧名思義就是 自己沒有能力處理這個方法 他需要將此方法轉(zhuǎn)交給別人類處理吻氧。

1、首先調(diào)用 forwardingTargetForSelector:方法 在此方法中:
如果返回值不為空 則直接給返回值轉(zhuǎn)發(fā)消息
如果返回值為空否則進入第2步 調(diào)用方法簽名函數(shù)

2咏连、調(diào)用methodSignatureForSelector:方法(方法簽名函數(shù))在此方法中:
如果返回值為空 則直接調(diào)用 調(diào)用doesNotRecognizeSelector:方法 然后程序報錯:unrecognized selector sent to instance 經(jīng)典錯誤
如果返回值不為空 則直接進入第3步 調(diào)用 forwardInvocation方法

3、forwardInvocation:方法
開發(fā)人員可以在此方法中處理調(diào)用的方法

第三階段流程圖如下所示

image

舉例:
1鲁森、首先我們創(chuàng)建一個類(Person 類) 然后在 Person 類中聲明一個對象方法(test)且不去實現(xiàn)

Person.h 聲明一個test方法

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

2祟滴、在其他地方調(diào)用 Person 類對象方法(test)

ViewController.m 調(diào)用 Person 類對象方法(test)

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor redColor];
    Person *person = [[Person alloc]init];
    [person test];
}

如果我們不做任何處理 則程序就會奔潰 報如下錯誤:

2021-01-19 10:18:44.480279+0800 LLLL[40671:839012] -[Person test]: unrecognized selector sent to instance 0x600000e20000
2021-01-19 10:18:44.485940+0800 LLLL[40671:839012] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x600000e20000'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff23e3de6e __exceptionPreprocess + 350
    1   libobjc.A.dylib                     0x00007fff512a19b2 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff23e5eb94 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    3   CoreFoundation                      0x00007fff23e4286c ___forwarding___ + 1436
    4   CoreFoundation                      0x00007fff23e44b58 _CF_forwarding_prep_0 + 120
    5   LLLL                                0x0000000102a9ff2e -[ViewController viewDidLoad] + 206
    6   UIKitCore                           0x00007fff48cc294e -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 83
    7   UIKitCore                           0x00007fff48cc786c -[UIViewController loadViewIfRequired] + 1084
    8   UIKitCore                           0x00007fff48cc7c89 -[UIViewController view] + 27
    9   UIKitCore                           0x00007fff493ab2d5 -[UIWindow addRootViewControllerViewIfPossible] + 326
    10  UIKitCore                           0x00007fff493aa8fe -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 219
    11  UIKitCore                           0x00007fff493ab989 -[UIWindow _setHidden:forced:] + 362
    12  UIKit                               0x0000000102fa4dc4 -[UIWindowAccessibility _orderFrontWithoutMakingKey] + 84
    13  UIKitCore                           0x00007fff493bedc5 -[UIWindow _mainQueue_makeKeyAndVisible] + 42
    14  UIKitCore                           0x00007fff495e0cdb -[UIWindowScene _makeKeyAndVisibleIfNeeded] + 202
    15  UIKitCore                           0x00007fff488cec30 +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1405
    16  UIKitCore                           0x00007fff4936eca5 -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1019
    17  UIKitCore                           0x00007fff4936efdc -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 291
    18  UIKitCore                           0x00007fff48ec177c -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361
    19  FrontBoardServices                  0x00007fff36d03d2e -[FBSSceneImpl _callOutQueue_agent_didCreateWithTransitionContext:completion:] + 419
    20  FrontBoardServices                  0x00007fff36d29dc1 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke.154 + 102
    21  FrontBoardServices                  0x00007fff36d0e757 -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 220
    22  FrontBoardServices                  0x00007fff36d29a52 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke + 355
    23  libdispatch.dylib                   0x0000000102d0ae8e _dispatch_client_callout + 8
    24  libdispatch.dylib                   0x0000000102d0dda2 _dispatch_block_invoke_direct + 300
    25  FrontBoardServices                  0x00007fff36d4f6e9 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 30
    26  FrontBoardServices                  0x00007fff36d4f3d7 -[FBSSerialQueue _queue_performNextIfPossible] + 441
    27  FrontBoardServices                  0x00007fff36d4f8e6 -[FBSSerialQueue _performNextFromRunLoopSource] + 22
    28  CoreFoundation                      0x00007fff23da1c91 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    29  CoreFoundation                      0x00007fff23da1bbc __CFRunLoopDoSource0 + 76
    30  CoreFoundation                      0x00007fff23da13ec __CFRunLoopDoSources0 + 268
    31  CoreFoundation                      0x00007fff23d9bf8e __CFRunLoopRun + 974
    32  CoreFoundation                      0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404
    33  GraphicsServices                    0x00007fff38c05bbe GSEventRunModal + 139
    34  UIKitCore                           0x00007fff49372964 UIApplicationMain + 1605
    35  LLLL                                0x0000000102aa01d2 main + 114
    36  libdyld.dylib                       0x00007fff5211c1fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

接下來使用runtime運行時 來處理這個崩潰

第一階段:消息發(fā)送階段

此階段為方法查找階段  找到了則調(diào)用 
 找不到則 進行第二步 方法動態(tài)解析階段 

第二階段:動態(tài)方法解析階段

在此階段 我們可以添加一個新的方法來 otherTest 來代替 test

Person.m

#import "Person.h"
#import <objc/runtime.h>

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(otherTest));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
         return YES;
    }
    return [super resolveInstanceMethod:sel];
}

- (void)otherTest{
    NSLog(@"我是代替方法 ----  %s",__func__);
}
@end

運行程序結(jié)果打印如下:

2021-01-19 10:41:28.143085+0800 LLLL[41023:856445] 我是代替方法 ----  -[Person otherTest]

這是第一補救階段,如果 +resolveInstanceMethod:函數(shù)內(nèi)部不做任何處理或則直接不實現(xiàn) 則進入 第三階段

第三階段:消息轉(zhuǎn)發(fā)階段

準(zhǔn)備:
首先再創(chuàng)建一個類(Student類) 然后在這個類中創(chuàng)建一個對象方法test 這個對象方法一定要與Person對象中的一樣 這樣才能找到 方法

Student.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Student : NSObject

- (void)test;

@end

NS_ASSUME_NONNULL_END

Student.m

#import "Student.h"

@implementation Student

- (void)test{

    NSLog(@"%s",__func__);

}
@end

實現(xiàn):

進入第三階段 首先會調(diào)用 forwardingTargetForSelector:方法 在此方法中我們可以把方法轉(zhuǎn)發(fā)給別的類來處理

Person.m

#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"
@implementation Person

//動態(tài)解析方法階段
+ (BOOL)resolveInstanceMethod:(SEL)sel{

//    //注意?? 如果不注釋則無法進行第三階段

//    if (sel == @selector(test)) {
//        Method method = class_getInstanceMethod(self, @selector(otherTest));
//        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
//         return YES;
//    }

    return [super resolveInstanceMethod:sel];
}

- (void)otherTest{
    NSLog(@"我是代替方法 ----  %s",__func__);
}

//消息轉(zhuǎn)發(fā)階段
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return  [[Student alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

此時運行代碼 控制臺打印如下:

2021-01-19 10:59:01.971909+0800 LLLL[41176:868929] -[Student test]

這一步則 完成了方法轉(zhuǎn)發(fā)歌溉。

如果 forwardingTargetForSelector: 方法中未返回其他類 則會調(diào)用以下兩個方法
methodSignatureForSelector:aSelector
forwardInvocation:anInvocation

Person.m

#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"
@implementation Person

//動態(tài)解析方法階段
+ (BOOL)resolveInstanceMethod:(SEL)sel{

    //    //注意?? 如果不注釋則無法進行第三階段

    //    if (sel == @selector(test)) {
    //        Method method = class_getInstanceMethod(self, @selector(otherTest));
    //        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
    //         return YES;
    //    }

    return [super resolveInstanceMethod:sel];
}

- (void)otherTest{
    NSLog(@"我是代替方法 ----  %s",__func__);
}

//消息轉(zhuǎn)發(fā)階段
- (id)forwardingTargetForSelector:(SEL)aSelector{
    //    //如果此方法不注釋 則不會走下一步
    //    if (aSelector == @selector(test)) {
    //        return  [[Student alloc]init];
    //    }
    return [super forwardingTargetForSelector:aSelector];
}

/*
 方法簽名:返回值垄懂、返回類型

 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{

    if (aSelector == @selector(test)) {
        /*
         v16@0:8

         第一個參數(shù) 返回值類型  如  v代表 void
         第二個參數(shù)  16 代表16個字這個節(jié)
         第三個參數(shù)  @ 代表一個對象
         第四個參數(shù)  0 從第0個字節(jié)開始
         第五個參數(shù)   : 代表SEL
         第六個參數(shù)  8代表從第八個字節(jié)開始

         可以簡寫成: v@:

         此處填寫請參考:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1

         舉例1:
         - (BOOL)ifSuccess:(NSString *)tag

         其ObjCTypes為:"B@:@",其中:

         "B":代表BOOL痛垛。 // NSLog(@"%s",@encode(BOOL))的結(jié)果為B
         "@":一個id類型的對象草慧,第一個參數(shù)類型,也就是objc _ msgSend的第一個參數(shù)
         ":":代表對應(yīng)的SEL匙头,第二個參數(shù)
         "@":一個id類型的對象漫谷,也就是tag。

         舉例2蹂析;

         - (void)goToSchoolWithPerson:(Person *)person;
         [zhangsan goToSchoolWithPerson:lisi];

         其ObjCTypes為: "v@:@" 那究竟是如何得來該字符串呢舔示?其實我們有兩種方式:

         1、 直接查表电抚。在Type Encodings里面列出了對應(yīng)關(guān)系惕稻。 鏈接如上

         2、使用 @encode()計算蝙叛。(如: NSLog(@"%s",@encode(BOOL))的結(jié)果為B )

         我們都知道消息發(fā)送會被轉(zhuǎn)換成objc _ msgSend(id reciever,SEL sel,prarams1,params2,....)俺祠。所以上面的方法會被轉(zhuǎn)換成:
         void objc_msgSend(zhangsan,@selector(goToSchoolWithPerson:),lisi);   //包含兩個隱藏參數(shù)

         這里的 “v@:@”就代表:
         "v":代表返回值void
         "@":代表一個對象,這里指代的id類型zhangsan,也就是消息的receiver
         ":":代表SEL
         "@":代表參數(shù)lisi

         */

        //        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];

    }

    return [super methodSignatureForSelector:aSelector];
}

/*
 NSInvocation封裝了一個方法調(diào)用,包括:方法調(diào)用者蜘渣、方法名淌铐、方法參數(shù)
 anInvocation.target 方法調(diào)用者
 anInvocation.selector 方法名
 [anInvocation getArgument:NULL atIndex:0]

 你可以在這個函數(shù)里面干你想干的事

 */

- (void)forwardInvocation:(NSInvocation *)anInvocation{

    //1、你可以轉(zhuǎn)發(fā)消息

    //    anInvocation.target = [[MJCat alloc] init];
    //    [anInvocation invoke];

    // 或
    //    [anInvocation invokeWithTarget:[[Student alloc] init]];

    //2宋梧、你可以干你想干的事匣沼。如:我就想打印
    NSLog(@"%s",__func__);

}

@end

由此可知:我們可以在三個地方來實現(xiàn)test方法

第一步:

+resolveInstanceMethod:(對象方法)
+resoveClassMethod:(類方法)

第二步:

forwardingTargetForSelector:  

第三步:

methodSignatureForSelector:aSelector
forwardInvocation:anInvocation

面試題 :什么是Runtime?平時項目中有用過么捂龄?

1释涛、OC是一門動態(tài)性比較強的編程語言,允許很多操作推遲到程序運行時再進行
2倦沧、OC的動態(tài)性就是由Runtime來支撐和實現(xiàn)的唇撬,Runtime是一套C語言的API,封裝了很多動態(tài)性相關(guān)的函數(shù)
3展融、平時編寫的OC代碼窖认,底層都是轉(zhuǎn)換成了Runtime API進行調(diào)用

具體應(yīng)用

1、利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
2告希、遍歷類的所有成員變量(修改textfield的占位文字顏色扑浸、字典轉(zhuǎn)模型、自動歸檔解檔)
3燕偶、交換方法實現(xiàn)(交換系統(tǒng)的方法)
4喝噪、利用消息轉(zhuǎn)發(fā)機制解決方法找不到的異常問題

舉例1:
1、窺探 系統(tǒng)自帶對象的私有屬性:如窺探UITextField 的屬性
我們可以根據(jù)需求找到你想要的東西 然后去重寫它等等

   UITextField * textField = [[UITextField alloc]init];
    // 成員變量的數(shù)量
    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField 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);

打印結(jié)果如下:

2021-01-20 10:06:44.646507+0800 LLLL[51081:1190161] _borderStyle q
2021-01-20 10:06:44.646605+0800 LLLL[51081:1190161] _minimumFontSize d
2021-01-20 10:06:44.646655+0800 LLLL[51081:1190161] _delegate @
2021-01-20 10:06:44.646713+0800 LLLL[51081:1190161] _background @"UIImage"
2021-01-20 10:06:44.646786+0800 LLLL[51081:1190161] _disabledBackground @"UIImage"
2021-01-20 10:06:44.646871+0800 LLLL[51081:1190161] _clearButtonMode q
2021-01-20 10:06:44.646929+0800 LLLL[51081:1190161] _leftView @"UIView"
2021-01-20 10:06:44.647006+0800 LLLL[51081:1190161] _leftViewMode q
2021-01-20 10:06:44.647095+0800 LLLL[51081:1190161] _rightView @"UIView"
2021-01-20 10:06:44.647180+0800 LLLL[51081:1190161] _rightViewMode q
2021-01-20 10:06:44.647274+0800 LLLL[51081:1190161] _contentCoverView @"UIView"
2021-01-20 10:06:44.647478+0800 LLLL[51081:1190161] _contentCoverViewMode q
2021-01-20 10:06:44.647650+0800 LLLL[51081:1190161] _backgroundCoverView @"UIView"
2021-01-20 10:06:44.647801+0800 LLLL[51081:1190161] _backgroundCoverViewMode q
2021-01-20 10:06:44.647988+0800 LLLL[51081:1190161] _traits @"UITextInputTraits"
2021-01-20 10:06:44.648146+0800 LLLL[51081:1190161] _nonAtomTraits @"UITextInputTraits"
2021-01-20 10:06:44.648569+0800 LLLL[51081:1190161] _fullFontSize @"_UIFullFontSize"
2021-01-20 10:06:44.648924+0800 LLLL[51081:1190161] _padding {UIEdgeInsets="top"d"left"d"bottom"d"right"d}
2021-01-20 10:06:44.649360+0800 LLLL[51081:1190161] _progress f
2021-01-20 10:06:44.649453+0800 LLLL[51081:1190161] _clearButton @"_UITextFieldClearButton"
2021-01-20 10:06:44.649528+0800 LLLL[51081:1190161] _clearButtonOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.649603+0800 LLLL[51081:1190161] _leftViewOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.649669+0800 LLLL[51081:1190161] _rightViewOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.665169+0800 LLLL[51081:1190161] _backgroundView @"UITextFieldBorderView"
2021-01-20 10:06:44.665276+0800 LLLL[51081:1190161] _disabledBackgroundView @"UITextFieldBorderView"
2021-01-20 10:06:44.665369+0800 LLLL[51081:1190161] _systemBackgroundView @"UITextFieldBackgroundView"
2021-01-20 10:06:44.665453+0800 LLLL[51081:1190161] _textContentView @"_UITextFieldCanvasView"
2021-01-20 10:06:44.665945+0800 LLLL[51081:1190161] _floatingContentView @"_UIFloatingContentView"
2021-01-20 10:06:44.666024+0800 LLLL[51081:1190161] _contentBackdropView @"UIVisualEffectView"
2021-01-20 10:06:44.666107+0800 LLLL[51081:1190161] _fieldEditorBackgroundView @"_UIDetachedFieldEditorBackgroundView"
2021-01-20 10:06:44.666174+0800 LLLL[51081:1190161] _fieldEditorEffectView @"UIVisualEffectView"
2021-01-20 10:06:44.666241+0800 LLLL[51081:1190161] _placeholderLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666313+0800 LLLL[51081:1190161] _suffixLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666406+0800 LLLL[51081:1190161] _prefixLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666497+0800 LLLL[51081:1190161] _iconView @"UIImageView"
2021-01-20 10:06:44.666576+0800 LLLL[51081:1190161] _label @"UILabel"
2021-01-20 10:06:44.666659+0800 LLLL[51081:1190161] _labelOffset d
2021-01-20 10:06:44.666745+0800 LLLL[51081:1190161] _overriddenPlaceholder @"NSAttributedString"
2021-01-20 10:06:44.666833+0800 LLLL[51081:1190161] _overriddenPlaceholderAlignment q
2021-01-20 10:06:44.666997+0800 LLLL[51081:1190161] _interactionAssistant @"UITextInteractionAssistant"
2021-01-20 10:06:44.667143+0800 LLLL[51081:1190161] _selectGestureRecognizer @"UITapGestureRecognizer"
2021-01-20 10:06:44.667323+0800 LLLL[51081:1190161] _fieldEditor @"UIFieldEditor"
2021-01-20 10:06:44.667865+0800 LLLL[51081:1190161] __textContainer @"NSTextContainer"
2021-01-20 10:06:44.667974+0800 LLLL[51081:1190161] __layoutManager @"_UIFieldEditorLayoutManager"
2021-01-20 10:06:44.668068+0800 LLLL[51081:1190161] _textStorage @"_UICascadingTextStorage"
2021-01-20 10:06:44.668145+0800 LLLL[51081:1190161] _linkTextAttributes @"NSDictionary"
2021-01-20 10:06:44.668221+0800 LLLL[51081:1190161] _pasteController @"UITextPasteController"
2021-01-20 10:06:44.668360+0800 LLLL[51081:1190161] _inputView @"UIView"
2021-01-20 10:06:44.668574+0800 LLLL[51081:1190161] _inputAccessoryView @"UIView"
2021-01-20 10:06:44.668765+0800 LLLL[51081:1190161] _recentsAccessoryView @"UIView"
2021-01-20 10:06:44.669371+0800 LLLL[51081:1190161] _systemInputViewController @"UISystemInputViewController"
2021-01-20 10:06:44.669459+0800 LLLL[51081:1190161] _atomBackgroundView @"UITextFieldAtomBackgroundView"
2021-01-20 10:06:44.669525+0800 LLLL[51081:1190161] _textDragDropSupport @"<UITextDragDropSupport>"
2021-01-20 10:06:44.669603+0800 LLLL[51081:1190161] _textItemDiscoverer @"_UITextItemDiscoverer"
2021-01-20 10:06:44.669681+0800 LLLL[51081:1190161] _textFieldFlags {?="verticallyCenterText"b1"isAnimating"b4"inactiveHasDimAppearance"b1"becomesFirstResponderOnClearButtonTap"b1"clearsPlaceholderOnBeginEditing"b1"adjustsFontSizeToFitWidth"b1"fieldEditorAttached"b1"canBecomeFirstResponder"b1"shouldSuppressShouldBeginEditing"b1"inResignFirstResponder"b1"undoDisabled"b1"explicitAlignment"b1"implementsCustomDrawing"b1"needsClearing"b1"suppressContentChangedNotification"b1"allowsEditingTextAttributes"b1"usesAttributedText"b1"backgroundViewState"b2"clearingBehavior"b2"overridePasscodeStyle"b1"shouldResignWithoutUpdate"b1"blurEnabled"b1"visualEffectViewEnabled"b1"disableFocus"b1"disableRemoteTextEditing"b1"allowsAttachments"b1"isReceivingDrop"b1"contentCoverUnsecuresText"b1"forcesClearButtonHighContrastAppearance"b1"contentInsetsFromFontsValid"b1}
2021-01-20 10:06:44.669868+0800 LLLL[51081:1190161] _deferringBecomeFirstResponder B
2021-01-20 10:06:44.670089+0800 LLLL[51081:1190161] _animateNextHighlightChange B
2021-01-20 10:06:44.670723+0800 LLLL[51081:1190161] _cuiCatalog @"CUICatalog"
2021-01-20 10:06:44.671168+0800 LLLL[51081:1190161] _cuiStyleEffectConfiguration @"CUIStyleEffectConfiguration"
2021-01-20 10:06:44.671270+0800 LLLL[51081:1190161] _roundedRectBackgroundCornerRadius d
2021-01-20 10:06:44.671352+0800 LLLL[51081:1190161] _overriddenAttributesForEditing @"NSArray"
2021-01-20 10:06:44.671407+0800 LLLL[51081:1190161] _adjustsFontForContentSizeCategory B
2021-01-20 10:06:44.671486+0800 LLLL[51081:1190161] _tvUseVibrancy B
2021-01-20 10:06:44.671550+0800 LLLL[51081:1190161] _disableTextColorUpdateOnTraitCollectionChange B
2021-01-20 10:06:44.671659+0800 LLLL[51081:1190161] _pasteDelegate @"<UITextPasteDelegate>"
2021-01-20 10:06:44.671865+0800 LLLL[51081:1190161] _baselineLayoutConstraint @"NSLayoutConstraint"
2021-01-20 10:06:44.672058+0800 LLLL[51081:1190161] _baselineLayoutLabel @"_UIBaselineLayoutStrut"
2021-01-20 10:06:44.672269+0800 LLLL[51081:1190161] _tvCustomTextColor @"UIColor"
2021-01-20 10:06:44.672410+0800 LLLL[51081:1190161] _tvCustomFocusedTextColor @"UIColor"
2021-01-20 10:06:44.672573+0800 LLLL[51081:1190161] _textDragOptions q
2021-01-20 10:06:44.672777+0800 LLLL[51081:1190161] _textDragDelegate @"<UITextDragDelegate>"
2021-01-20 10:06:44.672989+0800 LLLL[51081:1190161] _textDropDelegate @"<UITextDropDelegate>"
2021-01-20 10:06:44.673218+0800 LLLL[51081:1190161] _visualStyle @"_UITextFieldVisualStyle"

舉例2指么、字典轉(zhuǎn)模型

好多第三方字典轉(zhuǎn)模型 都會用到runtime  一般都是便利取出 模型(類)中的屬性獲取屬性名稱  然后經(jīng)過一系列的復(fù)雜處理 得到一個字符串酝惧,最后經(jīng)過 KVC來設(shè)置屬性值 (可自行查看 三方字典轉(zhuǎn)模型的內(nèi)部實現(xiàn))

1.講一下 OC 的消息機制

OC中的方法調(diào)用其實都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
objc_msgSend底層有3大階段
消息發(fā)送(當(dāng)前類伯诬、父類中查找)晚唇、動態(tài)方法解析、消息轉(zhuǎn)發(fā)

  1. 什么是Runtime盗似?平時項目中有用過么哩陕?

OC是一門動態(tài)性比較強的編程語言,允許很多操作推遲到程序運行時再進行
OC的動態(tài)性就是由Runtime來支撐和實現(xiàn)的桥言,Runtime是一套C語言的API萌踱,封裝了很多動態(tài)性相關(guān)的函數(shù)
平時編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進行調(diào)用
3.runtime具體應(yīng)用

利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
遍歷類的所有成員變量(修改textfield的占位文字顏色号阿、字典轉(zhuǎn)模型并鸵、自動歸檔解檔)
交換方法實現(xiàn)(交換系統(tǒng)的方法)
利用消息轉(zhuǎn)發(fā)機制解決方法找不到的異常問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扔涧,隨后出現(xiàn)的幾起案子园担,更是在濱河造成了極大的恐慌届谈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弯汰,死亡現(xiàn)場離奇詭異艰山,居然都是意外死亡,警方通過查閱死者的電腦和手機咏闪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門曙搬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸽嫂,你說我怎么就攤上這事纵装。” “怎么了据某?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵橡娄,是天一觀的道長。 經(jīng)常有香客問我癣籽,道長挽唉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任筷狼,我火速辦了婚禮瓶籽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘埂材。我一直安慰自己棘劣,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布楞遏。 她就那樣靜靜地躺著,像睡著了一般首昔。 火紅的嫁衣襯著肌膚如雪寡喝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天勒奇,我揣著相機與錄音预鬓,去河邊找鬼。 笑死赊颠,一個胖子當(dāng)著我的面吹牛格二,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播竣蹦,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼顶猜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痘括?” 一聲冷哼從身側(cè)響起长窄,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤滔吠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后挠日,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疮绷,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年嚣潜,在試婚紗的時候發(fā)現(xiàn)自己被綠了冬骚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡懂算,死狀恐怖只冻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情犯犁,我是刑警寧澤属愤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站酸役,受9級特大地震影響住诸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涣澡,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一贱呐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧入桂,春花似錦奄薇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜘腌,卻和暖如春沫屡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撮珠。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工沮脖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芯急。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓勺届,卻偏偏與公主長得像,于是被迫代替她去往敵國和親娶耍。 傳聞我的和親對象是個殘疾皇子免姿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • runtime詳解 公司項目用到一個三方開源庫,里面有個bug伺绽,不能改動源碼养泡,我想來想去嗜湃,只能通過runtime這...
    3875e78848bf閱讀 631評論 0 0
  • iOS Runtime機制的詳解 前要 將原代碼轉(zhuǎn)換為可執(zhí)行程序需要3步:編譯·鏈接·運行。不同的編譯語言在這個三...
    自律_自強_通達閱讀 4,566評論 0 4
  • 公司項目用到一個三方開源庫澜掩,里面有個bug购披,不能改動源碼,我想來想去肩榕,只能通過runtime這個萬能的手段來解決刚陡。...
    夜千尋墨閱讀 17,915評論 41 205
  • 引導(dǎo) runtime是運行時,對于從事iOS開發(fā)株汉,想要深入學(xué)習(xí)OC的人筐乳,runtime是必須熟悉掌握的東西。 ru...
    叫我小黑閱讀 901評論 1 4
  • 引導(dǎo) 對于從事 iOS 開發(fā)人員來說乔妈,所有的人都會答出「 Runtime 是運行時 」蝙云,什么情況下用 Runtim...
    Winny_園球閱讀 4,188評論 3 75