1.runtime

runtime 常見作用

1.動態(tài)交換兩個(gè)方法的實(shí)現(xiàn)

2.動態(tài)添加屬性

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

4.發(fā)送消息

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

6.攔截并替換方法

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

該文件的目的是為讀者誰可能有興趣學(xué)習(xí)的Objective-C的運(yùn)行時(shí)友鼻。
因?yàn)檫@不是一個(gè)關(guān)于C文件忧设,它假定了一些以前的熟人與該語言石挂。然而崔泵,它并不必須是一個(gè)廣泛的熟人秒赤。
職能任務(wù)
使用類
class_getName

class_getSuperclass

class_isMetaClass

class_getInstanceSize

class_getInstanceVariable

class_getClassVariable

class_addIvar

class_copyIvarList

class_getIvarLayout

class_setIvarLayout

class_getWeakIvarLayout

class_setWeakIvarLayout

class_getProperty

class_copyPropertyList

class_addMethod

class_getInstanceMethod

class_getClassMethod

class_copyMethodList

class_replaceMethod

class_getMethodImplementation

class_getMethodImplementation_stret

class_respondsToSelector

class_addProtocol

class_addProperty

class_replaceProperty

class_conformsToProtocol

class_copyProtocolList

class_getVersion

class_setVersion

objc_getFutureClass

objc_setFutureClass

class_setSuperclass
已過時(shí)的OS X V10.5

添加類
objc_allocateClassPair

objc_disposeClassPair

objc_registerClassPair

objc_duplicateClass

類的實(shí)例化
class_createInstance

objc_constructInstance

objc_destructInstance

用工作實(shí)例
object_copy

object_dispose

object_setInstanceVariable

object_getInstanceVariable

object_getIndexedIvars

object_getIvar

object_setIvar

object_getClassName

object_getClass

object_setClass

獲取類定義
objc_getClassList

objc_copyClassList

objc_lookUpClass

objc_getClass

objc_getRequiredClass

objc_getMetaClass

使用實(shí)例變量工作
ivar_getName

ivar_getTypeEncoding

ivar_getOffset

聯(lián)想?yún)⒖?br> objc_setAssociatedObject

objc_getAssociatedObject

objc_removeAssociatedObjects

查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父類
    const char *name                        OBJC2_UNAVAILABLE;  // 類名
    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,默認(rèn)為0
    long info                               OBJC2_UNAVAILABLE;  // 類信息憎瘸,供運(yùn)行期使用的一些位標(biāo)識
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實(shí)例變量大小
    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;  // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;

runtime 概念

Objective-C是基于 C 的入篮,它為 C 添加了面向?qū)ο蟮奶匦浴K鼘⒑芏囔o態(tài)語言在編譯和鏈接時(shí)期做的事放到了 runtime 運(yùn)行時(shí)來處理幌甘,可以說runtime是我們 Objective-C 幕后工作者潮售。

runtime(簡稱運(yùn)行時(shí))痊项,是一套 純C(C和匯編寫的) 的API。而 OC 就是運(yùn)行時(shí)機(jī)制酥诽,也就是在運(yùn)行時(shí)候的一些機(jī)制鞍泉,其中最主要的是消息機(jī)制。

對于 C 語言肮帐,函數(shù)的調(diào)用在編譯的時(shí)候會決定調(diào)用哪個(gè)函數(shù)塞弊。

OC的函數(shù)調(diào)用成為消息發(fā)送,屬于動態(tài)調(diào)用過程泪姨。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用饰抒。

事實(shí)證明:在編譯階段肮砾,OC 可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn)袋坑,只要聲明過就不會報(bào)錯(cuò)仗处,只有當(dāng)運(yùn)行的時(shí)候才會報(bào)錯(cuò),這是因?yàn)镺C是運(yùn)行時(shí)動態(tài)調(diào)用的枣宫。而 C 語言調(diào)用未實(shí)現(xiàn)的函數(shù)就會報(bào)錯(cuò)婆誓。

runtime 消息機(jī)制

我們寫 OC 代碼,它在運(yùn)行的時(shí)候也是轉(zhuǎn)換成了runtime方式運(yùn)行的也颤。任何方法調(diào)用本質(zhì):就是發(fā)送一個(gè)消息(用runtime發(fā)送消息洋幻,OC 底層實(shí)現(xiàn)通過runtime實(shí)現(xiàn))。

消息機(jī)制原理:對象根據(jù)方法編號SEL去映射表查找對應(yīng)的方法實(shí)現(xiàn)翅娶。

每一個(gè) OC 的方法文留,底層必然有一個(gè)與之對應(yīng)的runtime方法。

image.png

OC-->runtime

簡單示例:

驗(yàn)證:方法調(diào)用竭沫,是否真的是轉(zhuǎn)換為消息機(jī)制燥翅?

必須要導(dǎo)入頭文件#import

注解1:我們導(dǎo)入系統(tǒng)的頭文件,一般用尖括號蜕提。

注解2:OC 解決消息機(jī)制方法提示步驟【查找build setting-> 搜索msg->objc_msgSend(YES --> NO)】

注解3:最終生成消息機(jī)制,編譯器做的事情森书,最終代碼,需要把當(dāng)前代碼重新編譯谎势,用xcode編譯器凛膏,【clang -rewrite-objc main.m查看最終生成代碼】,示例:cd main.m --> 輸入前面指令脏榆,就會生成 .opp文件(C++代碼)

注解4:這里一般不會直接導(dǎo)入


image.png

message.h

示例代碼:OC 方法-->runtime 方法

說明: eat(無參) 和 run(有參) 是 Person模型類中的私有方法「可以幫我調(diào)用私有方法」译柏;

// Person *p = [Person alloc];

// 底層的實(shí)際寫法

Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));

// p = [p init];

p = objc_msgSend(p, sel_registerName("init"));

// 調(diào)用對象方法(本質(zhì):讓對象發(fā)送消息)

//[p eat];

// 本質(zhì):讓類對象發(fā)送消息

objc_msgSend(p, @selector(eat));

objc_msgSend([Person class], @selector(run:),20);

//--------------------------- <#我是分割線#> ------------------------------//
// 也許下面這種好理解一點(diǎn)

// id objc = [NSObject alloc];

id objc = objc_msgSend([NSObject class], @selector(alloc));

// objc = [objc init];

objc = objc_msgSend(objc, @selector(init));

runtime 方法調(diào)用流程「消息機(jī)制」

面試:消息機(jī)制方法調(diào)用流程
怎么去調(diào)用eat方法,對象方法:(保存到類對象的方法列表) 姐霍,類方法:(保存到元類(Meta Class)中方法列表)鄙麦。

1.OC 在向一個(gè)對象發(fā)送消息時(shí)典唇,runtime庫會根據(jù)對象的isa指針找到該對象對應(yīng)的類或其父類中查找方法。胯府。

