本文主要是針對runtime消息轉(zhuǎn)發(fā)進(jìn)行整理,并舉例關(guān)于消息轉(zhuǎn)發(fā)的運(yùn)用汽馋。
消息轉(zhuǎn)發(fā)
1渣磷、消息調(diào)用
OC中發(fā)送消息是通過objc_msgSend(id, SEL, ...) 來實(shí)現(xiàn)的厉膀,首先會(huì)根據(jù)isa所指向的類結(jié)構(gòu)中進(jìn)行方法查找(objc_method_list),如果該類中無法查找到所對應(yīng)的方法兰吟,則會(huì)沿類結(jié)構(gòu)中的超類指針super_class繼續(xù)向上索引诵冒,直至NSObject類亩进。一旦索引到對應(yīng)方法則會(huì)向該方法傳遞receiver對應(yīng)的數(shù)據(jù)結(jié)構(gòu)皂吮,同時(shí)润文,為了優(yōu)化索引速度,系統(tǒng)會(huì)緩存每次成功索引的方法名和實(shí)現(xiàn)地址到類結(jié)構(gòu)中的objc_cache丘侠。后續(xù)的方法索引將優(yōu)先索引緩存中的方法列表庐扫。
通過isa查找遵湖,流程如下:
if (沒有找到cache缺亮、objc_method_list翁涤,向父類索引至NSObject類) {
則去實(shí)現(xiàn)了動(dòng)態(tài)方法方法解析。
}
else if ( 如果沒有實(shí)現(xiàn)動(dòng)態(tài)方法解析或者解析失敗并且實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制) {
進(jìn)入消息轉(zhuǎn)發(fā)流程
}
else 程序crash
2瞬内、動(dòng)態(tài)方法解析
如果調(diào)用的是實(shí)例方法則會(huì)調(diào)起該方法
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
如果調(diào)用的是類方法則會(huì)調(diào)起該方法
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
舉個(gè)??:
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
3迷雪、消息轉(zhuǎn)發(fā)
未能向調(diào)用方法提供具體實(shí)現(xiàn)時(shí)即+ (BOOL)resolveInstanceMethod:(SEL)sel;或+ (BOOL)resolveClassMethod:(SEL)sel;返回值為NO限书。此時(shí)將轉(zhuǎn)入消息轉(zhuǎn)發(fā)流程虫蝶。
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
這個(gè)方法將是消息轉(zhuǎn)發(fā)的最后機(jī)會(huì),我們可以利用它將原有的消息轉(zhuǎn)發(fā)至另外的對象或者忽略倦西。其中參數(shù)anInvocation是基于面向?qū)ο髮υ蟹椒ㄕ{(diào)用的一層封裝能真,包含了方法名、調(diào)用參數(shù)、方法簽名等粉铐。
舉個(gè)??:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];//避免未處理而導(dǎo)致的Crash
}
運(yùn)用
隨著iOS系統(tǒng)版本的更新疼约,部分性能更優(yōu)異或者可讀性更高的API將有可能對原有API進(jìn)行廢棄與更替。因此在開發(fā)中經(jīng)常需要考慮蝙泼、判斷版本程剥,那是不是可以考慮用runtime來進(jìn)行動(dòng)態(tài)處理?
下面主要是針對適配iOS 11 contentInsetAdjustmentBehavior與automaticallyAdjustsScrollViewInsets做栗子
1汤踏、新建一個(gè)Category(RTForwarding)
用于調(diào)用到iOS 11屬性contentInsetAdjustmentBehavior的封裝處理
代碼如下:
//重寫runtime方法
//1.為即將轉(zhuǎn)發(fā)的消息返回一個(gè)對應(yīng)的方法簽名(該簽名后面用于對轉(zhuǎn)發(fā)消息對象(NSInvocation *)anInvocation進(jìn)行編碼用)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { // 1
NSMethodSignature *signature = nil;
if (aSelector == @selector(setContentInsetAdjustmentBehavior:)) {
signature = [UIViewController instanceMethodSignatureForSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)];
}else {
signature = [super methodSignatureForSelector:aSelector];
}
return signature;
}
//2.開始消息轉(zhuǎn)發(fā)((NSInvocation *)anInvocation封裝了原有消息的調(diào)用织鲸,包括了方法名,方法參數(shù)等)
- (void)forwardInvocation:(NSInvocation *)anInvocation { // 2
BOOL automaticallyAdjustsScrollViewInsets = NO;
UIViewController *topmostViewController = [self getTopmostViewController];
//3.由于轉(zhuǎn)發(fā)調(diào)用的API與原始調(diào)用的API不同溪胶,這里我們新建一個(gè)用于消息調(diào)用的NSInvocation對象viewControllerInvocatio并配置好對應(yīng)的target與selector
NSInvocation *viewControllerInvocation = [NSInvocation invocationWithMethodSignature:anInvocation.methodSignature]; // 3
[viewControllerInvocation setTarget:topmostViewController];
[viewControllerInvocation setSelector:@selector(setAutomaticallyAdjustsScrollViewInsets:)];
//4.配置所需參數(shù):由于每個(gè)方法實(shí)際是默認(rèn)自帶兩個(gè)參數(shù)的:self和_cmd搂擦,所以我們要配置其他參數(shù)時(shí)是從第三個(gè)參數(shù)開始配置
[viewControllerInvocation setArgument:&automaticallyAdjustsScrollViewInsets atIndex:2]; // 4
//5.消息轉(zhuǎn)發(fā)
[viewControllerInvocation invokeWithTarget:topmostViewController]; // 5
}
//獲取棧頂控制器
- (UIViewController *)getTopmostViewController {
UIViewController *resultVC;
resultVC = [self getTopmostViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (resultVC.presentedViewController) {
resultVC = [self getTopmostViewController:resultVC.presentedViewController];
}
return resultVC;
}
- (UIViewController *)getTopmostViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [self getTopmostViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [self getTopmostViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
}
2、調(diào)用Category(RTForwarding)
在需要使用的地方導(dǎo)入頭文件
#import "UIScrollView+RTForwarding.h"
在使用到iOS 11屬性contentInsetAdjustmentBehavior時(shí)哗脖,不需要進(jìn)行判斷就可以實(shí)現(xiàn)之前需要判斷功能瀑踢。
代碼如下:
CGSize main = [UIScreen mainScreen].bounds.size;
UITableView * tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, main.width, main.height - 64)];
tableView.delegate = self;
tableView.dataSource = self;
tableView.backgroundColor = [UIColor orangeColor];
tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;//無需判斷,簡單粗暴
[self.view addSubview:tableView];
理解了消息轉(zhuǎn)發(fā)才避,在應(yīng)用上還是能夠出其不意達(dá)到簡單粗暴的效果橱夭。本文主要是參考來源與鏈接,該文作者寫的賊棒桑逝,本文主要是對自己的理解進(jìn)行歸納整理徘钥,讓自己的思路更清晰,當(dāng)然如果有寫的不對的地方歡迎指出肢娘。最后呈础,當(dāng)然也是最重要的附上demo地址。