iOS组件化

iOS底层实现九宫格菜单可配置、页面跳转不需要#import &

2018-09-14  本文已影响849人  wg刚

一、需求:

1、每新跳一个页面都需要导入其文件,导致头文件很多,先要删除这些头文件,还能实现页面跳转
2、项目后期做组件化,涉及到跨业务组件的话,需要解耦
3、在做个性化定制时,可根据后台返回,实现可配置(例如九宫格菜单可配置)。

当然,我先给出代码,附带案例, 框架(WGControllerPush)可直接拖到项目中使用

项目中常会有下图情况出现,现在做到去除这些都文件,依然可以做跳转:

二、实现步骤:

1、页面传值

页面跳转首先会遇到传值问题,大体分为四类:

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、根据上面传值分类,首先写一个传值类型的枚举
#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)初始化完成,然后判断是否需要传值:根据上面的枚举
//根据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、根据@"property"关键字,获取存值的字典,然后获取字典的所有key数组
3、遍历key数组。根据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;
            //如果是数字则在字典中是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、根据重写的init方法名字作为key,每一个要传的值按对应顺序放到数组中在作为value,放到一个字典中

PS:同上,写死一个key(我是用的@"initWith")拼成一个大的字典

例如:

- (instancetype)initWithAge:(NSInteger)age name:(NSString *) name;

组装称字典:

//存储值得字典
NSDictionary *ParamDic = @{@"initWithAge:name:":@[18, @"小明"]};
//外层字典
NSDictionary *dic = @{@"initWith":ParamDic};

2、根据@"initWith"关键字,获取存值的字典,然后根据字典获取第一个key(其实只有一个key 就是initWithAge:name:)
3、根据key获取一个数组,里面存的是参数的值
4、这时通过objc_msgSend先alloc,然后initwith···
5、要求按顺序放入数组中就是为了在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数组元素个数是否和 key按:分割成数组后的个数相等
    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、把需要可配置的每个类的字符串名字和后台统一,用一个唯一标示pageId代替,例如@"1-1"代表@"HuInformationDetailViewController"
2、客户端本地用字典存储所有这些pageId
例如:

NSDictionary *ParamDic = @{@"1-1":@"FirstViewController",
                            @"1-2":@"FirstViewController"
                             };

3、这时后台只需要返给客户端一个pageId,客户端就可以根据这个id知道点击这个菜单按钮时,跳转到哪里了,这样是不是完成了点击功能
4、配置菜单按钮的图标和数量的话,本地给一个默认图标和数量,然后通过后台返回的最新图标和按钮数量来更新,这样就完成了图标的替换,菜单个数更新。

这是我项目中本地部分默认数组:可参考

defaultSkinArr = @[
            @{
                @"code" : @"1-2",
                @"defaultImg" : @"",
                @"name" : @"必修",
            },
            @{
                @"code" : @"1-3",
                @"defaultImg" : @"educate_share",
                @"name" : @"选修",
            },
            @{
                @"code" : @"1-6",
                @"defaultImg" : @"peixun",
                @"name" : @"分组培训",
            }
        ]
上一篇下一篇

猜你喜欢

热点阅读