注:本文不是原創(chuàng)斩萌,只是在學(xué)習(xí)中做的整理和筆記抹沪,以便自己以后更好的復(fù)習(xí)纵菌。原文來自runtime從入門到精通系列洋闽。
在開發(fā)過程中玄柠,可能有如下的需求:
- 在tableView類型的展示列表中,點(diǎn)擊每個cell中人物頭像都可以跳轉(zhuǎn)到人物詳情诫舅,可參見微博中的頭像羽利,同理包括轉(zhuǎn)發(fā)、評論按鈕刊懈、各種鏈接及l(fā)inkcard铐伴。
- 跳轉(zhuǎn)到任意頁面
(1)產(chǎn)品要求,某個頁面的不同banner圖俏讹,點(diǎn)擊可以跳轉(zhuǎn)到任何一個頁面当宴,可能是原生的頁面A、頁面B泽疆,或者是web頁C户矢。
(2)在web頁面,可以跳轉(zhuǎn)到任何一個原生頁面殉疼。
(3)在遠(yuǎn)程推送中跳轉(zhuǎn)到任意指定的頁面梯浪。
以上2種需求,我想大多數(shù)開發(fā)者都遇到過瓢娜,并且可以實(shí)現(xiàn)這種功能挂洛。畢竟,這是比較基礎(chǔ)的功能眠砾。但是代碼未必那么優(yōu)雅虏劲。
一般處理辦法:
針對 1:一般初學(xué)者會用target或者block等方法在tableView的代理方法拿到事件,并把要執(zhí)行的跳轉(zhuǎn)寫到controller里褒颈。功能是可以實(shí)現(xiàn)的柒巫,但問題是這種cell及相似的cell(布局有些變化,或者多幾個少幾個控件)一般出現(xiàn)在多個頁面谷丸。這樣的話相同的代碼就會出現(xiàn)在多個地方堡掏。就算把跳轉(zhuǎn)方法抽取出來寫成category,但是target或者block總是每個地方都要寫的刨疼。
針對 2:初級的方法是每個地方寫一坨判斷及跳轉(zhuǎn)泉唁,高級一些是抽取出來寫在基類或者category鹅龄。
優(yōu)雅的解決辦法(利用runtime):
利用runtime動態(tài)生成對象、屬性亭畜、方法這特性扮休,我們可以先跟服務(wù)端商量好,定義跳轉(zhuǎn)規(guī)則贱案,比如要跳轉(zhuǎn)到A控制器肛炮,需要傳屬性id、type宝踪,那么服務(wù)端返回字典給我侨糟,里面有控制器名,兩個屬性名跟屬性值瘩燥,客戶端就可以根據(jù)控制器名生成對象秕重,再用kvc給對象賦值,這樣就搞定了厉膀。
舉例:比如根據(jù)推送規(guī)則跳轉(zhuǎn)對應(yīng)界面HSFeedsViewController
進(jìn)入該界面需要傳的兩個屬性:
// HSFeedsViewController.h
@interface HSFeedsViewController : UIViewController
// 注:根據(jù)下面的兩個屬性溶耘,可以從服務(wù)器獲取對應(yīng)的頻道列表數(shù)據(jù)
/** 頻道ID */
@property (nonatomic, copy) NSString *ID;
/** 頻道type */
@property (nonatomic, copy) NSString *type;
@end
服務(wù)端推送過來的消息規(guī)則:
// 這個規(guī)則肯定事先跟服務(wù)端溝通好,跳轉(zhuǎn)對應(yīng)的界面需要對應(yīng)的參數(shù)
NSDictionary *userInfo = @{
@"class": @"HSFeedsViewController",
@"property": @{
@"ID": @"123",
@"type": @"12"
}
};
AppDelegate.m 中添加以下代碼片段:
接收推送消息
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[self push:userInfo];
}
跳轉(zhuǎn)界面
- (void)push:(NSDictionary *)params
{
// 類名
NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
// 從一個字串返回一個類
Class newClass = objc_getClass(className);
if (!newClass)
{
// 創(chuàng)建一個類
Class superClass = [UIViewController class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注冊你創(chuàng)建的這個類
objc_registerClassPair(newClass);
}
// 創(chuàng)建對象
id instance = [[newClass alloc] init];
// 對該對象賦值屬性
NSDictionary * propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 檢測這個對象是否存在該屬性
if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用kvc賦值
[instance setValue:obj forKey:key];
}
}];
// 獲取導(dǎo)航控制器
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
// 跳轉(zhuǎn)到對應(yīng)的控制器
[pushClassStance pushViewController:instance animated:YES];
}
檢測對象是否存在該屬性:
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 獲取對象里的屬性列表
objc_property_t * properties = class_copyPropertyList([instance
class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property =properties[i];
// 屬性名轉(zhuǎn)成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判斷該屬性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}