對(duì)runtime的總結(jié):讓你會(huì)用Runtime

導(dǎo)語(yǔ)
Runtime,簡(jiǎn)稱(chēng)運(yùn)行時(shí),就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制,是一套底層的純C語(yǔ)言的API,我們平時(shí)所編寫(xiě)的OC代碼,在程序的運(yùn)行過(guò)程中都轉(zhuǎn)成了runtime的代碼,平時(shí)調(diào)方法都是轉(zhuǎn)成了objc_msgSend函數(shù)。大家應(yīng)該都聽(tīng)過(guò)或者用過(guò)一個(gè)叼庫(kù)IQKeyboard,它只需導(dǎo)入進(jìn)工程,不需要寫(xiě)一行代碼就能實(shí)現(xiàn)功能,其實(shí)就是用的runtime機(jī)制實(shí)現(xiàn)的,俗稱(chēng)黑魔法。

runtime的作用:

Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性,這些深層次的知識(shí)雖然平時(shí)寫(xiě)代碼用的少一些,但是卻是每個(gè) Objc 程序員需要了解的。

  • 在程序運(yùn)行過(guò)程中,動(dòng)態(tài)的創(chuàng)建一個(gè)類(lèi)(KVO實(shí)現(xiàn)原理)以及動(dòng)態(tài)的為每個(gè)類(lèi)添加或修改屬性、方法
  • 遍歷一個(gè)類(lèi)中所有的成員變量以及方法(NSCoding的自動(dòng)解檔和歸檔、MJExtension的實(shí)現(xiàn))
  • 交換兩個(gè)方法的實(shí)現(xiàn)(Swizzle黑魔法)
  • ...
KVO實(shí)現(xiàn)原理:

當(dāng)你第一次觀察某個(gè)對(duì)象時(shí),runtime會(huì)創(chuàng)建一個(gè)新的類(lèi)NSKVONOtifying_class,該類(lèi)繼承自原先的class。在這個(gè)新的派生類(lèi)中,它重寫(xiě)了所有被觀察屬性的setter方法,然后將對(duì)象的isa指針指向新創(chuàng)建的NSKVONOtifying_class(這個(gè)指針告訴Objective-C的runtime某個(gè)對(duì)象到底是哪種類(lèi)型的對(duì)象)。所以該對(duì)象神奇地變成了新的子類(lèi)的實(shí)例。當(dāng)對(duì)象的屬性發(fā)生改變時(shí),會(huì)觸發(fā)setter方法,但這個(gè)方法已經(jīng)被重寫(xiě)了,并且在方法的內(nèi)部添加了發(fā)送通知機(jī)制,實(shí)現(xiàn)了自動(dòng)觸發(fā)通知機(jī)制,這就是KVO實(shí)現(xiàn)的原理。

交換兩個(gè)方法的實(shí)現(xiàn)

先看下面例子:
創(chuàng)建一個(gè)Student類(lèi),實(shí)現(xiàn)兩個(gè)類(lèi)方法:studyJava和studyC
使用runtime交換兩個(gè)方法的實(shí)現(xiàn):

    #import<objc/runtime.h>//別忘加運(yùn)行時(shí)頭文件
    //獲取class中的類(lèi)方法
    Method studyJava = class_getClassMethod([Student class], @selector(studyJava));
    Method studyC = class_getClassMethod([Student class], @selector(studyC));
    //交換方法的實(shí)現(xiàn)
    method_exchangeImplementations(studyJava, studyC);
    
    [Student studyC];
    [Student studyJava];

運(yùn)行后會(huì)發(fā)現(xiàn):讓他先學(xué)C再學(xué)Java就是不聽(tīng),結(jié)果是先學(xué)Java。

看到這你會(huì)覺(jué)得并沒(méi)有什么卵用,是的,這樣寫(xiě)意義不大,但我們換個(gè)角度想,如果交換的是系統(tǒng)的方法呢?假如我們要統(tǒng)一監(jiān)聽(tīng)控制器何時(shí)被銷(xiāo)毀,用runtime可以讓我們只寫(xiě)一次代碼,看例子:
首先新建一個(gè)UIViewController的分類(lèi),然后在里面寫(xiě)上代碼:

#import "UIViewController+Extension.h"
#import <objc/runtime.h>

@implementation UIViewController (Extension)
//將交換方法寫(xiě)在load方法中,使得在該類(lèi)被加載如內(nèi)存中就調(diào)用該方法以交換方法
+ (void)load {
    //class_getInstanceMethod :獲取實(shí)例方法
    Method dealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
    Method jyh_dealloc = class_getInstanceMethod(self, @selector(jyh_dealloc));
    method_exchangeImplementations(dealloc, jyh_dealloc);
}

