- 方法調(diào)用的本質(zhì),就是讓對象發(fā)送消息。
-
objc_msgSend,只有對象才能發(fā)送消息,因此以objc開頭。 - 使用消息機(jī)制的前提,必須導(dǎo)入
#import<objc/Runtime.h>
例子
調(diào)用對象方法 [dog eat]
本質(zhì)是讓對象發(fā)送消息 objc_msgSend(dog, @selector(ear));
消息機(jī)制的原理
對象根據(jù)方法編號 SEL 去映射表 Method List 查找對應(yīng)的方法實現(xiàn)

方法交換
例:給 + (instancetype)imageNamed:(NSString *)name 方法提供功能,每次加載圖片就打印 "hello word"
- 先搞個分類,定義一個加載圖片并且打印"hello"
- 在
+ (void)load方法中進(jìn)行方法交換
@implementation UIImage (log)
+ (void)load{
// 獲取兩個方法的方法地址
Method imageNamed = class_getClassMethod(self, @selector(imageNamed:));
Method imageWithName = class_getClassMethod(self, @selector(imageWihtName:));
// 交換方法
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類中重寫系統(tǒng)方法 `+ (instancetype)imageNamed:(NSString *)name` ,應(yīng)為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用 `super`
+ (instancetype)imageWithName:(NSString *)name{
// 這里調(diào)用 `imageWithName` ,相當(dāng)于調(diào)用 `imageName`
UIImage *image = [self imageWithName:name];
NSLog(@"Hello Word");
return image;
}
交換原理:


動態(tài)添加方法
經(jīng)典面試題:有沒有使用過 performSelector 其實主要想問你有沒有動態(tài)添加過方法
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 默認(rèn) `person` 沒有實現(xiàn) `eat` 方法,可以通過 `performSelector` 調(diào)用,但會報錯。
// 動態(tài)添加方法就不會報錯
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// void(*)()
// 默認(rèn)方法都有兩個隱式參數(shù)
void eat(id self, SEL sel){
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當(dāng)一個對象調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(eat)) {
// 動態(tài)添加 `eat` 方法
// @param:給哪個類添加方法
// @param:添加方法的方法編號
// @param:添加方法的函數(shù)實現(xiàn)(函數(shù)地址)
// @param:函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd
class_addmETHOD(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
給分類添加屬性
原理:不用繼承給一個類添加屬性,其實本質(zhì)就是給這個類添加關(guān)聯(lián),并不是直接把這個值的內(nèi)存空間添加到類的內(nèi)存空間
AssociationsManager里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關(guān)聯(lián)對象的,這相當(dāng)于把所有對象的關(guān)聯(lián)對象都存在一個全局map里面。而map的key是這個對象的指針地址,而這個map的value又是另一個AssociationsHasMap,里面保存了關(guān)聯(lián)對象的key value 對
使用runtime Associate 方法關(guān)聯(lián)的對象,在主對象dealloc的時候不需要釋放
1.調(diào)用 -release 應(yīng)用計數(shù)變?yōu)榱?br>
*調(diào)用 [self dealloc]
2.子類調(diào)用 - dealloc
3.NSObject 調(diào)用 - dealloc
*只做一件事:調(diào)用 runtime 的 objec_dispose() 方法
4.object_dispose()
*為C++的實例變量(ivars)調(diào)用destructors
*解除所有使用 runtime Associate 方法關(guān)系的對象
*解除所有 __weak 應(yīng)用
*調(diào)用 free()
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
// 給系統(tǒng) NSObject 類動態(tài)添加屬性 name
NSObject *object = [[NSObject alloc] init];
object.name = @"myName";
}
@end
// 定義關(guān)聯(lián)的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name{
// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name{
// @param: 給那個對象添加關(guān)聯(lián)
// @param: 關(guān)聯(lián)的 key ,通過這個 key 獲取
// @param: 關(guān)聯(lián)的 value
// @param: 關(guān)聯(lián)策略
objc_setAssociatedObject(self, &key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
斷開關(guān)聯(lián)
1. 單獨斷開某 key 的關(guān)聯(lián)
objc_setAssociatedObject(self, &key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
2. 斷開對象的所有關(guān)聯(lián)
objc_removeAssociatedObjects(self)
字典轉(zhuǎn)模型
@implementation NSObject (Model)
+ (instancetype)modelWithDict:(NSDictionary *)dict{
id objc = [[self alloc] init];
// 獲取類中的所有成員屬性
unsigned int count;
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count ; i++) {
Ivar ivar = ivarList[i];
// 獲取屬性名
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *key = [name substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對應(yīng)的value
id value = dict[key];
// 二級轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]]) {
// 獲取成員屬性類型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSRange range = [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];
}
}
// 三級轉(zhuǎn)換
if ([value isKindOfClass:[NSArray class]]) {
// 判斷對應(yīng)類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if ([self instancesRespondToSelector:@selector(arrayContainModelClass)]) {
// 轉(zhuǎn)換成 id類型,就能調(diào)用任何對象的方法
id idSelf = self;
// 獲取數(shù)組中字典對應(yīng)的模型
NSString *type = [idSelf performSelector:@selector(arrayContainModelClass)][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
for (NSDictionary *dict in value) {
id model = [classModel modelWithDict:dict];
[arrM addObject:model];
}
value = arrM;
}
}
if (value) {
[objc setObject:value forKey:key];
}
}
return objc;
}
@end
面試題
向nil發(fā)送消息
runtime會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類的方法列表以及其父類方法列表中尋找方法運行。如果對象為nil,在尋找對象的isa指針時就是0地址,直接返回。
runtime找不到方法時會調(diào)用
resolveInstanceMethod
forwardingTargetForSelector,將無法處理的selector轉(zhuǎn)發(fā)給其他對象