Runtime系列三:Runtime在項目中使用場景

前言:

關于Runtime的資料網(wǎng)上一搜很多,但總是寫的只言片語,不太全面。最近花了一個星期的時間重新學習Runtime,并整理了一個系列文章,并發(fā)表出來,同時也感謝開源貢獻的開發(fā)者。這里共有三篇文章:

Runtime系列一:Runtime的前世今生

Runtime系列二:Runtime的原理

Runtime系列三:Runtime在項目中使用場景

一、方法交換Swizzling

使用場景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴展一些功能,并且保持原有的功能。

方式一:繼承系統(tǒng)的類,重寫方法.

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

在Objective-C中調(diào)用一個方法,其實是向一個對象發(fā)送消息,而查找消息的唯一依據(jù)是selector的名字。所以,我們可以利用Objective-C的runtime機制,實現(xiàn)在運行時交換selector對應的方法實現(xiàn)以達到我們的目的。每個類都有一個方法列表,存放著selector的名字和方法實現(xiàn)的映射關系。IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn)

我們先看看SEL與IMP之間的關系圖:


交換圖1


從上圖可以看出來,每一個SEL與一個IMP一一對應,正常情況下通過SEL可以查找到對應消息的IMP實現(xiàn)。

但是,現(xiàn)在我們要做的就是把鏈接線解開,然后連到我們自定義的函數(shù)的IMP上。當然,交換了兩個SEL的IMP,還是可以再次交換回來了。交換后變成這樣的,如下圖


交換圖2

從圖中可以看出,我們通過swizzling特性,將selectorC的方法實現(xiàn)IMPc與selectorN的方法實現(xiàn)IMPn交換了,當我們調(diào)用selectorC,也就是給對象發(fā)送selectorC消息時,所查找到的對應的方法實現(xiàn)就是IMPn而不是IMPc了。

#import"UIViewController+swizzling.h"

#import@implementationUIViewController(swizzling)//load方法會在類第一次加載的時候被調(diào)用//調(diào)用的時間比較靠前,適合在這個方法里做方法交換

