初次接觸RunTime,記錄下自己的學(xué)習(xí)心得,為后來(lái)者鋪平道路,提供一個(gè)學(xué)習(xí)的切入點(diǎn)沟饥。
首先簡(jiǎn)單的介紹下RunTime,它就是傳說(shuō)中的運(yùn)行時(shí)迅涮,為什么是傳說(shuō)中的呢废赞?因?yàn)槲覀兪状温牭竭\(yùn)行時(shí)大多都是從他人口中,只知道很底層的東西叮姑,非常難以理解唉地。好了,上面都是廢話传透,我們先要了解蘋果的Object-C是面向運(yùn)行時(shí)的語(yǔ)言耘沼,是動(dòng)態(tài)的,它把編譯和鏈接期所做的事推遲到運(yùn)行時(shí)才進(jìn)行處理朱盐,給了我們很大的靈活性群嗤,可以進(jìn)行消息的轉(zhuǎn)發(fā),替換方法等兵琳。
理論上的東西說(shuō)的再多狂秘,不會(huì)用都是瞎掰骇径,我相信用的多了,慢慢自己也會(huì)深究其理者春,自然也就懂了破衔。那么RunTime可以用來(lái)做什么呢?
一钱烟、動(dòng)態(tài)添加方法的實(shí)現(xiàn)
先簡(jiǎn)單了解一下方法的調(diào)用機(jī)制晰筛,以Person類為例:
以對(duì)象方法為例當(dāng)調(diào)用eat方法時(shí)[p eat],底層會(huì)調(diào)用[p performSelector:@selector(eat)]方法忠售,編譯器再將代碼轉(zhuǎn)化為objc_msgSend(p, @selector(eat));
當(dāng)使用類方法調(diào)用時(shí)传惠,eat為類方法,[Person eat]會(huì)轉(zhuǎn)化為[[Person class] performSelector:@selector(eat)]方法稻扬,然后編譯器再將代碼轉(zhuǎn)化為objc_msgSend(p, @selector(eat));
那么當(dāng)我們只是對(duì)方法進(jìn)行了聲明卦方,而沒有實(shí)現(xiàn)時(shí),編譯時(shí)會(huì)有警告而不會(huì)報(bào)錯(cuò)泰佳。我們都知道在運(yùn)行時(shí)系統(tǒng)找不到相應(yīng)的方法實(shí)現(xiàn)盼砍,程序就會(huì)崩潰。但系統(tǒng)給了我們機(jī)會(huì)來(lái)防止崩潰逝她,在崩潰之前系統(tǒng)會(huì)來(lái)到攔截調(diào)用浇坐。攔截調(diào)用就是系統(tǒng)在找不到調(diào)用的方法,程序崩潰之前調(diào)用的方法黔宛。
當(dāng)調(diào)用了沒有實(shí)現(xiàn)的對(duì)象方法時(shí)近刘,就會(huì)調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel方法。當(dāng)調(diào)用了沒有實(shí)現(xiàn)的類方法時(shí)臀晃,就會(huì)調(diào)用+(BOOL)resolveClassMethod:(SEL)sel方法觉渴。通過這兩個(gè)方法就可以知道哪些方法沒有實(shí)現(xiàn),從而動(dòng)態(tài)添加方法徽惋。參數(shù)sel即表示沒有實(shí)現(xiàn)的方法案淋。
一個(gè)objective - C方法最終都是一個(gè)C函數(shù),默認(rèn)任何一個(gè)方法都有兩個(gè)參數(shù)险绘。self : 方法調(diào)用者 _cmd : 調(diào)用方法編號(hào)踢京。我們可以使用函數(shù)class_addMethod為類添加一個(gè)方法以及實(shí)現(xiàn)。
代碼實(shí)現(xiàn):需要在Person類的實(shí)現(xiàn)方法中調(diào)用
+(BOOL)resolveInstanceMethod:(SEL)sel{
// 動(dòng)態(tài)添加eat方法
// 首先判斷sel是不是eat方法 也可以轉(zhuǎn)化成字符串進(jìn)行比較宦棺。
if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
/**
來(lái)看一下class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types) 的參數(shù):
第一個(gè)參數(shù): cls:給哪個(gè)類添加方法
第二個(gè)參數(shù): SEL name:添加方法的編號(hào)
第三個(gè)參數(shù): IMP imp: 方法的實(shí)現(xiàn)瓣距,函數(shù)入口,函數(shù)名可與方法名不同(建議與方法名相同)
第四個(gè)參數(shù): types :方法類型渺氧,需要用特定符號(hào)旨涝,參考API
示例:v -> void 表示無(wú)返回值, @ -> object 表示id參數(shù),: -> method selector 表示SEL
*/
class_addMethod(self, sel, (IMP)eat , "v@:");
// 處理完返回YES
return YES;
}
return [super resolveInstanceMethod:sel];
}
?void eat (id self ,SEL _cmd){// 實(shí)現(xiàn)內(nèi)容
NSLog(@"%@的%@方法動(dòng)態(tài)實(shí)現(xiàn)了",self,NSStringFromSelector(_cmd));
}
二白华、動(dòng)態(tài)為分類添加屬性
RunTime提供了動(dòng)態(tài)添加屬性和獲得屬性的方法:
設(shè)置關(guān)聯(lián)屬性的值:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); ?參數(shù)介紹:參數(shù)1 ? 為哪個(gè)對(duì)象關(guān)聯(lián)屬性?
? ? ? ? ? ? ? ? ? ?參數(shù)2 ?可以自定義key值慨默,用來(lái)獲取被關(guān)聯(lián)屬性的值
? ? ? ? ? ? ? ? ? 參數(shù)3 ? 關(guān)聯(lián)的值,也就是set方法傳入的值給屬性去保存弧腥。
? ? ? ? ? ? ? ? ? 參數(shù)4 ? 屬性的保存方式厦取,常用OBJC_ASSOCIATION_RETAIN_NONATOMIC
獲取關(guān)聯(lián)屬性的值:
objc_getAssociatedObject(id object, const void *key);
參數(shù)介紹:參數(shù)1 ? 為哪個(gè)對(duì)象關(guān)聯(lián)屬性
? ? ? ? ? ? ? ? ? ?參數(shù)2 ? 通過自定義的key值獲取被關(guān)聯(lián)屬性的值
示例:為UIImageview的分類添加屬性@property(copy,nonatomic)NSString *imageName;
// 給分類添加屬性
- (void)setImageName:(NSString *)imageName {
//使用關(guān)聯(lián)對(duì)象設(shè)置值
objc_setAssociatedObject(self, @"name", imageName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)imageName {
//獲取關(guān)聯(lián)對(duì)象設(shè)置的值
NSString *imgName = objc_getAssociatedObject(self, @"name");
return imgName;
}
三、動(dòng)態(tài)獲取一個(gè)類中的成員變量管搪、屬性虾攻、方法、協(xié)議
需要?jiǎng)?chuàng)建NSObject的分類更鲁,這樣就可以獲取所有類的員變量霎箍、屬性、方法澡为、協(xié)議漂坏,并且與KVC配合可以修改系統(tǒng)的類
1)獲取屬性列表
const void * kAssociateObjectKey = @"kAssociateObjectKey";// key值用來(lái)做標(biāo)識(shí)的
+ (NSArray *)yk_propertyArray
{//關(guān)聯(lián)屬性
NSArray *prolist = objc_getAssociatedObject(self, kAssociateObjectKey);
if (prolist != nil) {
return prolist;
}
unsigned int count ;
objc_property_t *protertylist =? class_copyPropertyList([self class], &count);
//臨時(shí)可變數(shù)組
NSMutableArray *arrM = [NSMutableArray array];
for (NSInteger i = 0; i < count ; i ++ ){
? ? ?//取出屬性
? ? ?objc_property_t proterty =? protertylist[i];
? ? ? //獲取屬性名
? ? ? const char *name = property_getName(proterty);
? ? ? NSString *nameStr = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
? ? ? ?[arrM addObject:nameStr];
? ? ? }
// 設(shè)置關(guān)聯(lián)屬性
objc_setAssociatedObject(self, kAssociateObjectKey, arrM.copy,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//釋放內(nèi)存
free(protertylist);
return arrM.copy;
}
2)獲取成員變量列表
// 獲取成員變量
+(NSArray *)yk_ivarArray
{
unsigned int count;
Ivar *ivarList = class_copyIvarList([self class], &count);
NSMutableArray *arr = [NSMutableArray array];
for (NSInteger i = 0; i < count ; i ++ ) {
? ? ? // 取出成員變量
? ? ? Ivar ivar = ivarList[i];
? ? ?// 獲取成員變量名
? ? ? const char *name = ivar_getName(ivar);
? ? ?// 將C語(yǔ)言字符串轉(zhuǎn)化為OC字符串
? ? ?NSString *ivarStr = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
? ? ? NSLog(@"ivar = %@",ivarStr);
? ? ? [arr addObject:ivarStr];
? ? ? }
free(ivarList);
//? ? NSLog(@"%zd",count);
return arr.copy;
}
3)獲取方法列表
//獲取方法名
+ (NSArray *)yk_methodList{
unsigned int count;
Method *list = class_copyMethodList([self class], &count);
NSMutableArray *arr = [NSMutableArray array];
for (NSInteger i = 0; i < count ; i++ ){
? ? ? ? // 獲取方法結(jié)構(gòu)體
? ? ? ?Method m = list[i];
? ? ? ?// 獲取方法名
? ? ? ?SEL selector = method_getName(m);
? ? ? ? // 轉(zhuǎn)化為字符串
? ? ? ?NSString *name = NSStringFromSelector(selector);
? ? ? ?[arr addObject:name];
? ? ? ?}
free(list);
return arr.copy;
}
4) 獲取協(xié)議列表
// 獲取協(xié)議名
+ (NSArray *)yk_protocolList{
?unsigned int count;
// 返回值需要標(biāo)記
__unsafe_unretained? Protocol ** proList = class_copyProtocolList([self class], &count);
NSMutableArray *arr = [NSMutableArray array];
//遍歷求協(xié)議名
for (NSInteger i = 0; i < count ; i ++ ){
? ? ? ? ?Protocol *pro = proList[i];
? ? ? ? ?const char *name = protocol_getName(pro);
? ? ? ? ? NSString *nameStr = [NSString stringWithCString:name ? ? ? ? ? ? encoding:NSUTF8StringEncoding];
? ? ? ? ? [arr addObject:nameStr];
}
free(proList);
return arr.copy;
}
四、在獲取屬性列表的基礎(chǔ)上實(shí)現(xiàn)簡(jiǎn)單的字典轉(zhuǎn)模型
// 字典數(shù)組轉(zhuǎn)模型 以字典轉(zhuǎn)模型為基礎(chǔ)的
+ (NSArray *)modelWithDictArray:(NSArray *)array
{
if (array.count == 0) {
NSLog(@"數(shù)組為空媒至!");
return nil;
}
NSAssert([array[0] isKindOfClass:[NSDictionary class]], @"數(shù)組內(nèi)容必須為字典");
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary *dict in array) {
? ? ? ? // 創(chuàng)建模型對(duì)象
? ? ? ? id instance = [self modelWithDict:dict];
? ? ? ?[arr addObject:instance];
? ? ? ? ?}
return arr.copy;
}
// 字典轉(zhuǎn)模型
+(instancetype)modelWithDict:(NSDictionary *)dict
{
id instance = [[self alloc] init];
//獲取屬性列表 不能每次都用調(diào)用獲取屬性列表的方法 效率太低
NSArray *prolist = [self yk_propertyArray];
//遍歷字典
[dict enumerateKeysAndObjectsUsingBlock:^(id? _Nonnull key, id? _Nonnull obj, BOOL * _Nonnull stop) {
// 判斷字典屬性是否在屬性列表中
if ([prolist containsObject:key]) {
[instance setValue:obj forKey:key];
}
}];
return instance;
}
提供GitHub上demo的下載地址:鏈接