? Runtime簡介以及常見的使用場景

Runtime簡介以及常見的使用場景

? ?Runtime簡稱運行時,是一套比較底層的純C語言的API,作為OC的核心,運行時是一種面向?qū)ο蟮木幊陶Z言的運行環(huán)境,其中最主要的是消息機制,Objective-C就是基于運行時的。

? ?所謂運行時,是指盡可能地把決定從編譯期推遲到運行期,就是盡可能地做到動態(tài).只是在運行的時候才會去確定對象的類型和方法的.因此利用Runtime機制可以在程序運行時動態(tài)地修改類和對象中的所有屬性和方法。

? ?對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù)。對于OC的函數(shù),屬于動態(tài)調(diào)用過程,在編譯的時候并不能決定真正調(diào)用哪個函數(shù),只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用。

Objective-C從三種不同的層級上與Runtime系統(tǒng)進行交互,分別是

①通過Objective-C源代碼;

②通過Foundation框架的NSObject類定義的方法;

③通過對Runtime函數(shù)的直接調(diào)用(需要導入#import );

? ?大部分情況下只管寫OC代碼就行,因為OC底層默認實現(xiàn)Runtime,每一個OC的方法,底層必然有一個與之對應的Runtime方法。。

以下是Runtime的一些使用場景:

1.發(fā)送消息

方法調(diào)用的本質(zhì),就是讓對象發(fā)送消息。objc_msgSend,只有對象才能發(fā)送消息

舉個簡單的例子:如下

//(1).調(diào)用對象方法:

//創(chuàng)建Dog對象

Dog *dog = [[Dog alloc] init];

//調(diào)用對象方法

[dog run];

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

objc_msgSend(dog, @selector(run));

//(2).調(diào)用類方法方式:

//有兩種

//第一種通過類名調(diào)用

[Dog run];

//第二種通過類對象調(diào)用

[[Dog class] run];

//用類名調(diào)用類方法,底層會自動把類名轉(zhuǎn)換成類對象調(diào)用

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

objc_msgSend([Dog class],@selector(run));

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

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

開發(fā)使用場景:如果一個類方法非常多,加載類到內(nèi)存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態(tài)給某個類,添加方法解決。經(jīng)典面試題:有沒有使用performSelector,其實主要想問你有沒有動態(tài)添加過方法。簡單使用

@implementation ViewController

-(void)viewDidLoad {

[super viewDidLoad];

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

//默認person,沒有實現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會報錯。

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

[p performSelector:@selector(eat)];

}

@end

@implementation Person

//void(*)()

//默認方法都有兩個隱式參數(shù),

void eat(id self,SEL sel)

{

NSLog(@"%@%@",self,NSStringFromSelector(sel));

}

//當一個對象調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應的方法列表傳過來.

//剛好可以用來判斷,未實現(xiàn)的方法是不是我們想要動態(tài)添加的方法

+(BOOL)resolveInstanceMethod:(SEL)sel

{ ?if (sel == @selector(eat)) {

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

/**第一個參數(shù):給哪個類添加方法,第二個參數(shù):添加方法的方法編號,第三個參數(shù):添加方法的函數(shù)實現(xiàn)(函數(shù)地址),第四個參數(shù):函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd*/

class_addMethod(self, @selector(eat),eat, "v@:"); ?}

return [super resolveInstanceMethod:sel];

}

@end

注意:

? ?+ (BOOL)resolveInstanceMethod:(SEL)aSEL這個函數(shù)與forwardingTargetForSelector類似,都會在對象不能接受某個selector時觸發(fā),執(zhí)行起來略有差別。前者的目的主要在于給用戶一個機會來向該對象添加所需的selector,后者的目的在于允許用戶將selector轉(zhuǎn)發(fā)給另一個對象。另外觸發(fā)時機也不完全一樣,該函數(shù)是個類函數(shù),在程序剛啟動,界面尚未顯示出時,就會被調(diào)用。

? ?在類不能處理某個selector的情況下,如果類重載了該函數(shù),并使用class_addMethod添加了相應的selector,并返回YES,那么后面forwardingTargetForSelector就不會被調(diào)用,如果在該函數(shù)中沒有添加相應的selector,那么不管返回什么,后面都會繼續(xù)調(diào)用forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受該selector的對象,那么resolveInstanceMethod會再次被觸發(fā),這一次,如果仍然不添加selector,程序就會報異常

3.運行時關(guān)聯(lián)對象提高效率,給分類添加屬性。

使用的時候與懶加載的特點相似,從`關(guān)聯(lián)對象`中獲取對象屬性,如果有,直接返回。

@implementation ViewController

-(void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loadingthe view, typically from a nib.

//給系統(tǒng)NSObject類動態(tài)添加屬性name

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

objc.name = @"旺財";

NSLog(@"%@",objc.name);

}

@end

//定義關(guān)聯(lián)的key

staticconst char *key = "name";

@implementation NSObject (Property)

-(NSString *)name

{

//根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。

NSString*name =objc_getAssociatedObject(self, key);

if(name! = nil) {

return name;

}}

-(void)setName:(NSString *)name

{//第一個參數(shù):給哪個對象添加關(guān)聯(lián)

//第二個參數(shù):關(guān)聯(lián)的key,通過這個key獲取

//第三個參數(shù):關(guān)聯(lián)的value

//第四個參數(shù):關(guān)聯(lián)的策略

objc_setAssociatedObject(self, key, name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

@end

4.使用運行時字典轉(zhuǎn)模型

大體思路:利用運行時,遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。步驟:提供一個NSObject分類,專門字典轉(zhuǎn)模型,以后所有模型都可以通過這個分類轉(zhuǎn)。(所有字典轉(zhuǎn)模型框架的核心算法)

創(chuàng)建NSObject的分類Runtime:


在.h中的類方法如下

#import

@interface NSObject (Runtime)

///給定一個字典,創(chuàng)建self類對應的對象

///@param dict字典

///@return對象

+ (instancetype)hd_objWithDict:(NSDictionary*)dict;

///獲取類的屬性列表數(shù)組


///@return類的屬性列表數(shù)組

+ (NSArray*)hd_objProperties;

@end

在.m中的類方法如下:

//所有字典轉(zhuǎn)模型框架的核心算法

+ (instancetype)hd_objWithDict:(NSDictionary*)dict {

//實例化對象

id object = [[self alloc]init];

//使用字典,設置對象信息

1>獲得self的屬性列表

NSArray *proList = [self hd_objProperties];

2>遍歷字典

[dictenumerateKeysAndObjectsUsingBlock:^(id_Nonnullkey,id_Nonnullobj,BOOL*_Nonnullstop) {

NSLog(@"key %@ --- value %@", key,obj);

//

3>判斷key是否在proList中

if([proListcontainsObject:key]) {

//說明屬性存在,可以使用`KVC`設置數(shù)值

[objectsetValue:objforKey:key];

}}];

return object;

}

constchar* kPropertiesListKey ="CZPropertiesListKey";

+ (NSArray*)hd_objProperties{

//從`關(guān)聯(lián)對象`中獲取對象屬性,如果有,直接返回!

//獲取關(guān)聯(lián)對象-動態(tài)添加的屬性

NSArray*ptyList =objc_getAssociatedObject(self,kPropertiesListKey);

if(ptyList != nil) {

return ptyList;

}

//調(diào)用運行時方法,取得類的屬性列表

//Ivar成員變量

//Method方法

//Property屬性

//Protocol協(xié)議

//所有屬性的`數(shù)組`,C語言中,數(shù)組的名字,就是指向第一個元素的地址

//retain/create/copy需要release,最好option + click

unsigned int count =0;

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

NSLog(@"屬性的數(shù)量%d", count);

//創(chuàng)建數(shù)組

NSMutableArray *arrayM = [NSMutableArrayarray];

//遍歷所有的屬性

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

// 1.從數(shù)組中取得屬性

//C語言的結(jié)構(gòu)體指針,通常不需要`*`

objc_property_t ?pty = proList[i];

// 2.從pty中獲得屬性的名稱

const char *cName =property_getName(pty);

NSString *name = [NSStringstringWithCString:cNameencoding:NSUTF8StringEncoding];

//NSLog(@"%@",name);

// 3.屬性名稱添加到數(shù)組

[arrayM addObject:name];

}

//釋放數(shù)組

free(proList);

// ---

2.對象的屬性數(shù)組已經(jīng)獲取完畢,利用關(guān)聯(lián)對象,動態(tài)添加屬性

/**

參數(shù)

1.對象self [OC中class也是一個特殊的對象]

2.動態(tài)添加屬性的key,獲取值的時候使用

3.動態(tài)添加的屬性值

4.對象的引用關(guān)系

*/

objc_setAssociatedObject(self,kPropertiesListKey, arrayM.copy,OBJC_ASSOCIATION_RETAIN_NONATOMIC);

return arrayM.copy;

}

注意:必須保證,模型中的屬性和字典中的key一一對應。如果不一致,就會調(diào)用[ setValue:forUndefinedKey:],報key找不到的錯。

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

解決:重寫對象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋,

就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。

-(void)setValue:(id)value forUndefinedKey:(NSString *)key

{

}

? ?通過運行時字典轉(zhuǎn)模型的好處在于寫在NSObject的分類中,和類的關(guān)聯(lián)性不強對類解耦,以后再做字典轉(zhuǎn)模型的時候只需要把這個分類往任何一個程序中一拖,程序中的對象就都具備了這個字典轉(zhuǎn)模型的方法。

5.交叉方法(黑魔法)

? ?開發(fā)使用場景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴展一些功能,并且保持原有的功能。方式一:繼承系統(tǒng)的類,重寫方法.方式二:使用runtime,交換方法.

? ?Runtime在AFN中的使用細節(jié):在AFN的NSURLSessionMangerM方法里面第363行寫了一個靜態(tài)的內(nèi)聯(lián)函數(shù),做了一個交叉方法,交叉的是af_resume和resume方法,這樣的話,可以在發(fā)送網(wǎng)絡之前發(fā)起一個通知,能接受到任何一個網(wǎng)絡請求的事件的變化。

@implementation ViewController

-(void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loadingthe view, typically from a nib.

//需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。

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

//步驟二:交換imageNamed和imageWithName的實現(xiàn),就能調(diào)用imageWithName,間接調(diào)用imageWithName的實現(xiàn)。

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

}

@end

@implementation UIImage (Image)

//加載分類到內(nèi)存的時候調(diào)用

+(void)load

{

//交換方法

//獲取imageWithName方法地址

Method imageWithName =class_getClassMethod(self, @selector(imageWithName:));

//獲取imageWithName方法地址

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

//交換方法地址,相當于交換實現(xiàn)方式

method_exchangeImplementations(imageWithName,imageName);

}

//既能加載圖片又能打印

+(instancetype)imageWithName:(NSString *)name

{//這里調(diào)用imageWithName,相當于調(diào)用imageName

UIImage *image = [self imageWithName:name];

if (image == nil) {

NSLog(@"加載空的圖片");}

return image;

}

@end

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,762評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,592評論 33 466
  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,732評論 7 64
  • 黑桃先生讓我不要洗衣服了 黑桃先生自己燒水 黑桃先生問我要不要熱水袋 黑桃先生說把空調(diào)打開,他去端盆水到房間 黑桃...
    大圣快來閱讀 414評論 0 0
  • 燈光獻給了城市 我的憂慮就種下了病情 黑夜蓋足了被子 我卻迷失了垂聽的聲音 黎明喚醒了黑暗 后背再一次冰涼 治愈,...
    零溫度閱讀 185評論 0 4