前言
因?yàn)榍靶┤兆訉懥藗€(gè)關(guān)于導(dǎo)航欄控制器的Demo地址在這,開篇我想先稍微講一下這個(gè)琐旁,我是覺得原生的導(dǎo)航欄在UI如此豐富以及多層VC的情形下,導(dǎo)航條的顏色猜绣、按鈕灰殴、標(biāo)題、隱藏等定性的修改顯得不夠圓滑掰邢,因此就想采用一種透明的方式牺陶,將VC用NC包裝一層再push出去,這里就用到了AssociatedObjects尸变,為所推的VC添加了屬性义图。關(guān)聯(lián)對(duì)象只是運(yùn)行時(shí)中的一點(diǎn),本篇文章想就關(guān)聯(lián)對(duì)象和運(yùn)行時(shí)的一些其他常見用法姑且談?wù)勎嶂抟娬倮茫麙伌u引玉。
正文
其實(shí)網(wǎng)上關(guān)于運(yùn)行時(shí)的東西多如牛毛娃承,但感覺都像在一遍一遍的嚼舌根又不好理解奏夫,我就坦誠相見,拒絕抽象历筝。
#import <objc/runtime.h>
運(yùn)行時(shí)其實(shí)就是用C編寫的我們oc的基石酗昼,我們通過運(yùn)行時(shí)所提供的方法等可以跨越oc層直接與C交互,當(dāng)然對(duì)性能也會(huì)有所提升梳猪。運(yùn)行時(shí)會(huì)對(duì)一個(gè)類進(jìn)行完全的分解麻削,將類或者對(duì)象的每一個(gè)部分抽象成一種類型蒸痹,如果把oc的類比作一個(gè)組裝機(jī)器人,那他就會(huì)被運(yùn)行時(shí)拆分為手臂呛哟、腿叠荠、身體等,我們可以通過運(yùn)行時(shí)直接獲取到機(jī)器人的手臂一樣扫责,這對(duì)于操作一個(gè)類的屬性或者方法是非常方便的榛鼎。
我們在開發(fā)中切實(shí)可以用到的一些場景我做了歸納,下面一一講解:
1鳖孤、關(guān)聯(lián)對(duì)象
關(guān)聯(lián)對(duì)象相關(guān)的函數(shù)主要有三個(gè)者娱,命名相當(dāng)友好到一看就知道其實(shí)就是get/set方法,我們可以在category中使用它們實(shí)現(xiàn)動(dòng)態(tài)向類中添加屬性和方法苏揣。
- objc_setAssociatedObject
- objc_getAssociatedObject
- objc_removeAssociatedObjects
看一個(gè)添加屬性的例子黄鳍,我們創(chuàng)建一個(gè)NSObject的分類CategoryProperty:
@interface NSObject (CategoryProperty)
@property (nonatomic, strong) NSObject *property;
@end
@implementation NSObject (CategoryProperty)
- (NSObject *)property {
return objc_getAssociatedObject(self, @selector(property));
}
- (void)setProperty:(NSObject *)value {
objc_setAssociatedObject(self, @selector(property), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
key值
這三個(gè)函數(shù)的參數(shù)key值推薦三種命名方式:
- 聲明
static char kAssociatedObjectKey
,使用&kAssociatedObjectKey
作為key
值; - 聲明
static void *kAssociatedObjectKey
=&kAssociatedObjectKey
平匈,使用kAssociatedObjectKey
作為key
值框沟; - 用
selector
,使用getter
方法的名稱作為key
值吐葱。
上面的例子用的是第三種方法街望,省的命名了也算簡單。
關(guān)聯(lián)策略
至于關(guān)聯(lián)策略有五種可供選擇弟跑,有強(qiáng)弱引用和原子非原子的區(qū)分灾前,在絕大多數(shù)情況下,我們都會(huì)使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
的關(guān)聯(lián)策略孟辑,這可以保證我們持有關(guān)聯(lián)對(duì)象不會(huì)被過早的釋放哎甲。
在看一個(gè)添加方法的例子,我們創(chuàng)建一個(gè)UIButton的分類block:
typedef void (^btnBlock)();
@interface UIButton (block)
- (void)handelWithBlock:(btnBlock)block;
@end
static const char btnKey;
@implementation UIButton (block)
- (void)handelWithBlock:(btnBlock)block{
if (block){
objc_setAssociatedObject(self, &btnKey, block, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[self addTarget:self action:@selector(btnAction) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnAction{
btnBlock block = objc_getAssociatedObject(self, &btnKey);
block();
}
@end
這樣我們就為button添加了一個(gè)block的方法饲嗽,在調(diào)用button的時(shí)候就可以直接用handelWithBlock來回調(diào)了炭玫。
2、方法交換
顧名思義貌虾,就是兩個(gè)方法執(zhí)行交換吞加,我們建一個(gè)UIViewController的分類VCCategory:
@implementation UIViewController (VCCategory)
+ (void)load
{
//方法交換應(yīng)該被保證在程序中只會(huì)執(zhí)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL systemSel = @selector(viewWillAppear:);
SEL henvySel = @selector(hl_viewWillAppear:);
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method henvyMethod = class_getInstanceMethod([self class], henvySel);
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(henvyMethod), method_getTypeEncoding(henvyMethod));
if (isAdd) {
//如果成功,說明類中不存在這個(gè)方法的實(shí)現(xiàn)
//將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
class_replaceMethod(self, henvySel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否則尽狠,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(systemMethod, henvyMethod);
}
});
}
- (void)hl_viewWillAppear:(BOOL)animated{
//這里自己調(diào)用自己衔憨,表面上循環(huán)引用其實(shí)已經(jīng)被viewWillAppear替換掉了
[self hl_viewWillAppear:animated];
NSLog(@"henvy");
}
@end
主要看method_exchangeImplementations(systemMethod, henvyMethod),該函數(shù)將系統(tǒng)的方法和我的方法進(jìn)行了交換袄膏,這個(gè)時(shí)候在一個(gè)自己定義的viewController中viewWillAppear方法中就可以看到輸出henvy践图。這一點(diǎn)在我們需求需要更改的時(shí)候,但又不想改變原來的方法的時(shí)候是非常有用的沉馆。
3码党、發(fā)送消息
發(fā)送消息即objc_msgSend方法很簡單德崭,這里就舉個(gè)很簡單的例子,比如你要調(diào)用形如一下的一個(gè)方法,
//類揖盘、方法眉厨、參數(shù)
[someObject messageName:parameter];
還可以用objc_msgSend寫作為:
objc_msgSend(someObject,@selector(messageName),parameter);
4扣讼、字典轉(zhuǎn)模型
KVC是把字典中所有值給模型的屬性賦值缺猛,這個(gè)是要求字典中的Key必須要在模型里能找到相應(yīng)的值,如果找不到就會(huì)報(bào)錯(cuò)椭符,因此我們可以通過重寫KVC中的forUndefinedKey這個(gè)方法荔燎。當(dāng)然我們可以通過runtime的方式去實(shí)現(xiàn),類似于KVC逆操作销钝,先通過遍歷模型的值有咨,無誤后再賦值,這里新建一個(gè)模型ModelClass:
+ (instancetype)modelWithDict:(NSDictionary *)dict{
id objc = [[self alloc] init];
// count:成員變量個(gè)數(shù)
unsigned int count = 0;
// 獲取成員變量數(shù)組
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for (int i = 0; i < count; i++) {
// 獲取成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 獲取成員變量類型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 格式化
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 獲取key蒸健,序列為1是因?yàn)?_ivarName"
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找對(duì)應(yīng)value
id value = dict[key];
// 判斷下value是否是字典
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 獲取類
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}
// 給模型中屬性賦值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
寫在最后
總體上來說運(yùn)行時(shí)在開發(fā)中比較常用的到的場景我就先總結(jié)這么多座享,當(dāng)然也歡迎大神能夠來補(bǔ)充是最好不過,文章中的測試代碼我都寫在前言的Demo里了似忧,同時(shí)也歡迎到我的Github討論渣叛,如果本文有什么不太對(duì)的地方,也請一定要給我指正盯捌,感激不盡淳衙!
好了寫了這么多有點(diǎn)累洗洗睡了,祝大家晚安饺著!另外送上一首歌【李志—墻上的向日葵】