2.注冊方法編號(這里用方法編號的好處介衔,可以快速查找)。

3.根據(jù)方法編號去查找對應(yīng)方法骂因。

4.找到只是最終函數(shù)實(shí)現(xiàn)地址炎咖,根據(jù)地址去方法區(qū)調(diào)用對應(yīng)函數(shù)。

補(bǔ)充:一個(gè)objc對象的isa的指針指向什么寒波?有什么作用乘盼?
每一個(gè)對象內(nèi)部都有一個(gè)isa指針,這個(gè)指針是指向它的真實(shí)類型俄烁,根據(jù)這個(gè)指針就能知道將來調(diào)用哪個(gè)類的方法绸栅。

runtime 常見作用

1.動態(tài)交換兩個(gè)方法的實(shí)現(xiàn)

2.動態(tài)添加屬性

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

4.發(fā)送消息

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

6.攔截并替換方法

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

runtime 常用開發(fā)應(yīng)用場景「工作掌握」

1.runtime 交換方法
應(yīng)用場景:當(dāng)?shù)谌娇蚣?或者 系統(tǒng)原生方法功能不能滿足我們的時(shí)候,我們可以在保持系統(tǒng)原有方法功能的基礎(chǔ)上页屠,添加額外的功能粹胯。
需求:加載一張圖片直接用[UIImage imageNamed:@"image"];是無法知道到底有沒有加載成功。給系統(tǒng)的imageNamed添加額外功能(是否加載圖片成功)辰企。

方案一:繼承系統(tǒng)的類风纠,重寫方法.(弊端:每次使用都需要導(dǎo)入)

方案二:使用 runtime,交換方法.

實(shí)現(xiàn)步驟:

1.給系統(tǒng)的方法添加分類

2.自己實(shí)現(xiàn)一個(gè)帶有擴(kuò)展功能的方法

3.交換方法,只需要交換一次,

案例代碼:方法+調(diào)用+打印輸出

- (void)viewDidLoad {

[super viewDidLoad];

// 方案一:先搞個(gè)分類牢贸,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;

// 方案二:交換 imageNamed 和 ln_imageNamed 的實(shí)現(xiàn)竹观,就能調(diào)用 imageNamed,間接調(diào)用 ln_imageNamed 的實(shí)現(xiàn)潜索。

UIImage *image = [UIImage imageNamed:@"123"];

}

#import

@implementation UIImage (Image)

/**

load方法: 把類加載進(jìn)內(nèi)存的時(shí)候調(diào)用,只會調(diào)用一次

方法應(yīng)先交換栈幸,再去調(diào)用

*/
+ (void)load {

// 1.獲取 imageNamed方法地址

// class_getClassMethod(獲取某個(gè)類的方法)

Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));

// 2.獲取 ln_imageNamed方法地址

Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));

// 3.交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式;「method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)」

method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);

}

/**
看清楚下面是不會有死循環(huán)的

調(diào)用 imageNamed => ln_imageNamed

調(diào)用 ln_imageNamed => imageNamed

*/

// 加載圖片 且 帶判斷是否加載成功

+ (UIImage *)ln_imageNamed:(NSString *)name {

UIImage *image = [UIImage ln_imageNamed:name];

if (image) {

NSLog(@"runtime添加額外功能--加載成功");

} else {

NSLog(@"runtime添加額外功能--加載失敗");

}

return image;

}

/**

不能在分類中重寫系統(tǒng)方法imageNamed帮辟,因?yàn)闀严到y(tǒng)的功能給覆蓋掉速址,而且分類中不能調(diào)用super

所以第二步,我們要 自己實(shí)現(xiàn)一個(gè)帶有擴(kuò)展功能的方法.

+ (UIImage *)imageNamed:(NSString *)name {

}

*/

@end

// 打印輸出

2016-02-17 17:52:14.693 runtime[12761:543574] runtime添加額外功能--加載成功

總結(jié):我們所做的就是在方法調(diào)用流程第三步的時(shí)候由驹,交換兩個(gè)方法地址指向芍锚。而且我們改變指向要在系統(tǒng)的imageNamed:方法調(diào)用前,所以將代碼寫在了分類的load方法里蔓榄。最后當(dāng)運(yùn)行的時(shí)候系統(tǒng)的方法就會去找我們的方法的實(shí)現(xiàn)并炮。

2.runtime 給分類動態(tài)添加屬性
原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián)甥郑,并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間逃魄。
應(yīng)用場景:給系統(tǒng)的類添加屬性的時(shí)候,可以使用runtime動態(tài)添加屬性方法。
注解:系統(tǒng)NSObject添加一個(gè)分類澜搅,我們知道在分類中是不能夠添加成員屬性的伍俘,雖然我們用了@property邪锌,但是僅僅會自動生成get和set方法的聲明,并沒有帶下劃線的屬性和方法實(shí)現(xiàn)生成癌瘾。但是我們可以通過runtime就可以做到給它方法的實(shí)現(xiàn)觅丰。

需求:給系統(tǒng) NSObject 類動態(tài)添加屬性name字符串。

案例代碼:方法+調(diào)用+打印

@interface NSObject (Property)

// @property分類:只會生成get,set方法聲明,不會生成實(shí)現(xiàn),也不會生成下劃線成員屬性

@property NSString *name;

@property NSString *height;

@end

@implementation NSObject (Property)

- (void)setName:(NSString *)name {

// objc_setAssociatedObject(將某個(gè)值跟某個(gè)對象關(guān)聯(lián)起來妨退,將某個(gè)值存儲到某個(gè)對象中)

// object:給哪個(gè)對象添加屬性

// key:屬性名稱

// value:屬性值

// policy:保存策略

objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (NSString *)name {

return objc_getAssociatedObject(self, @"name");

}

// 調(diào)用

NSObject *objc = [[NSObject alloc] init];

objc.name = @"123";

NSLog(@"runtime動態(tài)添加屬性name==%@",objc.name);

// 打印輸出

2016-02-17 19:37:10.530 runtime[12761:543574] runtime動態(tài)添加屬性--name == 123

總結(jié):其實(shí)妇萄,給屬性賦值的本質(zhì),就是讓屬性與一個(gè)對象產(chǎn)生關(guān)聯(lián)咬荷,所以要給NSObject的分類的name屬性賦值就是讓name和NSObject產(chǎn)生關(guān)聯(lián)冠句,而runtime可以做到這一點(diǎn)。

3.runtime 字典轉(zhuǎn)模型

字典轉(zhuǎn)模型的方式:

一個(gè)一個(gè)的給模型屬性賦值(初學(xué)者)幸乒。

字典轉(zhuǎn)模型KVC實(shí)現(xiàn)

KVC 字典轉(zhuǎn)模型弊端:必須保證懦底,模型中的屬性和字典中的key一一對應(yīng)。

如果不一致逝变,就會調(diào)用[ setValue:forUndefinedKey:]報(bào)key找不到的錯(cuò)。

分析:模型中的屬性和字典的key不一一對應(yīng)奋构,系統(tǒng)就會調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)壳影。

