Runtime

  1. 方法調(diào)用的本質(zhì),就是讓對象發(fā)送消息。
  2. objc_msgSend,只有對象才能發(fā)送消息,因此以objc開頭。
  3. 使用消息機(jī)制的前提,必須導(dǎo)入 #import<objc/Runtime.h>

例子

調(diào)用對象方法 [dog eat]
本質(zhì)是讓對象發(fā)送消息 objc_msgSend(dog, @selector(ear));

消息機(jī)制的原理

對象根據(jù)方法編號 SEL 去映射表 Method List 查找對應(yīng)的方法實現(xiàn)

1.png

方法交換

例:給 + (instancetype)imageNamed:(NSString *)name 方法提供功能,每次加載圖片就打印 "hello word"

  1. 先搞個分類,定義一個加載圖片并且打印"hello"
  2. + (void)load 方法中進(jìn)行方法交換
@implementation UIImage (log)

+ (void)load{
    
    // 獲取兩個方法的方法地址
    Method imageNamed = class_getClassMethod(self, @selector(imageNamed:));
    Method imageWithName = class_getClassMethod(self, @selector(imageWihtName:));
    // 交換方法
    method_exchangeImplementations(imageWithName, imageName);
}

// 不能在分類中重寫系統(tǒng)方法 `+ (instancetype)imageNamed:(NSString *)name` ,應(yīng)為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用 `super`
+ (instancetype)imageWithName:(NSString *)name{
    // 這里調(diào)用 `imageWithName` ,相當(dāng)于調(diào)用 `imageName`
    UIImage *image = [self imageWithName:name];
    NSLog(@"Hello Word");
    return image;
}

交換原理:

2.png
3.png

動態(tài)添加方法

經(jīng)典面試題:有沒有使用過 performSelector 其實主要想問你有沒有動態(tài)添加過方法

@implementation ViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    
    Person *p = [[Person alloc] init];
    // 默認(rèn) `person` 沒有實現(xiàn) `eat` 方法,可以通過 `performSelector` 調(diào)用,但會報錯。
    // 動態(tài)添加方法就不會報錯
    [p performSelector:@selector(eat)];
}

@end

@implementation Person

// void(*)()
// 默認(rèn)方法都有兩個隱式參數(shù)
void eat(id self, SEL sel){
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當(dāng)一個對象調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(eat)) {
        // 動態(tài)添加 `eat` 方法
        
        // @param:給哪個類添加方法         
        // @param:添加方法的方法編號        
       // @param:添加方法的函數(shù)實現(xiàn)(函數(shù)地址) 
       // @param:函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd
       class_addmETHOD(self, @selector(eat), eat, "v@:");
    }
    
    return [super resolveInstanceMethod:sel];
}

@end

給分類添加屬性

原理:不用繼承給一個類添加屬性,其實本質(zhì)就是給這個類添加關(guān)聯(lián),并不是直接把這個值的內(nèi)存空間添加到類的內(nèi)存空間

AssociationsManager里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關(guān)聯(lián)對象的,這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個全局map里面。而map的key是這個對象的指針地址,而這個map的value又是另一個AssociationsHasMap,里面保存了關(guān)聯(lián)對象的key value 對

使用runtime Associate 方法關(guān)聯(lián)的對象,在主對象dealloc的時候不需要釋放
1.調(diào)用 -release 應(yīng)用計數(shù)變?yōu)榱?br> *調(diào)用 [self dealloc]
2.子類調(diào)用 - dealloc
3.NSObject 調(diào)用 - dealloc
*只做一件事:調(diào)用 runtime 的 objec_dispose() 方法
4.object_dispose()
*為C++的實例變量(ivars)調(diào)用destructors
*解除所有使用 runtime Associate 方法關(guān)系的對象
*解除所有 __weak 應(yīng)用
*調(diào)用 free()

@implementation ViewController

- (void)viewDidLoad{
    [super viewDidLoad];
    
    // 給系統(tǒng) NSObject 類動態(tài)添加屬性 name
    NSObject *object = [[NSObject alloc] init];
    object.name = @"myName";
}

@end
// 定義關(guān)聯(lián)的key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name{
    // 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name{
    // @param: 給那個對象添加關(guān)聯(lián)
    // @param: 關(guān)聯(lián)的 key ,通過這個 key 獲取
    // @param: 關(guān)聯(lián)的 value
    // @param: 關(guān)聯(lián)策略
    objc_setAssociatedObject(self, &key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

斷開關(guān)聯(lián)

1. 單獨斷開某 key 的關(guān)聯(lián)
objc_setAssociatedObject(self, &key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

2. 斷開對象的所有關(guān)聯(lián)
objc_removeAssociatedObjects(self)

字典轉(zhuǎn)模型

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict{
    id objc = [[self alloc] init];
    // 獲取類中的所有成員屬性
    unsigned int count;
    Ivar *ivarList = class_copyIvarList(self, &count);
    
    for (int i = 0; i < count ; i++) {
        Ivar ivar = ivarList[i];
        // 獲取屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *key = [name substringFromIndex:1];
        // 根據(jù)成員屬性名去字典中查找對應(yīng)的value
        id value = dict[key];
        
        // 二級轉(zhuǎn)換
        if ([value isKindOfClass:[NSDictionary class]]) {
            // 獲取成員屬性類型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            NSRange range = [type rangeOfString:@"\""];
            type = [type substringFromIndex:range.location+range.length];
            range = [type rangeOfString:@"\""];
            type = [type substringToIndex:range.location];
            // 根據(jù)字典串類名生成類對象
            Class modelClass = NSClassFromString(type);
            if (modelClass) {
                value = [modelClass modelWithDict:value];
            }
        }
        
        // 三級轉(zhuǎn)換
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應(yīng)類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self instancesRespondToSelector:@selector(arrayContainModelClass)]) {
                // 轉(zhuǎn)換成 id類型,就能調(diào)用任何對象的方法
                id idSelf = self;
                // 獲取數(shù)組中字典對應(yīng)的模型
                NSString *type = [idSelf performSelector:@selector(arrayContainModelClass)][key];
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                for (NSDictionary *dict in value) {
                    id model = [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }
                value = arrM;
            }
        }
        if (value) {
            [objc setObject:value forKey:key];
        }
    }
    return objc;
}

 
 @end

面試題

向nil發(fā)送消息
runtime會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類的方法列表以及其父類方法列表中尋找方法運行。如果對象為nil,在尋找對象的isa指針時就是0地址,直接返回。

runtime找不到方法時會調(diào)用
resolveInstanceMethod
forwardingTargetForSelector,將無法處理的selector轉(zhuǎn)發(fā)給其他對象

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

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

  • objc_getAssociatedObject返回與給定鍵的特定對象關(guān)聯(lián)的值。ID objc_getAssoci...
    有一種再見叫青春閱讀 1,761評論 0 7
  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,812評論 7 64
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,892評論 33 466
  • Runtime是什么 Runtime 又叫運行時,是一套底層的 C 語言 API,其為 iOS 內(nèi)部的核心之一,我...
    SuAdrenine閱讀 983評論 0 3
  • 曾幾何時,不再渴望周五。 可最近這段時間,特別渴望周五。 是弦繃得太緊了嗎? 不知道!
    向著太陽奔跑的石頭閱讀 159評論 0 0

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