runtime往事一二

runtime 的幾個(gè)應(yīng)用場(chǎng)景:

  • 消息轉(zhuǎn)發(fā)
  • method siwizzling
  • 歸解檔瀑踢、模式互轉(zhuǎn)
  • 自定義KVO

消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)機(jī)制的流程:

  • 動(dòng)態(tài)方法解析
  • 快速轉(zhuǎn)發(fā)
  • 慢速轉(zhuǎn)發(fā)(也就是完整的消息轉(zhuǎn)發(fā)流程)
消息轉(zhuǎn)發(fā)機(jī)制.png

動(dòng)態(tài)方法解析

給person類的.h添加一個(gè)方法yy_sendMessage耻矮,但是沒有實(shí)現(xiàn)善镰,
運(yùn)行[[Person new] yy_sendMessage:@"yy_msg"];這個(gè)方法會(huì)報(bào)錯(cuò):Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person yy_sendMessage:]: unrecognized selector sent to instance 0x6000025e0b50'

動(dòng)態(tài)方法解析可以實(shí)現(xiàn)動(dòng)態(tài)的為當(dāng)前類添加方法:

@implementation Person

void yy_sendMessage(id self, SEL _cmd, NSString *msg)
{
    NSLog(@"person--%@",msg);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"yy_sendMessage:"]) {
        BOOL flag = class_addMethod(self, sel, (IMP)yy_sendMessage, "v@:@");
        return flag;
    }
    return NO;
}

@end

再次運(yùn)行嗦枢,程序正常并打印消息

person--yy_msg

快速轉(zhuǎn)發(fā)

如果Person類沒有實(shí)現(xiàn)resolveInstanceMethod:方法,或者返回NO,可以通過快速轉(zhuǎn)發(fā)的方式,把消息發(fā)給別的對(duì)象航瞭,這里把消息轉(zhuǎn)給Car,前提是Car要有實(shí)現(xiàn)這個(gè)方法

@interface Car : NSObject
- (void)yy_sendMessage:(NSString *)msg;
@end

@implementation Car

- (void)yy_sendMessage:(NSString *)msg
{
    NSLog(@"car--%@",msg);
}

@end
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"yy_sendMessage:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    Car *car = [Car new];
    if ([car respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:car];
    }else{
        [super forwardInvocation:anInvocation];
    }
}
@end

打印結(jié)果:

car--yy_msg

重寫doesNotRecognizeSelector:

如果forwardInvocation:沒有找到合適的消息處理者坦辟,重寫doesNotRecognizeSelector:可以讓app繼續(xù)運(yùn)行:

- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"找不到方法刊侯,app繼續(xù)運(yùn)行");
}

method siwizzling

方法交換,就是把我們的方法O和系統(tǒng)的方法S交換锉走,在執(zhí)行S方法的時(shí)候滨彻,及時(shí)運(yùn)行的是O方法的邏輯。

@interface UITableView (YYDefaultDisplayView)
@property (nonatomic, strong) UILabel *nodataTipsView; 
@end
    
@implementation UITableView (YYDefaultDisplayView)

+ (void)load{
    static dispatch_once_t onceToken;
    // 確保只會(huì)執(zhí)行一次 (防止調(diào)皮的童鞋手動(dòng)再調(diào)用load方法)
    dispatch_once(&onceToken, ^{
        Method originMethod = class_getInstanceMethod(self, @selector(reloadData));
        Method swizzlingMethod = class_getInstanceMethod(self, @selector(yy_reloadData));
        
        // 互換方法
        method_exchangeImplementations(originMethod, swizzlingMethod);
    });
}

// 臥底
- (void)yy_reloadData{
    // yy_reloadData實(shí)際指向reloadData挪蹭,相當(dāng)于調(diào)用系統(tǒng)的方法
    [self yy_reloadData];
    
    // 這里添加我們想要做的事情
    [self showDefaultVeiw];
}

- (void)showDefaultVeiw{
    id<UITableViewDataSource> dataSource = self.dataSource;
    NSInteger section = [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)] ? [dataSource numberOfSectionsInTableView:self] : 1;
    NSInteger row = 0;
    for (NSInteger i= 0; i < section; i ++) {
        row = [dataSource tableView:self numberOfRowsInSection:section];
    }
    
    if (row == 0) {
        self.nodataTipsView = [[UILabel alloc] init];
        self.nodataTipsView.text  = @"暫時(shí)無數(shù)據(jù)亭饵,再刷新試試?";
        self.nodataTipsView.backgroundColor = UIColor.yellowColor;
        self.nodataTipsView.textAlignment = NSTextAlignmentCenter;
        self.nodataTipsView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
        [self addSubview:self.nodataTipsView];
    }else{
        self.nodataTipsView.hidden = YES;
    }
}

#pragma mark - getting && setting
- (void)setNodataTipsView:(UILabel *)nodataTipsView
{
    objc_setAssociatedObject(self, @selector(nodataTipsView), nodataTipsView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UILabel *)nodataTipsView
{
    return objc_getAssociatedObject(self, _cmd);
}

@end

這里有幾個(gè)值得思考的點(diǎn):

  1. 為什么選擇在laod方法里交換方法梁厉?
  2. 為什么要確保執(zhí)行一次辜羊?
  3. yy_reloadData里又調(diào)用了yy_reloadData,會(huì)造成死循環(huán)麼词顾?
  4. set方法和get方法里八秃,使用了_cmd作為關(guān)聯(lián)key,為什么肉盹?(都是const void *key類型)

詳情見這里

歸解檔/模式互轉(zhuǎn)

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age; 

- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)convertModelToDictionary;