解決:重寫對象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋弥臼,就能繼續(xù)使用KVC宴咧,字典轉(zhuǎn)模型了。

字典轉(zhuǎn)模型Runtime實(shí)現(xiàn)

思路:利用運(yùn)行時(shí)径缅,遍歷模型中所有屬性掺栅,根據(jù)模型的屬性名,去字典中查找key纳猪,取出對應(yīng)的值氧卧,給模型的屬性賦值(從提醒:字典中取值,不一定要全部取出來)氏堤。

考慮情況

1.當(dāng)字典的key和模型的屬性匹配不上沙绝。

2.模型中嵌套模型(模型屬性是另外一個(gè)模型對象)。

3.數(shù)組中裝著模型(模型的屬性是一個(gè)數(shù)組鼠锈,數(shù)組中是一個(gè)個(gè)模型對象)闪檬。

注解:根據(jù)上面的三種特殊情況,先是字典的key和模型的屬性不對應(yīng)的情況购笆。不對應(yīng)有兩種粗悯,一種是字典的鍵值大于模型屬性數(shù)量,這時(shí)候我們不需要任何處理同欠,因?yàn)閞untime是先遍歷模型所有屬性样傍,再去字典中根據(jù)屬性名找對應(yīng)值進(jìn)行賦值横缔,多余的鍵值對也當(dāng)然不會去看了;另外一種是模型屬性數(shù)量大于字典的鍵值對铭乾,這時(shí)候由于屬性沒有對應(yīng)值會被賦值為nil剪廉,就會導(dǎo)致crash,我們只需加一個(gè)判斷即可炕檩。考慮三種情況下面一一注解斗蒋;

步驟:提供一個(gè)NSObject分類,專門字典轉(zhuǎn)模型笛质,以后所有模型都可以通過這個(gè)分類實(shí)現(xiàn)字典轉(zhuǎn)模型泉沾。

MJExtension字典轉(zhuǎn)模型實(shí)現(xiàn)

底層也是對runtime的封裝,才可以把一個(gè)模型中所有屬性遍歷出來妇押。(你之所以看不懂跷究,是MJ封裝了很多層而已_.)。

這里針對字典轉(zhuǎn)模型 KVC 實(shí)現(xiàn)敲霍,就不做詳解了俊马,如果你 對 KVC 詳解使用或是實(shí)現(xiàn)原理 不是很清楚的,可以參考實(shí)用「KVC編碼 & KVO監(jiān)聽

字典轉(zhuǎn)模型 Runtime 方式實(shí)現(xiàn)

說明:下面這個(gè)示例肩杈,是考慮三種情況包含在內(nèi)的轉(zhuǎn)換示例柴我,具體可以看圖上的注解

image.png

Runtime 字典轉(zhuǎn)模型

1、runtime 字典轉(zhuǎn)模型-->字典的key和模型的屬性不匹配「模型屬性數(shù)量大于字典鍵值對數(shù)」扩然,這種情況處理如下:

// Runtime:根據(jù)模型中屬性,去字典中取出對應(yīng)的value給模型屬性賦值

// 思路:遍歷模型中所有屬性->使用運(yùn)行時(shí)

+ (instancetype)modelWithDict:(NSDictionary *)dict

{

// 1.創(chuàng)建對應(yīng)的對象

id objc = [[self alloc] init];

// 2.利用runtime給對象中的屬性賦值

/**

class_copyIvarList: 獲取類中的所有成員變量

Ivar:成員變量

第一個(gè)參數(shù):表示獲取哪個(gè)類中的成員變量

第二個(gè)參數(shù):表示這個(gè)類有多少成員變量艘儒,傳入一個(gè)Int變量地址,會自動給這個(gè)變量賦值

返回值Ivar *:指的是一個(gè)ivar數(shù)組夫偶,會把所有成員屬性放在一個(gè)數(shù)組中界睁,通過返回的數(shù)組就能全部獲取到。

count: 成員變量個(gè)數(shù)

*/

unsigned int count = 0;

// 獲取類中的所有成員變量

Ivar *ivarList = class_copyIvarList(self, &count);

// 遍歷所有成員變量

for (int i = 0; i < count; i++) {

// 根據(jù)角標(biāo)兵拢,從數(shù)組取出對應(yīng)的成員變量

Ivar ivar = ivarList[i];

// 獲取成員變量名字

NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開始截取)

NSString *key = [ivarName substringFromIndex:1];

// 根據(jù)成員屬性名去字典中查找對應(yīng)的value

id value = dict[key];

// 【如果模型屬性數(shù)量大于字典鍵值對數(shù)理搀矫,模型屬性會被賦值為nil】

// 而報(bào)錯(cuò) (could not set nil as the value for the key age.)

if (value) {

// 給模型中屬性賦值

[objc setValue:value forKey:key];

}

}

return objc;

}

注解:這里在獲取模型類中的所有屬性名入挣,是采取class_copyIvarList先獲取成員變量(以下劃線開頭) 霹购,然后再處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開始截取) 得到屬性名茧痕。

原因:Ivar:成員變量,以下劃線開頭,Property 屬性

獲取類里面屬性class_copyPropertyList

獲取類中的所有成員變量class_copyIvarList

{

int _a; // 成員變量

}

@property (nonatomic, assign) NSInteger attitudes_count; // 屬性

這里有成員變量截汪,就不會漏掉屬性疾牲;如果有屬性,可能會漏掉成員變量衙解;

使用runtime字典轉(zhuǎn)模型獲取模型屬性名的時(shí)候阳柔,最好獲取成員屬性名Ivar因?yàn)榭赡軙袀€(gè)屬性是沒有setter和getter方法的。

2蚓峦、runtime 字典轉(zhuǎn)模型-->模型中嵌套模型「模型屬性是另外一個(gè)模型對象」舌剂,這種情況處理如下:

+ (instancetype)modelWithDict2:(NSDictionary *)dict

{

// 1.創(chuàng)建對應(yīng)的對象

id objc = [[self alloc] init];

// 2.利用runtime給對象中的屬性賦值

unsigned int count = 0;

// 獲取類中的所有成員變量

Ivar *ivarList = class_copyIvarList(self, &count);

// 遍歷所有成員變量

for (int i = 0; i < count; i++) {

// 根據(jù)角標(biāo)济锄,從數(shù)組取出對應(yīng)的成員變量

Ivar ivar = ivarList[i];

// 獲取成員變量名字

NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 獲取成員變量類型

NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

// 替換: @\"User\" -> User

ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];

ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];

