iOS Runtime 應(yīng)用實(shí)踐

美女鎮(zhèn)樓圖.png

Objective-C 擴(kuò)展了 C 語(yǔ)言屉凯,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制博烂。而這個(gè)擴(kuò)展的核心是一個(gè)用 C 和 編譯語(yǔ)言 寫(xiě)的 Runtime 庫(kù)持痰。它是 Objective-C 面向?qū)ο蠛蛣?dòng)態(tài)機(jī)制的基石蕴坪。學(xué)會(huì)使用Runtime api ,在iOS開(kāi)發(fā)中實(shí)現(xiàn)更多的騷操作待牵。

Runtime介紹

Runtime其實(shí)有兩個(gè)版本: “modern” 和 “legacy”馒过。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行 (Modern) 版的 Runtime 系統(tǒng)坚俗,只能運(yùn)行在 iOSmacOS 10.5 之后的 64 位程序中禽作。而 macOS 較老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系統(tǒng)尸昧。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類旷偿,而現(xiàn)行版就不需要烹俗。

高級(jí)編程語(yǔ)言想要成為可執(zhí)行文件需要先編譯為匯編語(yǔ)言再匯編為機(jī)器語(yǔ)言碍沐,機(jī)器語(yǔ)言也是計(jì)算機(jī)能夠識(shí)別的唯一語(yǔ)言,但是OC并不能直接編譯為匯編語(yǔ)言衷蜓,而是要先轉(zhuǎn)寫(xiě)為純C語(yǔ)言再進(jìn)行編譯和匯編的操作,從OCC語(yǔ)言的過(guò)渡就是由runtime來(lái)實(shí)現(xiàn)的尘喝。然而我們使用OC進(jìn)行面向?qū)ο箝_(kāi)發(fā)磁浇,而C語(yǔ)言更多的是面向過(guò)程開(kāi)發(fā),這就需要將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^(guò)程的結(jié)構(gòu)體朽褪。

閱讀使用官方Api置吓,解決項(xiàng)目各種騷需求。

Runtime應(yīng)用

Runtime提供了很多api缔赠,功能強(qiáng)大衍锚,應(yīng)用場(chǎng)景非常多,下面主要介紹3中應(yīng)用場(chǎng)景嗤堰。

  • 關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性戴质;
  • 方法魔法(Method Swizzling)方法替換;
  • 實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換踢匣;

關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性

我們都是知道分類(category)中是不能自定義屬性和變量的告匠。但通過(guò)Runtime的關(guān)聯(lián)對(duì)象方法,就可以給分類添加屬性离唬。

關(guān)聯(lián)對(duì)象Runtime提供餓了下面幾個(gè)api:

//關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關(guān)聯(lián)的對(duì)象
id objc_getAssociatedObject(id object, const void *key)
//移除關(guān)聯(lián)的對(duì)象
void objc_removeAssociatedObjects(id object)

參數(shù)解釋

id object:被關(guān)聯(lián)的對(duì)象
const void *key:關(guān)聯(lián)的key后专,要求唯一
id value:關(guān)聯(lián)的對(duì)象
objc_AssociationPolicy policy:內(nèi)存管理的策略

內(nèi)存管理策略枚舉

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

想要使用 runtime 的 api 需要引入#import "objc/runtime.h"頭文件。
下面實(shí)現(xiàn)一個(gè) UIViewControllerCategory 添加自定義屬性NSString * newTitle输莺。

ViewController+Property.h

@interface ViewController (Property)

@property (nonatomic , strong) NSString *newTitle;

@end
--------------------------------------------------------------------------
ViewController+Property.m

#import "ViewController+Property.h"
#import "objc/runtime.h"

static char kNewTitleKey;

@implementation ViewController (Property)
- (void)setNewTitle:(NSString *)newTitle {

   objc_setAssociatedObject(self, &kNewTitleKey, newTitle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


- (NSString*)newTitle {

   return objc_getAssociatedObject(self, &kNewTitleKey);
}

@end

viewController 中的操作

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self testCategoryProperty];
}

- (void)testCategoryProperty {
    
    self.newTitle = @"FK";
    NSLog(@"%@",self.newTitle);
    NSLog(@"運(yùn)行成功");
}

如果不使用runtime中的關(guān)聯(lián)api對(duì)newTitle進(jìn)行關(guān)聯(lián)戚哎,運(yùn)行后會(huì)閃退,并顯示如下報(bào)錯(cuò):


Category 屬性取值賦值報(bào)錯(cuò).png

關(guān)聯(lián)成功后嫂用,newTitle setter getter 方法使用正常型凳。

Category 屬性取值賦值正常.png

成功為分類設(shè)置自定義屬性。

方法魔法(Method Swizzling)方法替換

曾經(jīng)在了解 +(void)load 方法時(shí)尸折,也有了解過(guò)一下 Method Swizzling 啰脚,今天針對(duì)方法魔法進(jìn)行實(shí)踐操作。

實(shí)現(xiàn)方法替換的核心:

//方法實(shí)現(xiàn)替換api
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