//自己實(shí)現(xiàn)的dealloc方法,監(jiān)聽(tīng)銷(xiāo)毀
- (void)jyh_dealloc {
    NSLog(@"%@ is dealloc", self);
    [self jyh_dealloc];//調(diào)用原來(lái)的dealloc方法
}

@end

這樣便實(shí)現(xiàn)了所用控制器銷(xiāo)毀的監(jiān)聽(tīng)。以上的例子只是為了說(shuō)明runtime的作用,它可以有很多擴(kuò)展,比如:將[UIImage imageNamed:]換成自己的方法,在方法中加入對(duì)圖片名稱(chēng)的操作,就可以實(shí)現(xiàn)換膚或適配等功能;可以替換viewDidLoad方法實(shí)現(xiàn)用戶(hù)進(jìn)入頁(yè)面的統(tǒng)計(jì),不用每個(gè)控制器都復(fù)制粘貼同樣的代碼(或許你會(huì)想我們可以使用 OOP 的特性之一,繼承的方式來(lái)解決這個(gè)問(wèn)題。創(chuàng)建一個(gè)基類(lèi),在這個(gè)基類(lèi)中添加統(tǒng)計(jì)方法,其他類(lèi)都繼承自這個(gè)基類(lèi)。然而,這種方式修改還是很大,而且定制性很差。以后有新人加入之后,都要囑咐其繼承自這個(gè)基類(lèi),所以這種方式并不可取。)IQKeyboard庫(kù)不需要寫(xiě)一行代碼就能實(shí)現(xiàn)功能,就是通過(guò)這一原理實(shí)現(xiàn)的。

遍歷類(lèi)中的所有成員變量

有時(shí)候我們要對(duì)一些信息進(jìn)行歸檔,如用戶(hù)信息類(lèi)UserInfo,這將需要重寫(xiě)initWithCoder和encodeWithCoder方法,并對(duì)每個(gè)屬性進(jìn)行encode和decode操作。那么問(wèn)題來(lái)了:當(dāng)屬性只有幾個(gè)的時(shí)候可以輕松寫(xiě)完,如果有幾十個(gè)屬性呢?我們可以用runtime提供的函數(shù)遍歷Model自身所有屬性,并對(duì)屬性進(jìn)行encode和decode操作。

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if(self = [super init]) {
        unsigned int outCount;
        //獲得所傳入類(lèi)的成員變量
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
        free(ivars);//別忘記釋放
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar *ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
    free(ivars);
}

同理,字典轉(zhuǎn)模型用的也是這種方法。

訪問(wèn)蘋(píng)果私有屬性

利用runtime獲取屬性名

objc_property_t *properties = class_copyPropertyList([UITextField class], &count);

這樣就可以獲取到UITextField中的所有屬性,包括在頭文件中蘋(píng)果不想讓你看到的。
這有什么用呢?假設(shè)我們有一個(gè)需求,想改變輸入框中占位文字的顏色,用runtime的這個(gè)作用可以讓我們很方便的實(shí)現(xiàn):
先將輸入框所包含的屬性打印出來(lái):

+ (void)getProperties
{
    unsigned int count = 0;
    
    objc_property_t *properties = class_copyPropertyList([UITextField class], &count);
    
    for (int i = 0; i<count; i++) {
        // 取出屬性
        objc_property_t property = properties[i];
        
        // 打印屬性名字
        JYHLog(@"%s   <---->   %s", property_getName(property), property_getAttributes(property));
    }
    
    free(properties);
}

我們可以看到這樣一個(gè)屬性:

查看私有屬性

顧名思義,這就是占位的Label,我們只要利用KVC就可以將其改成我們想要的樣子:

[self setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];

以上只是舉一些例子來(lái)窺探runtime的一些作用,可以有很多擴(kuò)展,還需我們繼續(xù)去學(xué)習(xí)。

如果文章對(duì)你有用的話請(qǐng)點(diǎn)下喜歡或關(guān)注,Thank You!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,762評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,592評(píng)論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂(lè)樂(lè)的簡(jiǎn)書(shū)閱讀 2,154評(píng)論 0 9
  • 對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,732評(píng)論 7 64
  • 今晚我決定偷次懶,為自己偷懶找了好多理由。 1. 簡(jiǎn)書(shū)頭牌彭小六曾經(jīng)在一次寫(xiě)作課上說(shuō)過(guò),寫(xiě)作可以三天打魚(yú)兩天曬網(wǎng)(...
    四眼蝸牛閱讀 312評(píng)論 0 0