// 處理成員屬性名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開始截取)

NSString *key = [ivarName substringFromIndex:1];

// 根據(jù)成員屬性名去字典中查找對應(yīng)的value

id value = dict[key];

//--------------------------- <#我是分割線#> ------------------------------//

//

// 二級轉(zhuǎn)換:如果字典中還有字典,也需要把對應(yīng)的字典轉(zhuǎn)換成模型

// 判斷下value是否是字典,并且是自定義對象才需要轉(zhuǎn)換

if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {

// 字典轉(zhuǎn)換成模型 userDict => User模型, 轉(zhuǎn)換成哪個(gè)模型

// 根據(jù)字符串類名生成類對象

Class modelClass = NSClassFromString(ivarType);

if (modelClass) { // 有對應(yīng)的模型才需要轉(zhuǎn)

// 把字典轉(zhuǎn)模型

value = [modelClass modelWithDict2:value];

}

}

// 給模型中屬性賦值

if (value) {

[objc setValue:value forKey:key];

}

}

return objc;

}

3霍转、runtime 字典轉(zhuǎn)模型-->數(shù)組中裝著模型「模型的屬性是一個(gè)數(shù)組荐绝,數(shù)組中是字典模型對象」,這種情況處理如下:

// Runtime:根據(jù)模型中屬性,去字典中取出對應(yīng)的value給模型屬性賦值

// 思路:遍歷模型中所有屬性->使用運(yùn)行時(shí)

+ (instancetype)modelWithDict3:(NSDictionary *)dict

{

// 1.創(chuàng)建對應(yīng)的對象

id objc = [[self alloc] init];

// 2.利用runtime給對象中的屬性賦值

unsigned int count = 0;

// 獲取類中的所有成員變量

Ivar *ivarList = class_copyIvarList(self, &count);

// 遍歷所有成員變量

for (int i = 0; i < count; i++) {

// 根據(jù)角標(biāo)避消,從數(shù)組取出對應(yīng)的成員變量

Ivar ivar = ivarList[i];

// 獲取成員變量名字

NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

// 處理成員屬性名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開始截取)

NSString *key = [ivarName substringFromIndex:1];

// 根據(jù)成員屬性名去字典中查找對應(yīng)的value

id value = dict[key];

//--------------------------- <#我是分割線#> ------------------------------//

// 三級轉(zhuǎn)換:NSArray中也是字典低滩,把數(shù)組中的字典轉(zhuǎn)換成模型.

// 判斷值是否是數(shù)組

if ([value isKindOfClass:[NSArray class]]) {

// 判斷對應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議

// arrayContainModelClass 提供一個(gè)協(xié)議,只要遵守這個(gè)協(xié)議的類岩喷,都能把數(shù)組中的字典轉(zhuǎn)模型

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 modelWithDict3:dict];

[arrM addObject:model];

}

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

value = arrM;

}

}

// 如果模型屬性數(shù)量大于字典鍵值對數(shù)理纱意,模型屬性會被賦值為nil,而報(bào)錯(cuò)

if (value) {

// 給模型中屬性賦值

[objc setValue:value forKey:key];

}

}

return objc;

}
image.png

runtime字典轉(zhuǎn)模型-->數(shù)組中裝著模型 打印輸出

總結(jié):我們既然能獲取到屬性類型婶溯,那就可以攔截到模型的那個(gè)數(shù)組屬性,進(jìn)而對數(shù)組中每個(gè)模型遍歷并字典轉(zhuǎn)模型偷霉,但是我們不知道數(shù)組中的模型都是什么類型迄委,我們可以聲明一個(gè)方法,該方法目的不是讓其調(diào)用类少,而是讓其實(shí)現(xiàn)并返回模型的類型叙身。

這里提到的你如果不是很清楚,建議參考我的Demo瞒滴,重要的部分代碼中都有相應(yīng)的注解和文字打印曲梗,運(yùn)行程序可以很直觀的表現(xiàn)赞警。

//--------------------------- <#我是分割線#> ------------------------------//

4.runtime 其它作用「面試熟悉」

動態(tài)添加方法

應(yīng)用場景:如果一個(gè)類方法非常多妓忍,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表愧旦,可以使用動態(tài)給某個(gè)類世剖,添加方法解決。

注解:OC 中我們很習(xí)慣的會用懶加載笤虫,當(dāng)用到的時(shí)候才去加載它旁瘫,但是實(shí)際上只要一個(gè)類實(shí)現(xiàn)了某個(gè)方法,就會被加載進(jìn)內(nèi)存琼蚯。當(dāng)我們不想加載這么多方法的時(shí)候酬凳,就會使用到runtime動態(tài)的添加方法。

需求:runtime 動態(tài)添加方法處理調(diào)用一個(gè)未實(shí)現(xiàn)的方法 和 去除報(bào)錯(cuò)遭庶。

案例代碼:方法+調(diào)用+打印輸出

- (void)viewDidLoad {

[super viewDidLoad];

Person *p = [[Person alloc] init];

// 默認(rèn)person宁仔,沒有實(shí)現(xiàn)run:方法,可以通過performSelector調(diào)用峦睡,但是會報(bào)錯(cuò)翎苫。

// 動態(tài)添加方法就不會報(bào)錯(cuò)

[p performSelector:@selector(run:) withObject:@10];

}

@implementation Person

// 沒有返回值,1個(gè)參數(shù)

// void,(id,SEL)

void aaa(id self, SEL _cmd, NSNumber *meter) {

NSLog(@"跑了%@米", meter);

}

// 任何方法默認(rèn)都有兩個(gè)隱式參數(shù),self,_cmd(當(dāng)前方法的方法編號)

// 什么時(shí)候調(diào)用:只要一個(gè)對象調(diào)用了一個(gè)未實(shí)現(xiàn)的方法就會調(diào)用這個(gè)方法,進(jìn)行處理

// 作用:動態(tài)添加方法,處理未實(shí)現(xiàn)

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

// [NSStringFromSelector(sel) isEqualToString:@"run"];

if (sel == NSSelectorFromString(@"run:")) {

// 動態(tài)添加run方法

// class: 給哪個(gè)類添加方法

// SEL: 添加哪個(gè)方法权埠,即添加方法的方法編號

// IMP: 方法實(shí)現(xiàn) => 函數(shù) => 函數(shù)入口 => 函數(shù)名(添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址))

// type: 方法類型,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd

class_addMethod(self, sel, (IMP)aaa, "v@:@");

return YES;

}

return [super resolveInstanceMethod:sel];

}

@end

// 打印輸出

2016-02-17 19:05:03.917 runtime[12761:543574] runtime動態(tài)添加方法--跑了10米

第二種方法