下面實(shí)現(xiàn)一個(gè)替換 ViewControllerviewDidLoad 方法的例子实夹。

@implementation ViewController

+ (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(FKViewdidLoad);

        Method originalMethod = class_getInstanceMethod(class,originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);

        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSLog(@"viewDidLoad");
}

- (void)FKViewdidLoad {
    
    NSLog(@"FKViewdidLoad");
}

@end

最后的打印結(jié)果


犯法替換結(jié)果.png

viewDidLoadFKViewdidLoad 兩個(gè)方法替換成功橄浓。

此處值得注意的是 swizzling 應(yīng)該只在+load中完成。 在 Objective-C 的運(yùn)行時(shí)中亮航,每個(gè)類有兩個(gè)方法都會(huì)自動(dòng)調(diào)用荸实。+load 是在一個(gè)類被初始裝載時(shí)調(diào)用,+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的缴淋。兩個(gè)方法都是可選的准给,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用泄朴。
swizzling應(yīng)該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài),所以我們需要確保每個(gè)預(yù)防措施在運(yùn)行時(shí)都是可用的露氮。原子操作就是這樣一個(gè)用于確保代碼只會(huì)被執(zhí)行一次的預(yù)防措施祖灰,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatchdispatch_once滿足了所需要的需求畔规,并且應(yīng)該被當(dāng)做使用swizzling的初始化單例方法的標(biāo)準(zhǔn)局扶。

實(shí)現(xiàn)圖解.png

實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換

原理描述:用runtime提供的函數(shù)遍歷Model自身所有屬性,如果屬性在json中有對(duì)應(yīng)的值叁扫,則將其賦值三妈。 核心api:

//遍歷類型中所有屬性
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

NSObject 中添加方法:

@implementation NSObject (RuntimeModel)

- (void)initWithDict:(NSDictionary *)dict {
    
    //(1)獲取類的屬性及屬性對(duì)應(yīng)的類型
    NSMutableArray * keys = [NSMutableArray array];
    NSMutableArray * attributes = [NSMutableArray array];
    /*
     * 例子
     * name = value3 attribute = T@"NSString",C,N,V_value3
     * name = value4 attribute = T^i,N,V_value4
     */
    unsigned int outCount;
    objc_property_t * properties = class_copyPropertyList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = properties[i];
        //通過(guò)property_getName函數(shù)獲得屬性的名字
        NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        [keys addObject:propertyName];
        //通過(guò)property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
        NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
        [attributes addObject:propertyAttribute];
    }
    //立即釋放properties指向的內(nèi)存
    free(properties);
    
    //(2)根據(jù)類型給屬性賦值
    for (NSString * key in keys) {
        if ([dict valueForKey:key] == nil) continue;
        [self setValue:[dict valueForKey:key] forKey:key];
    }
}

@end

viewController 中的操作

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.runtimeM initWithDict:@{@"time" : @"5點(diǎn)" , @"name" : @"FK" , @"sex" : @"Man"}];
    NSLog(@"%@ - %@ - %@",self.runtimeM.time , self.runtimeM.name , self.runtimeM.sex);
}

打印結(jié)果如下:


屏幕快照 2018-09-09 下午5.52.33.png

成功將字典類型轉(zhuǎn)換為模型類型。

拓展
使用 class_copyPropertyList 遍歷方法莫绣,可以遍歷對(duì)象中所有屬性畴蒲,并且可以把所有屬性制空。

總結(jié)

到這里对室,本文想介紹的 runtime 3個(gè)應(yīng)用場(chǎng)景就介紹完了模燥。本文已貼出Demo中核心代碼,如果想閱讀完整代碼掩宜,可以下載Demo查看涧窒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锭亏,隨后出現(xiàn)的幾起案子纠吴,更是在濱河造成了極大的恐慌,老刑警劉巖慧瘤,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戴已,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡锅减,警方通過(guò)查閱死者的電腦和手機(jī)糖儡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怔匣,“玉大人握联,你說(shuō)我怎么就攤上這事∶柯鳎” “怎么了金闽?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)剿骨。 經(jīng)常有香客問(wèn)我代芜,道長(zhǎng),這世上最難降的妖魔是什么浓利? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任挤庇,我火速辦了婚禮钞速,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嫡秕。我一直安慰自己渴语,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布昆咽。 她就那樣靜靜地躺著遵班,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潮改。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天腹暖,我揣著相機(jī)與錄音汇在,去河邊找鬼。 笑死脏答,一個(gè)胖子當(dāng)著我的面吹牛糕殉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播殖告,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阿蝶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了黄绩?” 一聲冷哼從身側(cè)響起羡洁,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎爽丹,沒(méi)想到半個(gè)月后筑煮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粤蝎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年真仲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初澎。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秸应,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碑宴,到底是詐尸還是另有隱情软啼,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布延柠,位于F島的核電站焰宣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捕仔。R本人自食惡果不足惜匕积,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一盈罐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧闪唆,春花似錦盅粪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至帆调,卻和暖如春奠骄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背番刊。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工含鳞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芹务。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓蝉绷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親枣抱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子熔吗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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