runtime 的幾個(gè)應(yīng)用場(chǎng)景:
- 消息轉(zhuǎn)發(fā)
- method siwizzling
- 歸解檔瀑踢、模式互轉(zhuǎn)
- 自定義KVO
消息轉(zhuǎn)發(fā)
消息轉(zhuǎn)發(fā)機(jī)制的流程:
- 動(dòng)態(tài)方法解析
- 快速轉(zhuǎn)發(fā)
- 慢速轉(zhuǎn)發(fā)(也就是完整的消息轉(zhuǎn)發(fā)流程)
動(dòng)態(tài)方法解析
給person類的.h添加一個(gè)方法yy_sendMessage
耻矮,但是沒有實(shí)現(xiàn)善镰,
運(yùn)行[[Person new] yy_sendMessage:@"yy_msg"];
這個(gè)方法會(huì)報(bào)錯(cuò):Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person yy_sendMessage:]: unrecognized selector sent to instance 0x6000025e0b50'
動(dòng)態(tài)方法解析可以實(shí)現(xiàn)動(dòng)態(tài)的為當(dāng)前類添加方法:
@implementation Person
void yy_sendMessage(id self, SEL _cmd, NSString *msg)
{
NSLog(@"person--%@",msg);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"yy_sendMessage:"]) {
BOOL flag = class_addMethod(self, sel, (IMP)yy_sendMessage, "v@:@");
return flag;
}
return NO;
}
@end
再次運(yùn)行嗦枢,程序正常并打印消息
person--yy_msg
快速轉(zhuǎn)發(fā)
如果Person類沒有實(shí)現(xiàn)resolveInstanceMethod:
方法,或者返回NO,可以通過快速轉(zhuǎn)發(fā)的方式,把消息發(fā)給別的對(duì)象航瞭,這里把消息轉(zhuǎn)給Car,前提是Car要有實(shí)現(xiàn)這個(gè)方法
@interface Car : NSObject
- (void)yy_sendMessage:(NSString *)msg;
@end
@implementation Car
- (void)yy_sendMessage:(NSString *)msg
{
NSLog(@"car--%@",msg);
}
@end
@implementation Person
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *methodName = NSStringFromSelector(aSelector);
if ([methodName isEqualToString:@"yy_sendMessage:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = anInvocation.selector;
Car *car = [Car new];
if ([car respondsToSelector:sel]) {
[anInvocation invokeWithTarget:car];
}else{
[super forwardInvocation:anInvocation];
}
}
@end
打印結(jié)果:
car--yy_msg
重寫doesNotRecognizeSelector:
如果forwardInvocation:
沒有找到合適的消息處理者坦辟,重寫doesNotRecognizeSelector:
可以讓app繼續(xù)運(yùn)行:
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"找不到方法刊侯,app繼續(xù)運(yùn)行");
}
method siwizzling
方法交換,就是把我們的方法O和系統(tǒng)的方法S交換锉走,在執(zhí)行S方法的時(shí)候滨彻,及時(shí)運(yùn)行的是O方法的邏輯。
@interface UITableView (YYDefaultDisplayView)
@property (nonatomic, strong) UILabel *nodataTipsView;
@end
@implementation UITableView (YYDefaultDisplayView)
+ (void)load{
static dispatch_once_t onceToken;
// 確保只會(huì)執(zhí)行一次 (防止調(diào)皮的童鞋手動(dòng)再調(diào)用load方法)
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod(self, @selector(reloadData));
Method swizzlingMethod = class_getInstanceMethod(self, @selector(yy_reloadData));
// 互換方法
method_exchangeImplementations(originMethod, swizzlingMethod);
});
}
// 臥底
- (void)yy_reloadData{
// yy_reloadData實(shí)際指向reloadData挪蹭,相當(dāng)于調(diào)用系統(tǒng)的方法
[self yy_reloadData];
// 這里添加我們想要做的事情
[self showDefaultVeiw];
}
- (void)showDefaultVeiw{
id<UITableViewDataSource> dataSource = self.dataSource;
NSInteger section = [dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)] ? [dataSource numberOfSectionsInTableView:self] : 1;
NSInteger row = 0;
for (NSInteger i= 0; i < section; i ++) {
row = [dataSource tableView:self numberOfRowsInSection:section];
}
if (row == 0) {
self.nodataTipsView = [[UILabel alloc] init];
self.nodataTipsView.text = @"暫時(shí)無數(shù)據(jù)亭饵,再刷新試試?";
self.nodataTipsView.backgroundColor = UIColor.yellowColor;
self.nodataTipsView.textAlignment = NSTextAlignmentCenter;
self.nodataTipsView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
[self addSubview:self.nodataTipsView];
}else{
self.nodataTipsView.hidden = YES;
}
}
#pragma mark - getting && setting
- (void)setNodataTipsView:(UILabel *)nodataTipsView
{
objc_setAssociatedObject(self, @selector(nodataTipsView), nodataTipsView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UILabel *)nodataTipsView
{
return objc_getAssociatedObject(self, _cmd);
}
@end
這里有幾個(gè)值得思考的點(diǎn):
- 為什么選擇在
laod
方法里交換方法梁厉? - 為什么要確保執(zhí)行一次辜羊?
-
yy_reloadData
里又調(diào)用了yy_reloadData
,會(huì)造成死循環(huán)麼词顾? - set方法和get方法里八秃,使用了_cmd作為關(guān)聯(lián)key,為什么肉盹?(都是
const void *key
類型)
詳情見這里
歸解檔/模式互轉(zhuǎn)
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
- (NSDictionary *)convertModelToDictionary;
@end
#import "Person.h"
#import <objc/message.h>
@implementation Person
// 字典->模型
- (instancetype)initWithDictionary:(NSDictionary *)dictionary{
self = [super init];
if (self) {
for (NSString *key in dictionary.allKeys) {
// 通過key構(gòu)建set方法
NSString *methodName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
SEL sel = NSSelectorFromString(methodName);
if (sel) {
/*
指針函數(shù)的形式:
returnType (*functionName) (param1, param2, ...)
void (*)(id, SEL, id)
使用指針調(diào)用函數(shù):
(returnType (*functionName) (param1, param2, ...))
*/
NSString *value = dictionary[key];
((void (*)(id, SEL, id))objc_msgSend)(self, sel, value);
}
}
}
return self;
}
// 模型->字典
- (NSDictionary *)convertModelToDictionary{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &count);
if (count == 0) {
free(properties);
return nil;
}
NSMutableDictionary *dic = NSMutableDictionary.dictionary;
for (int i = 0; i < count; i ++) {
const char *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
SEL sel = NSSelectorFromString(name);
if (sel) {
// 通過get方法獲取value
NSString *value = ((id (*)(id, SEL))objc_msgSend)(self, sel);
if (value) {
dic[name] = value;
}else{
dic[name] = @"";
}
}
}
// 釋放
free(properties);
return dic;
}
@end
// 測(cè)試:
NSDictionary *dic = @{@"name": @"iO骨灰級(jí)菜鳥", @"age": @(18)};
Person *p = [[Person alloc] initWithDictionary:dic];
NSLog(@"name: %@ age:%@",p.name, p.age);
NSDictionary *dic2 = [p convertModelToDictionary];
NSLog(@"dic2:%@",dic2);
// 打游羟:
name: iO骨灰級(jí)菜鳥 age:18
dic2:{
age = 18;
name = "iO\U9aa8\U7070\U7ea7\U83dc\U9e1f";
}
person
類提供了字典和模型互轉(zhuǎn)的方法,分別對(duì)應(yīng)屬性的set方法和get方法上忍。核心代碼是通過函數(shù)指針的方式運(yùn)行objc_sendMsg()
方法骤肛。
字典轉(zhuǎn)模型里,注意方法函數(shù)名字大寫的拼接處理睡雇,改進(jìn)的空間有:
- 兼容性處理:判斷參數(shù)是否是字典萌衬,字典多層嵌套等
- 性能優(yōu)化:緩存結(jié)果饮醇、使用更底層的函數(shù)以提高性能等
模型轉(zhuǎn)字典里它抱,注意釋放變量。
自定義KVO
KVO的底層實(shí)現(xiàn)也是基于runtime朴艰。當(dāng)一個(gè)對(duì)象Obj被監(jiān)聽的時(shí)候观蓄,系統(tǒng)會(huì)為Obj創(chuàng)建一個(gè)子類,并加上一個(gè)前綴NSKVONotifing_Obj祠墅。接著把isa指針也改為指向新的子類侮穿,所以蘋果說不要通過isa來判斷這個(gè)類的真實(shí)類型。同時(shí)毁嗦,Apple 還重寫了 -class
方法亲茅,企圖欺騙我們這個(gè)類沒有變,就是原本那個(gè)類。當(dāng)然克锣,還要重寫set方法茵肃,在set方法里實(shí)現(xiàn)通知的邏輯。具體實(shí)現(xiàn)可以看我的這篇博文