+ (void)load{//方法交換應該被保證,在程序中只會執(zhí)行一次staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{

//獲得viewController的生命周期方法的selector

SEL systemSel =@selector(viewWillAppear:);

//自己實現(xiàn)的將要被交換的方法的selector

SEL swizzSel =@selector(swiz_viewWillAppear:);

//兩個方法的Method

Method ? systemMethod = class_getInstanceMethod([selfclass], systemSel); ? ??

? Method swizzMethod = class_getInstanceMethod([selfclass], swizzSel);

//首先動態(tài)添加方法,實現(xiàn)是被交換的方法,返回值表示添加成功還是失敗BOOLisAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));

if(isAdd) {

//如果成功,說明類中不存在這個方法的實現(xiàn)//將被交換方法的實現(xiàn)替換到這個并不存在的實現(xiàn)class_replaceMethod(self,swizzSel,method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));? ? ? ? }

else{

//否則,交換兩個方法的實現(xiàn)method_exchangeImplementations(systemMethod, swizzMethod);? ? ? ? }? ? });}

- (void)swiz_viewWillAppear:(BOOL)animated{/

/這時候調(diào)用自己,看起來像是死循環(huán)//但是其實自己的實現(xiàn)已經(jīng)被替換了

[self ?swiz_viewWillAppear:animated];

NSLog(@"swizzle");}

@end

在一個自己定義的viewController中重寫viewWillAppear

- (void)viewWillAppear:(BOOL)animated{

? ? [super viewWillAppear:animated];? ?

? ? NSLog(@"viewWillAppear");

}

二、設置關聯(lián)值

使用場景:現(xiàn)在你準備用一個系統(tǒng)的類,但是系統(tǒng)的類并不能滿足你的需求,你需要額外添加一個屬性。給一個類聲明屬性,其實本質(zhì)就是給這個類添加關聯(lián),并不是直接把這個值的內(nèi)存空間添加到類存空間。分類只能添加方法

1.設置關聯(lián)值

這種情況的一般解決辦法就是繼承。

但是,只增加一個屬性,就去繼承一個類,總是覺得太麻煩類。

這個時候,runtime的關聯(lián)屬性就發(fā)揮它的作用了。

1、添加關聯(lián)對象

-(void)addAssociatedObject:(id)object{objc_setAssociatedObject(self,@selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}//獲取關聯(lián)對象-(id)getAssociatedObject{returnobjc_getAssociatedObject(self, _cmd);}

注意:這里面我們把getAssociatedObject方法的地址作為唯一的key,_cmd代表當前調(diào)用方法的地址。

參數(shù)說明:

object:與誰關聯(lián),通常是傳self

key:唯一鍵,在獲取值時通過該鍵獲取,通常是使用static

const void *來聲明

value:關聯(lián)所設置的值

policy:內(nèi)存管理策略,比如使用copy

voidobjc_setAssociatedObject(idobject,constvoid*key, idvalue, objc _AssociationPolicy policy)

2.獲取關聯(lián)值

參數(shù)說明:

object:與誰關聯(lián),通常是傳self,在設置關聯(lián)時所指定的與哪個對象關聯(lián)的那個對象

key:唯一鍵,在設置關聯(lián)時所指定的鍵

idobjc_getAssociatedObject(idobject,constvoid*key)

3.取消關聯(lián)

voidobjc_removeAssociatedObjects(idobject)

關聯(lián)策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){OBJC_ASSOCIATION_ASSIGN =0,// 表示弱引用關聯(lián),通常是基本數(shù)據(jù)類型OBJC_ASSOCIATION_RETAIN_NONATOMIC =1,// 表示強引用關聯(lián)對象,是線程安全的OBJC_ASSOCIATION_COPY_NONATOMIC =3,// 表示關聯(lián)對象copy,是線程安全的OBJC_ASSOCIATION_RETAIN =01401,// 表示強引用關聯(lián)對象,不是線程安全的OBJC_ASSOCIATION_COPY =01403// 表示關聯(lián)對象copy,不是線程安全的};

@implementationViewController

- (void)viewDidLoad {?

? [super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.

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

NSObject*objc = [[NSObjectalloc] init];?

? objc.name =@"123";NSLog(@"%@",objc.name);}

@end

// 定義關聯(lián)的key static const char*key ="name";

@implementationNSObject(Property)

- (NSString*)name{// 根據(jù)關聯(lián)的key,獲取關聯(lián)的值。returnobjc_getAssociatedObject(self, key);}

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

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

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

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

// 第四個參數(shù):關聯(lián)的策略objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}

@end

三、動態(tài)添加方法

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

@implementationViewController

- (void)viewDidLoad {?

? [superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.

Person *p = [[Person alloc] init];// 默認person,沒有實現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會報錯。

// 動態(tài)添加方法就不會報錯[p performSelector:@selector(eat)];}

@end

@implementationPerson//

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

void eat(idself,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->_cmdclass_addMethod(self,@selector(eat), eat,"v@:");? ? }return[superresolveInstanceMethod:sel];}

@end


四、字典轉模型

設計模型

模型屬性,通常需要跟字典中的key一一對應

問題:一個一個的生成模型屬性,很慢?

需求:能不能自動根據(jù)一個字典,生成對應的屬性。

解決:提供一個分類,專門根據(jù)字典生成對應的屬性字符串。

@implementation ?NSObject(Log)// 自動打印屬性字符串

+ (void)resolveDict:(NSDictionary*)dict{

// 拼接屬性字符串代碼NSMutableString*strM = [NSMutableString string];

// 1.遍歷字典,把字典中的所有key取出來,生成對應的屬性代碼

[dict enumerateKeysAndObjectsUsingBlock:^(id_Nonnull key,id_Nonnull obj,BOOL* _Nonnull stop) {

// 類型經(jīng)常變,抽出來

NSString*type;

if([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {? ?

? ? ? ? type =@"NSString";? ? ? ? }

else if([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){?

? ? ? ? ? type =@"NSArray";? ? ? ? }

else if([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){? ? ? ?

? ? type =@"int";? ? ? ? }

else if([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){? ? ? ?

? ? type =@"NSDictionary";? ? ? ? }

// 屬性字符串NSString*str;

if([type containsString:@"NS"]) {? ?

? str = [NSStringstringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];? ? ? ? }

else{? ? ? ? ? ?

str = [NSStringstringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];? ? ? ? }

// 每生成屬性字符串,就自動換行。

[strM appendFormat:@"\n%@\n",str];? ? }];

// 把拼接好的字符串打印出來,就好了。NSLog(@"%@",strM);}

@end

字典轉模型的方式一:KVC

@implementation Status

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

?Status*status= [[self alloc] init];?

? [status setValuesForKeysWithDictionary:dict];

return ?status;

}

@end

KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。

如果不一致,就會調(diào)用[ setValue:forUndefinedKey:]

報key找不到的錯。

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

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

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

-(void)setValue:(id)valueforUndefinedKey:(NSString*)key{}

字典轉模型的方式二:Runtime

思路:利用運行時,遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。

步驟:提供一個NSObject分類,專門字典轉模型,以后所有模型都可以通過這個分類轉。

@implementation ?ViewController

- (void)viewDidLoad {? ?

[superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.//

解析Plist文件

NSString*filePath = [[NSBundlemainBundle]pathForResource:@"status.plist"ofType:nil];

NSDictionary*statusDict = [NSDictionary ?dictionaryWithContentsOfFile:filePath];

// 獲取字典數(shù)組NSArray*dictArr = statusDict[@"statuses"];

// 自動生成模型的屬性字符串//?

? [NSObject resolveDict:dictArr[0][@"user"]];

_statuses = [NSMutableArray array];/

/ 遍歷字典數(shù)組

for(NSDictionary*dictindict Arr)

{? ? ? ? Status *status = [Status modelWithDict:dict];? ? ?

? [_statuses addObject:status];? ?

}

// 測試數(shù)據(jù)NSLog(@"%@ %@",_statuses,[_statuses[0] user]);}

@end

@implementation ?NSObject(Model)

+ (instancetype)modelWithDict:(NSDictionary*)dict

{

// 思路:遍歷模型中所有屬性-》使用運行時

// 0.創(chuàng)建對應的對象id objc = [[selfalloc] init];

// 1.利用runtime給對象中的成員屬性賦值

// class_copyIvarList:獲取類中的所有成員屬性

// Ivar:成員屬性的意思

// 第一個參數(shù):表示獲取哪個類中的成員屬性

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

// 返回值Ivar *:指的是一個ivar數(shù)組,會把所有成員屬性放在一個數(shù)組中,通過返回的數(shù)組就能全部獲取到。/* 類似下面這種寫法

Ivar ivar;

Ivar ivar1;

Ivar ivar2;

// 定義一個ivar的數(shù)組a

Ivar a[] = {ivar,ivar1,ivar2};

// 用一個Ivar *指針指向數(shù)組第一個元素

Ivar *ivarList = a;

// 根據(jù)指針訪問數(shù)組第一個元素

ivarList[0];

*/unsignedintcount;

// 獲取類中的所有成員屬性Ivar *ivarList = class_copyIvarList(self, &count);

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

// 根據(jù)角標,從數(shù)組取出對應的成員屬性Ivar ivar = ivarList[i];

// 獲取成員屬性名

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

// 處理成員屬性名->字典中的key

// 從第一個角標開始截取

NSString*key = [name substringFromIndex:1];

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

id value = dict[key];

// 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型

// 判斷下value是否是字典

if([value isKindOfClass:[NSDictionaryclass]]) {

// 字典轉模型// 獲取模型的類對象,調(diào)用modelWithDict

// 模型的類名已知,就是成員屬性的類型

// 獲取成員屬性類型

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

// 生成的是這種@"@\"User\"" 類型 -》 @"User"? 在OC字符串中 \" -> ",\是轉義的意思,不占用字符// 裁剪類型字符串NSRangerange = [type rangeOfString:@"\""];? ? ?

? ? type = [type substringFromIndex:range.location + range.length];? ? ?

? ? ? range = [type rangeOfString:@"\""];// 裁剪到哪個角標,不包括當前角標type = [type substringToIndex:range.location];

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

Class modelClass =NSClassFromString(type);

if(modelClass) {

// 有對應的模型才需要轉

// 把字典轉模型value? =? [modelClass modelWithDict:value];? ? ? ? ? ? }? ? ? ? }

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

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

if([value isKindOfClass:[NSArrayclass]]) {

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

if([self respondsToSelector:@selector(arrayContainModelClass)]) {/

/ 轉換成id類型,就能調(diào)用任何對象的方法id

id Self =self;

// 獲取數(shù)組中字典對應的模型

NSString*type =? [id ?Self arrayContainModelClass][key];

// 生成模型

Class classModel =NSClassFromString(type);

NSMutableArray*arrM = [NSMutableArray ?array];

// 遍歷字典數(shù)組,生成模型數(shù)組for(NSDictionary*dict in value) {

// 字典轉模型id model =? [classModel modelWithDict:dict];? ? ? ? ? ? ?

? ?[arrM addObject:model];? ? ? ? ? ? ? ? }

// 把模型數(shù)組賦值給value ?value = arrM;? ? ? ? ? ? }? ? ? ? }

if(value) {

// 有值,才需要給模型的屬性賦值

// 利用KVC給模型中的屬性賦值[objc setValue:value forKey:key];? ? ? ? }? ?

}return ? objc;}

@end

五、參考文章

http://www.itdecent.cn/p/e071206103a4

http://www.itdecent.cn/p/adf0d566c887

http://www.itdecent.cn/p/927c8384855a

http://chun.tips/2014/11/05/objc-runtime-1/#more

http://blog.sunnyxx.com/2016/08/13/reunderstanding-runtime-0/

http://blog.csdn.net/wzzvictory/article/details/8624057

http://www.cocoachina.com/ios/20151208/14595.html

http://www.itdecent.cn/p/46dd81402f63

六、后記

說實話,剛開始做開發(fā)是后,也一直聽說runtime,但項目中用的少,直到最近項目不是太忙,才重新看看蘋果的runtime機制,有一種驀然回首,茅塞頓開的感覺,學會runtime,一是可以更好的幫助你理解OC的運行機制。還有一點就是可以裝-B

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,799評論 7 64
  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評論 0 9
  • OC最實用的runtime總結,面試、工作你看我就足夠了! 前言runtime的資料網(wǎng)上有很多了,部分有些晦澀難懂...
    small_Sun閱讀 967評論 1 12
  • 原:還原,把事情還原到對方所處的那個身份,狀態(tài),背景。 涼:諒解,把自己放下,諒解自己。 人特別怕的狀態(tài):拿不起,...
    海過留痕燕過留聲閱讀 714評論 0 1
  • 1、 do what yow say,say what you do.
    魂歸瀟湘閱讀 173評論 0 0

友情鏈接更多精彩內(nèi)容