- (void)viewDidLoad
{
    [super viewDidLoad];
        
    
    XMView *vc = [[XMView alloc] init];
//
    vc.frame = CGRectMake(100, 100, 200, 200);
//    XMView *vc = [[XMView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    

    [self.view addSubview:vc];
    
//    [vc run];
    
//    [vc performSelector:@selector(run:) withObject:@10];

    class_addMethod([XMView class],
                    @selector(printPerson),
                    class_getMethodImplementation([XMViewController class], @selector(find)),
                    "v@:");
    
    
    [vc performSelector:@selector(printPerson)];
    
    
}

- (void)find {

    // Initialization code
  
    NSLog(@"=搜索==123==");

}
首先我們看看這個(gè)方法里面的參數(shù):

Class cos:我們需要一個(gè)class煎谍,比如我的[Person class]攘蔽。

SEL name:這個(gè)很有意思,這個(gè)名字自己可以隨意想呐粘,就是添加的方法在本類里面叫做的名字满俗,但是方法的格式一定要和你需要添加的方法的格式一樣,比如有無參數(shù)事哭。(有幾個(gè)小伙伴問我Demo里面的findInSelf這個(gè)方法沒有找到漫雷,請看這里呀。這個(gè)就是里面為啥沒有findInSelf方法而可以直接調(diào)用的原因)

IMP imp:IMP就是Implementation的縮寫鳍咱,它是指向一個(gè)方法實(shí)現(xiàn)的指針降盹,每一個(gè)方法都有一個(gè)對應(yīng)的IMP。這里需要的是IMP谤辜,所以你不能直接寫方法蓄坏,需要用到一個(gè)方法:

OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name)

__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

這個(gè)方法也是runtime的方法,就是獲得對應(yīng)的方法的指針丑念,也就是IMP涡戳。

const char *types:這一個(gè)也很有意思,我剛開始也很費(fèi)解脯倚,結(jié)果看了好多人的解釋我釋然了渔彰,知道嗎,我釋然啦推正,????恍涂。這個(gè)東西其實(shí)也很好理解:

比如:”v@:”意思就是這已是一個(gè)void類型的方法,沒有參數(shù)傳入植榕。

再比如 “i@:”就是說這是一個(gè)int類型的方法再沧,沒有參數(shù)傳入。

再再比如”i@:@”就是說這是一個(gè)int類型的方法尊残,又一個(gè)參數(shù)傳入炒瘸。

好了,參數(shù)解釋完了寝衫,還有一點(diǎn)需要注意顷扩,用這個(gè)方法添加的方法是無法直接調(diào)用的,必須用performSelector:調(diào)用慰毅。為甚么呢隘截???

因?yàn)閜erformSelector是運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)去找方法的技俐,在編譯時(shí)候不做任何校驗(yàn)乘陪;如果直接調(diào)用編譯是會自動校驗(yàn)。

知道為甚么了吧雕擂,你添加方法是在運(yùn)行時(shí)添加的啡邑,你在編譯的時(shí)候還沒有這個(gè)本類方法,所以當(dāng)然不行啦井赌。

//--------------------------- <#我是分割線#> ------------------------------//
5.動態(tài)變量控制

現(xiàn)在有一個(gè)Person類谤逼,創(chuàng)建 xiaoming對象

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

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

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

const char *varName = ivar_getName(var);

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

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

代碼參考

-(void)answer{
    
    unsigned int count = 0;
    
    Ivar *ivar = class_copyIvarList([self 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, var, @"20");
            
            break;
            
        }
        
    }
    
    NSLog(@"XiaoMing's age is %@",self.age);
    
}

//--------------------------- <#我是分割線#> ------------------------------//
6.實(shí)現(xiàn)NSCoding的自動歸檔和解檔

如果你實(shí)現(xiàn)過自定義模型數(shù)據(jù)持久化的過程,那么你也肯定明白仇穗,如果一個(gè)模型有許多個(gè)屬性流部,那么我們需要對每個(gè)屬性都實(shí)現(xiàn)一遍encodeObject和decodeObjectForKey方法,如果這樣的模型又有很多個(gè)纹坐,這還真的是一個(gè)十分麻煩的事情枝冀。下面來看看簡單的實(shí)現(xiàn)方式。

假設(shè)現(xiàn)在有一個(gè)Movie類耘子,有3個(gè)屬性果漾。先看下.h文件

// Movie.h文件

//1. 如果想要當(dāng)前類可以實(shí)現(xiàn)歸檔與反歸檔,需要遵守一個(gè)協(xié)議NSCoding

@interface Movie : NSObject

@property (nonatomic, copy) NSString *movieId;

@property (nonatomic, copy) NSString *movieName;

@property (nonatomic, copy) NSString *pic_url;

@end

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

// Movie.m文件

@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個(gè)屬性绒障,那么我們也只能把100個(gè)屬性都給寫一遍嗎。

不過你會使用runtime后捍歪,這里就有更簡便的方法户辱,如下。

#import "Movie.h"

#import

@implementation Movie