@end
#import "Person.h"
#import <objc/message.h>

@implementation Person

// 字典->模型
- (instancetype)initWithDictionary:(NSDictionary *)dictionary{
    self = [super init];
    if (self) {
        for (NSString *key in dictionary.allKeys) {
            
            // 通過key構(gòu)建set方法
            NSString *methodName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
            SEL sel = NSSelectorFromString(methodName);
            if (sel) {
                /*
                 指針函數(shù)的形式:
                 returnType (*functionName) (param1, param2, ...)
                 void (*)(id, SEL, id)
                 
                 使用指針調(diào)用函數(shù):
                 (returnType (*functionName) (param1, param2, ...))
                 */
                NSString *value = dictionary[key];
                ((void (*)(id, SEL, id))objc_msgSend)(self, sel, value);
            }
        }
    }
    return self;
}

// 模型->字典
- (NSDictionary *)convertModelToDictionary{
    
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(self.class, &count);
    
    if (count == 0) {
        free(properties);
        return nil;
    }
    
    NSMutableDictionary *dic = NSMutableDictionary.dictionary;
    for (int i = 0; i < count; i ++) {
        const char *propertyName = property_getName(properties[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        SEL sel = NSSelectorFromString(name);
        if (sel) {
            // 通過get方法獲取value
            NSString *value =  ((id (*)(id, SEL))objc_msgSend)(self, sel);
            
            if (value) {
                dic[name] = value;
            }else{
                dic[name] = @"";
            }
        }
    }
    
    // 釋放
    free(properties);
    return dic;
}
@end
// 測(cè)試:
NSDictionary *dic = @{@"name": @"iO骨灰級(jí)菜鳥", @"age": @(18)};
    Person *p = [[Person alloc] initWithDictionary:dic];
    
    NSLog(@"name: %@  age:%@",p.name, p.age);
    
    NSDictionary *dic2 = [p convertModelToDictionary];
    NSLog(@"dic2:%@",dic2);
 
 // 打游羟:
name: iO骨灰級(jí)菜鳥  age:18
dic2:{
    age = 18;
    name = "iO\U9aa8\U7070\U7ea7\U83dc\U9e1f";
}

person類提供了字典和模型互轉(zhuǎn)的方法,分別對(duì)應(yīng)屬性的set方法和get方法上忍。核心代碼是通過函數(shù)指針的方式運(yùn)行objc_sendMsg()方法骤肛。

字典轉(zhuǎn)模型里,注意方法函數(shù)名字大寫的拼接處理睡雇,改進(jìn)的空間有:

  1. 兼容性處理:判斷參數(shù)是否是字典萌衬,字典多層嵌套等
  2. 性能優(yōu)化:緩存結(jié)果饮醇、使用更底層的函數(shù)以提高性能等

模型轉(zhuǎn)字典里它抱,注意釋放變量。

自定義KVO

KVO的底層實(shí)現(xiàn)也是基于runtime朴艰。當(dāng)一個(gè)對(duì)象Obj被監(jiān)聽的時(shí)候观蓄,系統(tǒng)會(huì)為Obj創(chuàng)建一個(gè)子類,并加上一個(gè)前綴NSKVONotifing_Obj祠墅。接著把isa指針也改為指向新的子類侮穿,所以蘋果說不要通過isa來判斷這個(gè)類的真實(shí)類型。同時(shí)毁嗦,Apple 還重寫了 -class 方法亲茅,企圖欺騙我們這個(gè)類沒有變,就是原本那個(gè)類。當(dāng)然克锣,還要重寫set方法茵肃,在set方法里實(shí)現(xiàn)通知的邏輯。具體實(shí)現(xiàn)可以看我的這篇博文

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袭祟,一起剝皮案震驚了整個(gè)濱河市验残,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巾乳,老刑警劉巖您没,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胆绊,居然都是意外死亡氨鹏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門压状,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喻犁,“玉大人,你說我怎么就攤上這事何缓≈。” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碌廓,是天一觀的道長传轰。 經(jīng)常有香客問我,道長谷婆,這世上最難降的妖魔是什么慨蛙? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮纪挎,結(jié)果婚禮上期贫,老公的妹妹穿的比我還像新娘。我一直安慰自己异袄,他們只是感情好通砍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烤蜕,像睡著了一般封孙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讽营,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天虎忌,我揣著相機(jī)與錄音,去河邊找鬼橱鹏。 笑死膜蠢,一個(gè)胖子當(dāng)著我的面吹牛堪藐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挑围,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼庶橱,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了贪惹?” 一聲冷哼從身側(cè)響起苏章,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奏瞬,沒想到半個(gè)月后枫绅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡硼端,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年并淋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珍昨。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡县耽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镣典,到底是詐尸還是另有隱情兔毙,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布兄春,位于F島的核電站澎剥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赶舆。R本人自食惡果不足惜哑姚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芜茵。 院中可真熱鬧叙量,春花似錦、人聲如沸九串。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒸辆。三九已至征炼,卻和暖如春析既,著一層夾襖步出監(jiān)牢的瞬間躬贡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工眼坏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拂玻,地道東北人酸些。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像檐蚜,于是被迫代替她去往敵國和親魄懂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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