前言:
關(guān)于Runtime的資料網(wǎng)上一搜很多,但總是寫的只言片語,不太全面。最近花了一個星期的時間重新學(xué)習(xí)Runtime,并整理了一個系列文章生音,并發(fā)表出來,同時也感謝開源貢獻(xiàn)的開發(fā)者窒升。這里共有三篇文章:
Runtime系列一:Runtime的前世今生
Runtime系列二:Runtime的原理
Runtime系列三:Runtime在項(xiàng)目中使用場景
一缀遍、方法交換Swizzling
使用場景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能饱须,并且保持原有的功能域醇。
方式一:繼承系統(tǒng)的類,重寫方法.
方式二:使用runtime,交換方法.
在Objective-C中調(diào)用一個方法蓉媳,其實(shí)是向一個對象發(fā)送消息譬挚,而查找消息的唯一依據(jù)是selector的名字。所以酪呻,我們可以利用Objective-C的runtime機(jī)制减宣,實(shí)現(xiàn)在運(yùn)行時交換selector對應(yīng)的方法實(shí)現(xiàn)以達(dá)到我們的目的。每個類都有一個方法列表玩荠,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系蚪腋。IMP有點(diǎn)類似函數(shù)指針丰歌,指向具體的Method實(shí)現(xiàn)
我們先看看SEL與IMP之間的關(guān)系圖:
從上圖可以看出來姨蟋,每一個SEL與一個IMP一一對應(yīng)屉凯,正常情況下通過SEL可以查找到對應(yīng)消息的IMP實(shí)現(xiàn)。
但是眼溶,現(xiàn)在我們要做的就是把鏈接線解開悠砚,然后連到我們自定義的函數(shù)的IMP上。當(dāng)然堂飞,交換了兩個SEL的IMP灌旧,還是可以再次交換回來了。交換后變成這樣的绰筛,如下圖
從圖中可以看出枢泰,我們通過swizzling特性,將selectorC的方法實(shí)現(xiàn)IMPc與selectorN的方法實(shí)現(xiàn)IMPn交換了铝噩,當(dāng)我們調(diào)用selectorC衡蚂,也就是給對象發(fā)送selectorC消息時,所查找到的對應(yīng)的方法實(shí)現(xiàn)就是IMPn而不是IMPc了骏庸。
#import"UIViewController+swizzling.h"
#import@implementationUIViewController(swizzling)//load方法會在類第一次加載的時候被調(diào)用//調(diào)用的時間比較靠前毛甲,適合在這個方法里做方法交換
+ (void)load{//方法交換應(yīng)該被保證,在程序中只會執(zhí)行一次staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{
//獲得viewController的生命周期方法的selector
SEL systemSel =@selector(viewWillAppear:);
//自己實(shí)現(xiàn)的將要被交換的方法的selector
SEL swizzSel =@selector(swiz_viewWillAppear:);
//兩個方法的Method
Method ? systemMethod = class_getInstanceMethod([selfclass], systemSel); ? ??
? Method swizzMethod = class_getInstanceMethod([selfclass], swizzSel);
//首先動態(tài)添加方法具被,實(shí)現(xiàn)是被交換的方法玻募,返回值表示添加成功還是失敗BOOLisAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if(isAdd) {
//如果成功,說明類中不存在這個方法的實(shí)現(xiàn)//將被交換方法的實(shí)現(xiàn)替換到這個并不存在的實(shí)現(xiàn)class_replaceMethod(self,swizzSel,method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));? ? ? ? }
else{
//否則一姿,交換兩個方法的實(shí)現(xiàn)method_exchangeImplementations(systemMethod, swizzMethod);? ? ? ? }? ? });}
- (void)swiz_viewWillAppear:(BOOL)animated{/
/這時候調(diào)用自己七咧,看起來像是死循環(huán)//但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
[self ?swiz_viewWillAppear:animated];
NSLog(@"swizzle");}
@end
在一個自己定義的viewController中重寫viewWillAppear
- (void)viewWillAppear:(BOOL)animated{
? ? [super viewWillAppear:animated];? ?
? ? NSLog(@"viewWillAppear");
}
二、設(shè)置關(guān)聯(lián)值
使用場景:現(xiàn)在你準(zhǔn)備用一個系統(tǒng)的類叮叹,但是系統(tǒng)的類并不能滿足你的需求艾栋,你需要額外添加一個屬性。給一個類聲明屬性衬横,其實(shí)本質(zhì)就是給這個類添加關(guān)聯(lián)裹粤,并不是直接把這個值的內(nèi)存空間添加到類存空間。分類只能添加方法
1.設(shè)置關(guān)聯(lián)值
這種情況的一般解決辦法就是繼承蜂林。
但是遥诉,只增加一個屬性,就去繼承一個類噪叙,總是覺得太麻煩類矮锈。
這個時候,runtime的關(guān)聯(lián)屬性就發(fā)揮它的作用了睁蕾。
1苞笨、添加關(guān)聯(lián)對象
-(void)addAssociatedObject:(id)object{objc_setAssociatedObject(self,@selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}//獲取關(guān)聯(lián)對象-(id)getAssociatedObject{returnobjc_getAssociatedObject(self, _cmd);}
注意:這里面我們把getAssociatedObject方法的地址作為唯一的key债朵,_cmd代表當(dāng)前調(diào)用方法的地址。
參數(shù)說明:
object:與誰關(guān)聯(lián)瀑凝,通常是傳self
key:唯一鍵序芦,在獲取值時通過該鍵獲取,通常是使用static
const void *來聲明
value:關(guān)聯(lián)所設(shè)置的值
policy:內(nèi)存管理策略粤咪,比如使用copy
voidobjc_setAssociatedObject(idobject,constvoid*key, idvalue, objc _AssociationPolicy policy)
2.獲取關(guān)聯(lián)值
參數(shù)說明:
object:與誰關(guān)聯(lián)谚中,通常是傳self,在設(shè)置關(guān)聯(lián)時所指定的與哪個對象關(guān)聯(lián)的那個對象
key:唯一鍵寥枝,在設(shè)置關(guān)聯(lián)時所指定的鍵
idobjc_getAssociatedObject(idobject,constvoid*key)
3.取消關(guān)聯(lián)
voidobjc_removeAssociatedObjects(idobject)
關(guān)聯(lián)策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){OBJC_ASSOCIATION_ASSIGN =0,// 表示弱引用關(guān)聯(lián)宪塔,通常是基本數(shù)據(jù)類型OBJC_ASSOCIATION_RETAIN_NONATOMIC =1,// 表示強(qiáng)引用關(guān)聯(lián)對象,是線程安全的OBJC_ASSOCIATION_COPY_NONATOMIC =3,// 表示關(guān)聯(lián)對象copy囊拜,是線程安全的OBJC_ASSOCIATION_RETAIN =01401,// 表示強(qiáng)引用關(guān)聯(lián)對象某筐,不是線程安全的OBJC_ASSOCIATION_COPY =01403// 表示關(guān)聯(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
// 定義關(guān)聯(lián)的key static const char*key ="name";
@implementationNSObject(Property)
- (NSString*)name{// 根據(jù)關(guān)聯(lián)的key冠跷,獲取關(guān)聯(lián)的值南誊。returnobjc_getAssociatedObject(self, key);}
- (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
三蔽莱、動態(tài)添加方法
使用場景:如果一個類方法非常多弟疆,加載類到內(nèi)存的時候也比較耗費(fèi)資源,需要給每個方法生成映射表盗冷,可以使用動態(tài)給某個類怠苔,添加方法解決。
@implementationViewController
- (void)viewDidLoad {?
? [superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];// 默認(rèn)person仪糖,沒有實(shí)現(xiàn)eat方法柑司,可以通過performSelector調(diào)用,但是會報(bào)錯锅劝。
// 動態(tài)添加方法就不會報(bào)錯[p performSelector:@selector(eat)];}
@end
@implementationPerson//
void(*)()// 默認(rèn)方法都有兩個隱式參數(shù)攒驰,
void eat(idself,SEL sel){NSLog(@"%@ %@",self,NSStringFromSelector(sel));}
// 當(dāng)一個對象調(diào)用未實(shí)現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來.// 剛好可以用來判斷故爵,未實(shí)現(xiàn)的方法是不是我們想要動態(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel ==@selector(eat)) {
// 動態(tài)添加eat方法// 第一個參數(shù):給哪個類添加方法
// 第二個參數(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
四、字典轉(zhuǎn)模型
設(shè)計(jì)模型
模型屬性诬垂,通常需要跟字典中的key一一對應(yīng)
問題:一個一個的生成模型屬性劲室,很慢?
需求:能不能自動根據(jù)一個字典结窘,生成對應(yīng)的屬性很洋。
解決:提供一個分類,專門根據(jù)字典生成對應(yīng)的屬性字符串隧枫。
@implementation ?NSObject(Log)// 自動打印屬性字符串
+ (void)resolveDict:(NSDictionary*)dict{
// 拼接屬性字符串代碼NSMutableString*strM = [NSMutableString string];
// 1.遍歷字典喉磁,把字典中的所有key取出來谓苟,生成對應(yīng)的屬性代碼
[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
字典轉(zhuǎn)模型的方式一:KVC
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
?Status*status= [[self alloc] init];?
? [status setValuesForKeysWithDictionary:dict];
return ?status;
}
@end
KVC字典轉(zhuǎn)模型弊端:必須保證纱皆,模型中的屬性和字典中的key一一對應(yīng)。
如果不一致芭商,就會調(diào)用[ setValue:forUndefinedKey:]
報(bào)key找不到的錯。
分析:模型中的屬性和字典的key不一一對應(yīng)搀缠,系統(tǒng)就會調(diào)用setValue:forUndefinedKey:報(bào)錯铛楣。
解決:重寫對象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋,
就能繼續(xù)使用KVC艺普,字典轉(zhuǎn)模型了簸州。
-(void)setValue:(id)valueforUndefinedKey:(NSString*)key{}
字典轉(zhuǎn)模型的方式二:Runtime
思路:利用運(yùn)行時,遍歷模型中所有屬性歧譬,根據(jù)模型的屬性名岸浑,去字典中查找key,取出對應(yīng)的值瑰步,給模型的屬性賦值矢洲。
步驟:提供一個NSObject分類,專門字典轉(zhuǎn)模型缩焦,以后所有模型都可以通過這個分類轉(zhuǎn)读虏。
@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
{
// 思路:遍歷模型中所有屬性-》使用運(yùn)行時
// 0.創(chuàng)建對應(yī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ù)角標(biāo)揩徊,從數(shù)組取出對應(yīng)的成員屬性Ivar ivar = ivarList[i];
// 獲取成員屬性名
NSString*name = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 處理成員屬性名->字典中的key
// 從第一個角標(biāo)開始截取
NSString*key = [name substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應(yīng)的value
id value = dict[key];
// 二級轉(zhuǎn)換:如果字典中還有字典,也需要把對應(yīng)的字典轉(zhuǎn)換成模型
// 判斷下value是否是字典
if([value isKindOfClass:[NSDictionaryclass]]) {
// 字典轉(zhuǎn)模型// 獲取模型的類對象嵌赠,調(diào)用modelWithDict
// 模型的類名已知塑荒,就是成員屬性的類型
// 獲取成員屬性類型
NSString*type = [NSString ?stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是這種@"@\"User\"" 類型 -》 @"User"? 在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思猾普,不占用字符// 裁剪類型字符串NSRangerange = [type rangeOfString:@"\""];? ? ?
? ? type = [type substringFromIndex:range.location + range.length];? ? ?
? ? ? range = [type rangeOfString:@"\""];// 裁剪到哪個角標(biāo)袜炕,不包括當(dāng)前角標(biāo)type = [type substringToIndex:range.location];
// 根據(jù)字符串類名生成類對象
Class modelClass =NSClassFromString(type);
if(modelClass) {
// 有對應(yīng)的模型才需要轉(zhuǎn)
// 把字典轉(zhuǎn)模型value? =? [modelClass modelWithDict:value];? ? ? ? ? ? }? ? ? ? }
// 三級轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型
// 判斷值是否是數(shù)組
if([value isKindOfClass:[NSArrayclass]]) {
// 判斷對應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if([self respondsToSelector:@selector(arrayContainModelClass)]) {/
/ 轉(zhuǎn)換成id類型初家,就能調(diào)用任何對象的方法id
id Self =self;
// 獲取數(shù)組中字典對應(yīng)的模型
NSString*type =? [id ?Self arrayContainModelClass][key];
// 生成模型
Class classModel =NSClassFromString(type);
NSMutableArray*arrM = [NSMutableArray ?array];
// 遍歷字典數(shù)組偎窘,生成模型數(shù)組for(NSDictionary*dict in value) {
// 字典轉(zhuǎn)模型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.reibang.com/p/e071206103a4
http://www.reibang.com/p/adf0d566c887
http://www.reibang.com/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.reibang.com/p/46dd81402f63
六陌知、后記
說實(shí)話他托,剛開始做開發(fā)是后,也一直聽說runtime,但項(xiàng)目中用的少仆葡,直到最近項(xiàng)目不是太忙赏参,才重新看看蘋果的runtime機(jī)制,有一種驀然回首沿盅,茅塞頓開的感覺把篓,學(xué)會runtime,一是可以更好的幫助你理解OC的運(yùn)行機(jī)制。還有一點(diǎn)就是可以裝-B