- (void)encodeWithCoder:(NSCoder *)encoder{
    
    unsigned int count = 0;
    
    Ivar *ivars = class_copyIvarList([self 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([self 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

這樣的方式實(shí)現(xiàn)糙臼,不管有多少個(gè)屬性庐镐,寫這幾行代碼就搞定了。怎么弓摘,代碼有點(diǎn)多焚鹊,

好說下面看看更加簡便的方法:兩句代碼搞定痕届。

#define encodeRuntime \
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([self 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 \
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([self 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;

優(yōu)化:上面是encodeWithCoder和initWithCoder這兩個(gè)方法抽成宏韧献。我們可以把這兩個(gè)宏單獨(dú)放到一個(gè)文件里面,這里以后需要進(jìn)行數(shù)據(jù)持久化的模型都可以直接使用這兩個(gè)宏研叫。

runtime 下Class的各項(xiàng)操作

下面是 runtime 下Class的常見方法 及 帶有使用示例代碼锤窑。各項(xiàng)操作,原著 http://www.reibang.com/p/46dd81402f63

//--------------------------- <#我是分割線#> ------------------------------//

unsigned int count;

獲取屬性列表

objc_property_t *propertyList = class_copyPropertyList([self class], &count);

for (unsigned int i=0; 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

Method method = methodList[i];

NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));

}

獲取成員變量列表

Ivar *ivarList = class_copyIvarList([self class], &count);

for (unsigned int i; 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

Protocol *myProtocal = protocolList[i];

const char *protocolName = protocol_getName(myProtocal);

NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);

}

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

獲得類方法

Class PersonClass = object_getClass([Person class]);

SEL oriSEL = @selector(test1);

Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);

獲得實(shí)例方法

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));

替換原方法實(shí)現(xiàn)

class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

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

method_exchangeImplementations(oriMethod, cusMethod);

常用方法

附:上面有提到寫常用示例渊啰,這里再總結(jié)下 ~

// 得到類的所有方法

Method *allMethods = class_copyMethodList([Person class], &count);

// 得到所有成員變量

Ivar *allVariables = class_copyIvarList([Person class], &count);

// 得到所有屬性

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

// 根據(jù)名字得到類變量的Ivar指針,但是這個(gè)在OC中好像毫無意義

Ivar oneCVIvar = class_getClassVariable([Person class], name);

// 根據(jù)名字得到實(shí)例變量的Ivar指針

Ivar oneIVIvar = class_getInstanceVariable([Person class], name);

// 找到后可以直接對私有變量賦值

object_setIvar(_per, oneIVIvar, @"Mike");//強(qiáng)制修改name屬性

/* 動態(tài)添加方法:

第一個(gè)參數(shù)表示Class cls 類型;

第二個(gè)參數(shù)表示待調(diào)用的方法名稱绘证;

第三個(gè)參數(shù)(IMP)myAddingFunction隧膏,IMP是一個(gè)函數(shù)指針,這里表示指定具體實(shí)現(xiàn)方法myAddingFunction嚷那;

第四個(gè)參數(shù)表方法的參數(shù)胞枕,0代表沒有參數(shù);

*/

class_addMethod([_per class], @selector(sayHi), (IMP)myAddingFunction, 0);

// 交換兩個(gè)方法

method_exchangeImplementations(method1, method2);

// 關(guān)聯(lián)兩個(gè)對象

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

/*

id object                    :表示關(guān)聯(lián)者魏宽,是一個(gè)對象腐泻,變量名理所當(dāng)然也是object

const void *key              :獲取被關(guān)聯(lián)者的索引key

id value                      :被關(guān)聯(lián)者,這里是一個(gè)block

objc_AssociationPolicy policy : 關(guān)聯(lián)時(shí)采用的協(xié)議队询,有assign派桩,retain,copy等協(xié)議蚌斩,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC

*/

// 獲得某個(gè)類的類方法

Method class_getClassMethod(Class cls , SEL name)

// 獲得某個(gè)類的實(shí)例對象方法

Method class_getInstanceMethod(Class cls , SEL name)

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

void method_exchangeImplementations(Method m1 , Method m2)

// 將某個(gè)值跟某個(gè)對象關(guān)聯(lián)起來铆惑,將某個(gè)值存儲到某個(gè)對象中

void objc_setAssociatedObject(id object , const void *key ,id value ,objc_AssociationPolicy policy)

// 利用參數(shù)key 將對象object中存儲的對應(yīng)值取出來

id objc_getAssociatedObject(id object , const void *key)

// 獲得某個(gè)類的所有成員變量(outCount 會返回成員變量的總數(shù))

Ivar *class_copyIvarList(Class cls , unsigned int *outCount)

// 獲得成員變量的名字

const char *ivar_getName(Ivar v)

// 獲得成員變量的類型

const char *ivar_getTypeEndcoding(Ivar v)

// 獲取類里面所有方法

class_copyMethodList(__unsafe_unretained Class cls, unsigned int *outCount)// 本質(zhì):創(chuàng)建誰的對象

// 獲取類里面屬性

class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount)

runtime 幾個(gè)參數(shù)概念

以上的幾種方法應(yīng)該算是runtime在實(shí)際場景中所應(yīng)用的大部分的情況了,平常的編碼中差不多足夠用了送膳。

這里在對runtime幾個(gè)參數(shù)概念鸭津,做一簡單說明

1、objc_msgSend

這是個(gè)最基本的用于發(fā)送消息的函數(shù)肠缨。

其實(shí)編譯器會根據(jù)情況在objc_msgSend逆趋,objc_msgSend_stret,,objc_msgSendSuper晒奕, 或objc_msgSendSuper_stret四個(gè)方法中選擇一個(gè)來調(diào)用闻书。如果消息是傳遞給超類,那么會調(diào)用名字帶有Super的函數(shù)脑慧;如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí)魄眉,那么會調(diào)用名字帶有stret的函數(shù)。

2闷袒、SEL

objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL坑律,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器囊骤,可以理解為區(qū)分方法的ID晃择,而這個(gè)ID的數(shù)據(jù)結(jié)構(gòu)是SEL:

typedef struct objc_selector *SEL;

其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()``或者 Runtime系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè)SEL類型的方法選擇器也物。

3宫屠、id

objc_msgSend第一個(gè)參數(shù)類型為id,大家對它都不陌生滑蚯,它是一個(gè)指向類實(shí)例的指針:

typedef struct objc_object *id;

那objc_object又是啥呢:

struct objc_object { Class isa; };

objc_object結(jié)構(gòu)體包含一個(gè)isa指針浪蹂,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類抵栈。

4、runtime.h里Class的定義

struct objc_class {

Class isa OBJC_ISA_AVAILABILITY;//每個(gè)Class都有一個(gè)isa指針

if !OBJC2

Class super_class OBJC2_UNAVAILABLE;//父類

const char *name OBJC2_UNAVAILABLE;//類名

long version OBJC2_UNAVAILABLE;//類版本

long info OBJC2_UNAVAILABLE;//!*!供運(yùn)行期使用的一些位標(biāo)識坤次。如:CLS_CLASS (0x1L)表示該類為普通class; CLS_META(0x2L)表示該類為metaclass等(runtime.h中有詳細(xì)列出)

long instance_size OBJC2_UNAVAILABLE;//實(shí)例大小

struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;//存儲每個(gè)實(shí)例變量的內(nèi)存地址

struct objc_method_list *methodLists OBJC2_UNAVAILABLE;//!!根據(jù)info的信息確定是類還是實(shí)例古劲,運(yùn)行什么函數(shù)方法等

struct objc_cache *cache OBJC2_UNAVAILABLE;//緩存

struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;//協(xié)議

endif

} OBJC2_UNAVAILABLE;

可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針,類名缰猴,成員變量绢慢,方法,緩存洛波,還有附屬的協(xié)議胰舆。

在objc_class結(jié)構(gòu)體中:``ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針蹬挤。也就是說可以動態(tài)修改*methodLists的值來添加成員方法缚窿,這也是Category實(shí)現(xiàn)的原理。

什么是 method swizzling(俗稱黑魔法)

簡單說就是進(jìn)行方法交換

在Objective-C中調(diào)用一個(gè)方法焰扳,其實(shí)是向一個(gè)對象發(fā)送消息倦零,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動態(tài)特性吨悍,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對應(yīng)的方法實(shí)現(xiàn)扫茅,達(dá)到給方法掛鉤的目的

每個(gè)類都有一個(gè)方法列表,存放著方法的名字和方法實(shí)現(xiàn)的映射關(guān)系育瓜,selector的本質(zhì)其實(shí)就是方法名葫隙,IMP有點(diǎn)類似函數(shù)指針,指向具體的Method實(shí)現(xiàn)躏仇,通過selector就可以找到對應(yīng)的IMP恋脚。

