
正常開發(fā)中runtime要說吧功能確實強(qiáng)大,但是用的卻是很少,主要是有些用不上,有些吧,可以替代,所以就尷尬了。
但是會用runtime確實能解決很多常規(guī)方法很棘手的偏門問題
1.發(fā)送消息
額,這個方法調(diào)用的本質(zhì),就是讓對象發(fā)送消息
2.交換方法
開發(fā)使用場景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。
1.我們通常的方法,繼承系統(tǒng)的類,重寫方法.(此處略)
2.使用runtime,交換方法.
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 需求:給imageNamed方法提供功能,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:寫個分類,定義一個能加載圖片且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實現(xiàn),就能調(diào)用imageWithName,間接調(diào)用imageWithName的實現(xiàn)。
UIImage *image = [UIImage imageNamed:@"speed"];
}
@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:));
// 交換方法地址
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類中重寫系統(tǒng)方法imageNamed,因為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.
// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
// 這里調(diào)用imageWithName,相當(dāng)于調(diào)用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"沒有圖片");
}
return image;
}
@end
3.動態(tài)添加方法
開發(fā)使用場景:如果一個類方法非常多,加載類到內(nèi)存的時候也比較耗費(fèi)資源,需要給每個方法生成映射表,可以使用動態(tài)給某個類,添加方法解決。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Dog *dog = [[Dog alloc] init];
// 默認(rèn)Dog,沒有實現(xiàn)eat方法,可以通過performSelector調(diào)用,但是會報錯。
// 動態(tài)添加方法就不會報錯
[dog performSelector:@selector(eat)];
}
@end
Dog類繼承自NSObjct,.h文件不作任何操作
@implementation Dog
// 默認(rèn)方法都有兩個隱式參數(shù),
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當(dāng)一個對象調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來.
// 剛好可以用來判斷,未實現(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

4.給分類添加屬性(分類正常情況下是不允許添加屬性)
原理:給一個類聲明屬性,其實本質(zhì)就是給這個類添加關(guān)聯(lián),并不是直接把這個值的內(nèi)存空間添加到類存空間。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 給系統(tǒng)NSObject類動態(tài)添加屬性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"Guss";
NSLog(@"%@",objc.name);
}
@end
//.h
#import <Foundation/Foundation.h>
@interface NSObject (Property)
@property (nonatomic ,copy)NSString *name;
@end
//.m
static const char *key = "name";// 定義關(guān)聯(lián)的key
@implementation NSObject (Property)
- (NSString *)name
{
// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
return objc_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
5.字典轉(zhuǎn)模型
1.KVC
其實KVC也是可以字典轉(zhuǎn)模型,應(yīng)該沒用第三方前都是這么做的,這樣的字典轉(zhuǎn)模型弊端:必須保證,模型中的屬性和字典中的key一一對應(yīng)。模型中的屬性和字典的key不一一對應(yīng),系統(tǒng)就會調(diào)用setValue:forUndefinedKey:報錯。
解決:重寫對象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋, 就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。
2.runtime
而用runtime實現(xiàn)字典轉(zhuǎn)模型,其思路是利用運(yùn)行時,遍歷模型中所有成員變量,根據(jù)模型的成員變量,去字典中查找key,取出對應(yīng)的值,給模型的屬性賦值。
3.兩者區(qū)別
兩者的方式本質(zhì)上相反,KVC是通過字典去模型屬性里面去找,而runtime可以做到根據(jù)<成員屬性>去字典里面找,這樣就會更安全
本文就不講這個功能實現(xiàn),因為MJExtersion寫的很好(個人覺得),想具體理解可以看第三方源碼.
總結(jié)
項目中交換方法,動態(tài)添加方法,給分類添加屬性還是能用到的,理解runtime其實能大大增加代碼的性能