一、需求:
1阳液、每新跳一個頁面都需要導入其文件,導致頭文件很多帘皿,先要刪除這些頭文件,還能實現(xiàn)頁面跳轉
2鹰溜、項目后期做組件化,涉及到跨業(yè)務組件的話曹动,需要解耦
3斋日、在做個性化定制時恶守,可根據(jù)后臺返回,實現(xiàn)可配置(例如九宮格菜單可配置)贡必。
當然,我先給出代碼衫樊,附帶案例, 框架(WGControllerPush)可直接拖到項目中使用
項目中常會有下圖情況出現(xiàn),現(xiàn)在做到去除這些都文件科侈,依然可以做跳轉:
二、實現(xiàn)步驟:
1兑徘、頁面?zhèn)髦?/h5>
頁面跳轉首先會遇到傳值問題,大體分為四類:
a、不需要傳值
b、只通過屬性傳值
例如:
@property (nonatomic, copy) NSString *name;
c欲侮、重寫了init方法
例如:
- (instancetype)initWithDic:(NSDictionary *)dic model:(WGModel *)model school:(NSString *)school;
d、既有屬性傳值威蕉,重寫了init來傳值
例如:
@property (nonatomic, assign) BOOL isMale;
- (instancetype)initWithDic:(NSDictionary *)dic name:(NSString *) name;
2、根據(jù)上面?zhèn)髦捣诸惾驼牵紫葘懸粋€傳值類型的枚舉
#pragma mark -傳值類型
typedef NS_ENUM(NSInteger , WGPushControllerType) {
WGPushNoParam = 0, //不需要傳值
WGPushProperty = 1, //只有屬性傳值
WGPushInit = 2, //重寫了init方法傳值
WGPushOther = 3, //其他,混合方式傳值(既有屬性又有initWith)等
};
3虑粥、因為沒有導入頭文件,所以不能再如下圖這樣
4娩贷、怎么辦呢第晰?
舉個例子:
Person *per = [[Person alloc] init];
可以拆分為
Person *per = [Person alloc];
[per init];
(1)在知道一個類的字符串名字時,可以轉為Class的
就是這句:
Class classCon = NSClassFromString(toCon);
(2)有這個類的Class彬祖,是不是就可以alloc init了
PS:要用id類型接收
id con = [[classCon alloc] init];
(3)初始化完成茁瘦,然后判斷是否需要傳值:根據(jù)上面的枚舉
//根據(jù)HuPushControllerType判斷是否有值傳到下一頁
switch (type) {
case WGPushNoParam: {
//不需要傳值
} break;
case WGPushProperty: {
//只屬性傳值
[self getToConFromProperty:paramDic toCon:con];
} break;
case WGPushInit: {
//只通過重寫init傳值
con = [self getToConFromInit:paramDic classCon:classCon];
} break;
case WGPushOther: {
//既有屬性傳值,還重寫init傳值
con = [self getToConFromInit:paramDic classCon:classCon];
[self getToConFromProperty:paramDic toCon:con];
} break;
default:
break;
}
(4)重點來了储笑,要傳值咯
1甜熔、屬性傳值
我這里簡單說下思路:
1、把需要通過屬性傳值的值和屬性名字放到一個字典中突倍,值為字典的value腔稀,屬性的字符串名字為字典的key
PS:這個字典再用一個寫死的key(自己定義好一個宏就可以,我是寫了一個@"property")拼成一個大的字典, 來讓程序通過這個key拿到需要屬性傳值的字典
例如:
@property (nonatomic, copy) NSString *name;
組裝成字典為:
//存儲值的字典
NSDictionary *ParamDic = @{@"name": @"小明"};
//外層字典
NSDictionary *dic = @{@"property":ParamDic};
2赘方、根據(jù)@"property"關鍵字烧颖,獲取存值的字典,然后獲取字典的所有key數(shù)組
3窄陡、遍歷key數(shù)組炕淮。根據(jù)key的字符串生成對應屬性的set方法
4、最后通過objc_msgSend完成賦值
:代碼:
//屬性傳值
- (void)getToConFromProperty:(NSDictionary *)paramDic toCon:(id)toCon {
//需要屬性傳值跳夭,則通過運行時來解決
if (!toCon) {
return;
}
NSDictionary *propertyDic = [paramDic valueForKey:WGProperty];
NSArray *keyArr = [propertyDic allKeys];
for (int i = 0; i < keyArr.count; i++) {
NSString *key = [keyArr objectAtIndex:i];
id value = [propertyDic valueForKey:key];
//把key的首字母大寫
NSString *firstStr = [key substringWithRange:NSMakeRange(0, 1)].uppercaseString;
NSString *restStr = [key substringFromIndex:1];
//生成對應屬性的set方法
NSString *selName = [NSString stringWithFormat:@"set%@%@:", firstStr, restStr];
SEL method = NSSelectorFromString(selName);
if ([toCon respondsToSelector:method]) {
//等價于controller.shuxing = value;
//如果是數(shù)字則在字典中是NSNumber類型,需要把NSNumber類型轉為NSInteger或者CGFloat
if ([value isKindOfClass:[NSNumber class]]) {
NSString *vale = [(NSNumber *) value stringValue];
if ([vale containsString:@"."]) {
CGFloat val = [vale doubleValue];
void (*action)(id, SEL, CGFloat) = (void (*)(id, SEL, CGFloat)) objc_msgSend;
action(toCon, method, val);
}else{
NSInteger val = [(NSNumber *) value integerValue];
void (*action)(id, SEL, NSInteger) = (void (*)(id, SEL, NSInteger)) objc_msgSend;
action(toCon, method, val);
}
} else {
void (*action)(id, SEL, id) = (void (*)(id, SEL, id)) objc_msgSend;
action(toCon, method, value);
}
}
}
}
2涂圆、重寫init方法傳值
我這里簡單說下思路:
1、根據(jù)重寫的init方法名字作為key币叹,每一個要傳的值按對應順序放到數(shù)組中在作為value润歉,放到一個字典中
PS:同上,寫死一個key(我是用的@"initWith")拼成一個大的字典
例如:
- (instancetype)initWithAge:(NSInteger)age name:(NSString *) name;
組裝稱字典:
//存儲值得字典
NSDictionary *ParamDic = @{@"initWithAge:name:":@[18, @"小明"]};
//外層字典
NSDictionary *dic = @{@"initWith":ParamDic};
2颈抚、根據(jù)@"initWith"關鍵字踩衩,獲取存值的字典,然后根據(jù)字典獲取第一個key(其實只有一個key 就是initWithAge:name:)
3、根據(jù)key獲取一個數(shù)組驱富,里面存的是參數(shù)的值
4锚赤、這時通過objc_msgSend先alloc,然后initwith···
5褐鸥、要求按順序放入數(shù)組中就是為了在init時值不會錯亂
代碼:
//init傳值,類似于initWithDic
- (id)getToConFromInit:(NSDictionary *)paramDic classCon:(Class)classCon {
id toCon = nil;
NSDictionary *initDic = [paramDic valueForKey:WGInitWith];
NSString *key = [initDic allKeys].firstObject;
//把OC的字符串改成C語言的字符串
const char *ky = [key UTF8String];
NSArray *value = [initDic valueForKey:key];
//這里判斷value數(shù)組元素個數(shù)是否和 key按:分割成數(shù)組后的個數(shù)相等
if ([key containsString:@":"] && value) {
if ([key componentsSeparatedByString:@":"].count == (value.count + 1)) {
switch (value.count) {
case 1: {
//先alloc
id classAlloc = objc_msgSend(classCon, sel_registerName("alloc"));
//sel_registerName(ky)等價于@selecter(ky)
if ([classAlloc respondsToSelector:sel_registerName(ky)]) {
id paramOne = [value objectAtIndex:0];
if ([paramOne isKindOfClass:[NSNumber class]]) {
NSString *val = [(NSNumber *) paramOne stringValue];
if ([val containsString:@"."]) {
CGFloat vale = [val doubleValue];
id (*action)(id, SEL, CGFloat) = (id(*)(id, SEL, CGFloat)) objc_msgSend;
//等價于[[class alloc] iniWith:]
toCon = action(classAlloc, sel_registerName(ky), vale);
}else{
id (*action)(id, SEL, NSInteger) = (id(*)(id, SEL, NSInteger)) objc_msgSend;
//等價于[[class alloc] iniWith:]
toCon = action(classAlloc, sel_registerName(ky), [val integerValue]);
}
}else{
id (*action)(id, SEL, id) = (id(*)(id, SEL, id)) objc_msgSend;
toCon = action(classAlloc, sel_registerName(ky), paramOne);
}
}
} break;
case 2: {
id classAlloc = objc_msgSend(classCon, sel_registerName("alloc"));
if ([classAlloc respondsToSelector:sel_registerName(ky)]) {
id paramOne = [value objectAtIndex:0];
id paramTwo = [value objectAtIndex:1];
id (*action)(id, SEL, id, id) = (id(*)(id, SEL, id, id)) objc_msgSend;
toCon = action(classAlloc, sel_registerName(ky), paramOne, paramTwo);
}
} break;
case 3: {
id classAlloc = objc_msgSend(classCon, sel_registerName("alloc"));
if ([classAlloc respondsToSelector:sel_registerName(ky)]) {
id (*action)(id, SEL, id, id, id) = (id(*)(id, SEL, id, id, id)) objc_msgSend;
toCon = action(classAlloc, sel_registerName(ky), [value objectAtIndex:0], [value objectAtIndex:1], [value objectAtIndex:2]);
}
} break;
case 4: {
id classAlloc = objc_msgSend(classCon, sel_registerName("alloc"));
if ([classAlloc respondsToSelector:sel_registerName(ky)]) {
id (*action)(id, SEL, id, id, id, id) = (id(*)(id, SEL, id, id, id, id)) objc_msgSend;
toCon = action(classAlloc, sel_registerName(ky), [value objectAtIndex:0], [value objectAtIndex:1], [value objectAtIndex:2], [value objectAtIndex:3]);
}
} break;
default:
break;
}
}
}
return toCon;
}
三:到這里已經完成了前兩個需求了线脚,還有一個九宮格菜單可配置
九宮格的demo我也寫好了,可下載參考(代碼在最上面)
1叫榕、把需要可配置的每個類的字符串名字和后臺統(tǒng)一,用一個唯一標示pageId代替浑侥,例如@"1-1"代表@"HuInformationDetailViewController"
2、客戶端本地用字典存儲所有這些pageId
例如:
NSDictionary *ParamDic = @{@"1-1":@"FirstViewController",
@"1-2":@"FirstViewController"
};
3晰绎、這時后臺只需要返給客戶端一個pageId寓落,客戶端就可以根據(jù)這個id知道點擊這個菜單按鈕時,跳轉到哪里了寒匙,這樣是不是完成了點擊功能
4、配置菜單按鈕的圖標和數(shù)量的話考蕾,本地給一個默認圖標和數(shù)量会宪,然后通過后臺返回的最新圖標和按鈕數(shù)量來更新,這樣就完成了圖標的替換塞帐,菜單個數(shù)更新巍沙。
這是我項目中本地部分默認數(shù)組:可參考
defaultSkinArr = @[
@{
@"code" : @"1-2",
@"defaultImg" : @"",
@"name" : @"必修",
},
@{
@"code" : @"1-3",
@"defaultImg" : @"educate_share",
@"name" : @"選修",
},
@{
@"code" : @"1-6",
@"defaultImg" : @"peixun",
@"name" : @"分組培訓",
}
]