image.png

selector --> 對應(yīng)的IMP

交換方法的幾種實(shí)現(xiàn)方式

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

利用class_replaceMethod替換方法的實(shí)現(xiàn)

利用method_setImplementation來直接設(shè)置某個(gè)方法的IMP。

image.png

交換方法

這里可以參考簡友這篇:【Runtime Method Swizzling開發(fā)實(shí)例匯總】http://www.reibang.com/p/f6dad8e1b848

最后一道面試題的注解

下面的代碼輸出什么?

@implementation Son : NSObject

  • (id)init

{

self = [super init];

if (self) {

NSLog(@"%@", NSStringFromClass([self class]));

NSLog(@"%@", NSStringFromClass([super class]));

}

return self;

}

@end

先思考一下焰手,會打印出來什么?

關(guān)注我的更多干貨分享_.

答案:都輸出 Son

class獲取當(dāng)前方法的調(diào)用者的類糟描,superClass獲取當(dāng)前方法的調(diào)用者的父類,super僅僅是一個(gè)編譯指示器书妻,就是給編譯器看的船响,不是一個(gè)指針。

本質(zhì):只要編譯器看到super這個(gè)標(biāo)志躲履,就會讓當(dāng)前對象去調(diào)用父類方法见间,本質(zhì)還是當(dāng)前對象在調(diào)用

這個(gè)題目主要是考察關(guān)于objc中對self和super的理解:

self是類的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例崇呵。而super本質(zhì)是一個(gè)編譯器標(biāo)示符缤剧,和self是指向的同一個(gè)消息接受者

當(dāng)使用self調(diào)用方法時(shí)馅袁,會從當(dāng)前類的方法列表中開始找域慷,如果沒有,就從父類中再找;

而當(dāng)使用super時(shí)犹褒,則從父類的方法列表中開始找抵窒。然后調(diào)用父類的這個(gè)方法

調(diào)用[self class]時(shí),會轉(zhuǎn)化成objc_msgSend函數(shù)

id objc_msgSend(id self, SEL op, ...)

  • 調(diào)用 [super class]時(shí)叠骑,會轉(zhuǎn)化成 objc_msgSendSuper 函數(shù).

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一個(gè)參數(shù)是 objc_super 這樣一個(gè)結(jié)構(gòu)體李皇,其定義如下

struct objc_super {

__unsafe_unretained id receiver;

__unsafe_unretained Class super_class;

};

第一個(gè)成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個(gè)參數(shù)self

第二個(gè)成員是記錄當(dāng)前類的父類是什么庆尘,告訴程序從父類中開始找方法乳愉,找到方法后,最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用颤芬, 此時(shí)已經(jīng)和[self class]調(diào)用相同了慰丛,故上述輸出結(jié)果仍然返回 Son

objc Runtime 開源代碼對- (Class)class方法的實(shí)現(xiàn)

-(Class)class { return object_getClass(self);

}

Runtime 模塊博文推薦 (??數(shù)量較多)

作者Runtime 模塊推薦閱讀博文

西木完整總結(jié)http://www.reibang.com/p/6b905584f536

天口三水羊objc_msgSendhttp://www.reibang.com/p/9e1bc8d890f9

夜千尋墨詳解http://www.reibang.com/p/46dd81402f63

袁崢Seemygo快速上手http://www.reibang.com/p/e071206103a4

鄭欽洪_實(shí)現(xiàn)自動化歸檔http://www.reibang.com/p/bd24c3f3cd0a

HenryCheng消息機(jī)制http://www.reibang.com/p/f6300eb3ec3d

賣報(bào)的小畫家SureMethod Swizzling開發(fā)實(shí)例匯總http://www.reibang.com/p/f6dad8e1b848

滕大鳥OC最實(shí)用的runtime總結(jié)http://www.reibang.com/p/ab966e8a82e2

黑花白花Runtime在實(shí)際開發(fā)中的應(yīng)用http://www.reibang.com/p/851b21870d91

Runtime & Runloop 面試最常問到的題整理【建議看】
整理原文:2017年5月iOS招人心得(附面試題)

objc在向一個(gè)對象發(fā)送消息時(shí)卓囚,發(fā)生了什么?

什么時(shí)候會報(bào)unrecognized selector錯(cuò)誤诅病?iOS有哪些機(jī)制來避免走到這一步哪亿?

能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量贤笆?為什么蝇棉?

runtime如何實(shí)現(xiàn)weak變量的自動置nil?

給類添加一個(gè)屬性后芥永,在類結(jié)構(gòu)體里哪些元素會發(fā)生變化篡殷?

RunLoop

runloop是來做什么的?runloop和線程有什么關(guān)系埋涧?主線程默認(rèn)開啟了runloop么贴唇?子線程呢?

runloop的mode是用來做什么的飞袋?有幾種mode戳气?

為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)以后,滑動scrollview的時(shí)候NSTimer卻不動了巧鸭?

蘋果是如何實(shí)現(xiàn)Autorelease Pool的瓶您?

//-------------------- 【我是分割線】 ---------------------//

整理原文:2017年iOS面試題總結(jié),附上答案

Runtime

01

問題:objc在向一個(gè)對象發(fā)送消息時(shí)纲仍,發(fā)生了什么呀袱?

解答:根據(jù)對象的 isa 指針找到類對象 id,在查詢類對象里面的 methodLists 方法函數(shù)列表郑叠,如果沒有在好到夜赵,在沿著 superClass ,尋找父類,再在父類 methodLists 方法列表里面查詢乡革,最終找到 SEL ,根據(jù) id 和 SEL 確認(rèn) IMP(指針函數(shù)),在發(fā)送消息寇僧;
消息轉(zhuǎn)發(fā)過程的關(guān)鍵方法:

動態(tài)方法解析
向當(dāng)前類發(fā)送resolveInstanceMethod:消息摊腋,檢查是否動態(tài)向類添加了方 法,如果返回YES嘁傀,則系統(tǒng)認(rèn)為方法已經(jīng)被添加兴蒸,則會重新發(fā)送消息。

快速消息轉(zhuǎn)發(fā)
檢查當(dāng)前類是否實(shí)現(xiàn)forwardingTargetForSelector:方法细办,若實(shí)現(xiàn)則調(diào) 用橙凳,如果方法返回值為非nil或非self的對象,則向返回的對象重新發(fā)送消息笑撞。

標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)
Runtime發(fā)送methodSignatureForSelector:消息獲取selector對應(yīng)方法的簽名岛啸,如果有方法簽名返回,則根據(jù)方法簽名創(chuàng)建描述消息的NSInvocation茴肥,向當(dāng)前對象發(fā)送forwardInvocation:消息值戳,如果沒有方法簽名返回,即返回值為nil炉爆,則向當(dāng)前對象發(fā)送doesNotRecognizeSelector:消息堕虹,應(yīng)用崩潰退出
消息轉(zhuǎn)發(fā)

03

問題:什么時(shí)候會報(bào)unrecognized selector錯(cuò)誤?iOS有哪些機(jī)制來避免走到這一步芬首?

解答:當(dāng)發(fā)送消息的時(shí)候赴捞,我們會根據(jù)類里面的 methodLists 列表去查詢我們要?jiǎng)佑玫腟EL,當(dāng)查詢不到的時(shí)候郁稍,我們會一直沿著父類查詢赦政,當(dāng)最終查詢不到的時(shí)候我們會報(bào)unrecognized selector錯(cuò)誤,當(dāng)系統(tǒng)查詢不到方法的時(shí)候耀怜,會調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel動態(tài)解釋的方法來給我一次機(jī)會來添加恢着,調(diào)用不到的方法〔破疲或者我們可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法來告訴系統(tǒng)掰派,該調(diào)用什么方法,一來保證不會崩潰左痢。

04

問題:能否向編譯后得到的類中增加實(shí)例變量靡羡?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么俊性?

解答:1略步、不能向編譯后得到的類增加實(shí)例變量 2、能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量定页√吮。【解釋】:1. 編譯后的類已經(jīng)注冊在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定,runtime會調(diào)用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.所以不能向存在的類中添加實(shí)例變量典徊。2. 運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量杭煎,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后恩够,objc_registerClassPair 之前,原因同上.

05

問題:runtime如何實(shí)現(xiàn)weak變量的自動置nil?

解答:runtime 對注冊的類岔帽, 會進(jìn)行布局玫鸟,對于 weak 對象會放入一個(gè) hash 表中导绷。 用 weak 指向的對象內(nèi)存地址作為 key犀勒,當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a妥曲,那么就會以a為鍵贾费, 在這個(gè) weak 表中搜索,找到所有以a為鍵的 weak 對象檐盟,從而設(shè)置為 nil褂萧。

06

問題:給類添加一個(gè)屬性后,在類結(jié)構(gòu)體里哪些元素會發(fā)生變化葵萎?

解答:instance_size :實(shí)例的內(nèi)存大械加獭;objc_ivar_list *ivars:屬性列表

RunLoop

01

問題:runloop是來做什么的羡忘?runloop和線程有什么關(guān)系谎痢?主線程默認(rèn)開啟了runloop么?子線程呢卷雕?

解答:runloop: 從字面意思看:運(yùn)行循環(huán)节猿、跑圈,其實(shí)它內(nèi)部就是do-while循環(huán)漫雕,在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source滨嘱、Timer、Observer)事件浸间。runloop和線程的關(guān)系:一個(gè)線程對應(yīng)一個(gè)RunLoop太雨,主線程的RunLoop默認(rèn)創(chuàng)建并啟動,子線程的RunLoop需手動創(chuàng)建且手動啟動(調(diào)用run方法)魁蒜。RunLoop只能選擇一個(gè)Mode啟動躺彬,如果當(dāng)前Mode中沒有任何Source(Sources0、Sources1)梅惯、Timer宪拥,那么就直接退出RunLoop。

02

問題:runloop的mode是用來做什么的铣减?有幾種mode她君?

解答:model:是runloop里面的運(yùn)行模式,不同的模式下的runloop處理的事件和消息有一定的差別葫哗。系統(tǒng)默認(rèn)注冊了5個(gè)Mode:(1)kCFRunLoopDefaultMode: App的默認(rèn) Mode缔刹,通常主線程是在這個(gè) Mode 下運(yùn)行的球涛。(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動校镐,保證界面滑動時(shí)不受其他 Mode 影響亿扁。(3)UIInitializationRunLoopMode: 在剛啟動 App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動完成后就不再使用鸟廓。(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode从祝,通常用不到。(5)kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode引谜,沒有實(shí)際作用牍陌。注意iOS 對以上5中model進(jìn)行了封裝 NSDefaultRunLoopMode、NSRunLoopCommonModes

03

問題:為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)以后员咽,滑動scrollview的時(shí)候NSTimer卻不動了毒涧?

解答:nstime對象是在 NSDefaultRunLoopMode下面調(diào)用消息的,但是當(dāng)我們滑動scrollview的時(shí)候贝室,NSDefaultRunLoopMode模式就自動切換到UITrackingRunLoopMode模式下面契讲,卻不可以繼續(xù)響應(yīng)nstime發(fā)送的消息。所以如果想在滑動scrollview的情況下面還調(diào)用nstime的消息滑频,我們可以把nsrunloop的模式更改為NSRunLoopCommonModes.

04

問題:蘋果是如何實(shí)現(xiàn)Autorelease Pool的捡偏?

解答:Autorelease Pool作用:緩存池,可以避免我們經(jīng)常寫relase的一種方式误趴。其實(shí)就是延遲release霹琼,將創(chuàng)建的對象,添加到最近的autoreleasePool中凉当,等到autoreleasePool作用域結(jié)束的時(shí)候枣申,會將里面所有的對象的引用計(jì)數(shù)器 - autorelease.

其他runtime 完整總結(jié) http://www.reibang.com/p/6b905584f536

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市看杭,隨后出現(xiàn)的幾起案子忠藤,更是在濱河造成了極大的恐慌,老刑警劉巖楼雹,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件模孩,死亡現(xiàn)場離奇詭異,居然都是意外死亡贮缅,警方通過查閱死者的電腦和手機(jī)榨咐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谴供,“玉大人块茁,你說我怎么就攤上這事。” “怎么了数焊?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵永淌,是天一觀的道長。 經(jīng)常有香客問我佩耳,道長遂蛀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任干厚,我火速辦了婚禮李滴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘萍诱。我一直安慰自己悬嗓,他們只是感情好污呼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布裕坊。 她就那樣靜靜地躺著,像睡著了一般燕酷。 火紅的嫁衣襯著肌膚如雪籍凝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天苗缩,我揣著相機(jī)與錄音饵蒂,去河邊找鬼。 笑死酱讶,一個(gè)胖子當(dāng)著我的面吹牛退盯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泻肯,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼渊迁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灶挟?” 一聲冷哼從身側(cè)響起琉朽,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稚铣,沒想到半個(gè)月后箱叁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惕医,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年耕漱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抬伺。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡螟够,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沛简,到底是詐尸還是另有隱情齐鲤,我是刑警寧澤斥废,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站给郊,受9級特大地震影響牡肉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淆九,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一统锤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炭庙,春花似錦饲窿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腻脏,卻和暖如春鸦泳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背永品。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工做鹰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鼎姐。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓钾麸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炕桨。 傳聞我的和親對象是個(gè)殘疾皇子饭尝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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