iOS 万能跳转界面方法 (runtime实用篇一)

作者: 汉斯哈哈哈 | 来源:发表于2015-08-13 20:21 被阅读23985次

    在开发项目中,会有这样变态的需求:

    • 推送:根据服务端推送过来的数据规则,跳转到对应的控制器
    • feeds列表:不同类似的cell,可能跳转不同的控制器(嘘!产品经理是这样要求:我也不确定会跳转哪个界面哦,可能是这个又可能是那个,能给我做灵活吗?根据后台返回规则任意跳转?)

    思考:wocao!这变态的需求,要拒绝他吗?
    switch判断呗,考虑所有跳转的因素?这不得写死我...

    switch () {
        case :
            break;
        default:
            break;
    }
    

    我是这么个实现的(runtime是个好东西)

    利用runtime动态生成对象、属性、方法这特性,我们可以先跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性id、type,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了 ---O(∩_∩)O哈哈哈

    比如:根据推送规则跳转对应界面HSFeedsViewController

    HSFeedsViewController.h

    • 进入该界面需要传的属性
    @interface HSFeedsViewController : UIViewController
    
    // 注:根据下面的两个属性,可以从服务器获取对应的频道列表数据
    
    /** 频道ID */
    @property (nonatomic, copy) NSString *ID;
    
    /** 频道type */
    @property (nonatomic, copy) NSString *type;
    
    @end
    

    AppDelegate.m

    • 推送过来的消息规则
    // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
    NSDictionary *userInfo = @{
                               @"class": @"HSFeedsViewController",
                               @"property": @{
                                            @"ID": @"123",
                                            @"type": @"12"
                                       }
                               };
    
    
    • 接收推送消息
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    {
        [self push:userInfo];
    }
    
    
    • 跳转界面
    - (void)push:(NSDictionary *)params
    {
        // 类名
        NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
        const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
        
        // 从一个字串返回一个类
        Class newClass = objc_getClass(className);
        if (!newClass)
        {
            // 创建一个类
            Class superClass = [NSObject class];
            newClass = objc_allocateClassPair(superClass, className, 0);
            // 注册你创建的这个类
            objc_registerClassPair(newClass);
        }
        // 创建对象
        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];
            }
        }];
        
        // 获取导航控制器
        UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
        UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
        // 跳转到对应的控制器
        [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];
            //  属性名转成字符串
            NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            // 判断该属性是否存在
            if ([propertyName isEqualToString:verifyPropertyName]) {
                free(properties);
                return YES;
            }
        }
        free(properties);
        
        return NO;
    }
    

    具体使用和代码: https://github.com/HHuiHao/Universal-Jump-ViewController

    文章同步到微信公众号:hans_iOS 有疑问可以在公众号里直接发

    相关文章

      网友评论

      • 整个夏天:这跳转了有毛用啊,难道逻辑也要
      • smirkk:思路不错,不过太麻烦了,直接用反射机制不就解决了
      • Steven_Wu:直接NSClassFromString(params[@"class"])
        不就创建了服务器推送过来的VC的类了吗?需要搞个运行时这么麻烦?
        梦蝶Two:@Steven_Wu 同感
      • PGOne爱吃饺子:楼主写的真不赖啊
      • 波儿菜:和后端耦合高了 得不偿失
        梦蝶Two:@indulge_in 同感
      • SuAdrenine:我觉得可以把这文章理解成alloc,init并对属性赋值的另一种实现
        汉斯哈哈哈:好吧,其实就利用oc运行时特性.
      • 最初九月雪:如果是我, 我可能会在本地建一个文档或者建立一个专门的管理类, 把里面对应的类型记录在里面, 比如说type=1的时候是VC1, type=2的时候是VC2. 这样的话我接到消息每次只要读取本地的文件然后实例化对应的VC就行了. 省了和服务器耦合的关系......
        汉斯哈哈哈:哈,简单高效就好,如果业务不复杂那就尽量简单.
      • 从来吃不胖:1、为了iOS端的方便,你有考虑过安卓和后台的同学吗。
        2、checkIsExistPropertyWithInstance: verifyPropertyName:方法里做的运行时方法都是很慢的。不做任何缓存处理每次都是去遍历处理,效率真的很低
        汉斯哈哈哈:android不是也有映射吗,不过不知可行不,至于你说的运行效率可以忽略不计吧
      • 632c95cfdded:思路很好 明天试试
      • 丰田李:感谢分享
      • 狼人王:楼主的思路很好,希望你能解决后期维护问题,另外期待你优化更好的代码,会关注你的:smile:
        汉斯哈哈哈:好的,谢谢哒
      • a70e7e356e6f:runtime玩坏了
      • 471b0707ab73:思路确实是挺好的,不过用来在这种确实是耦合性太大了,客户端不能随意的修改控制器名字了,客户端做路由表也是需要维护,期待一个解决方法。
        471b0707ab73:@汉斯哈哈哈 如果有唯一标记,又没法做到万能跳转了
        汉斯哈哈哈:如果改名字,肯定要修改对应的路由表. 如果有控制器唯一标识或许可以解决更换名字问题,但感觉阅读性又差了.
      • 4375ed763f87:好像挺6的
      • iOS小洁:“如果要跳转n个控制器,那不是要写n次实例化,用runtime就是用一份的代码实现跳转n个控制器所不同实例与传参”对这句话不太了解
        汉斯哈哈哈:你理解下我提出的这个方案背景。
      • 巴图鲁:膜拜
      • 张俊一:喜欢
      • saplingdan:楼主写的真棒
      • 鬼丶白:跳转成功后会反回到那个界面
        汉斯哈哈哈:@soime 还是在当前导航控制器,相当于back一级
      • a194872ed988:撸主这么做是用纯代码搭的界面吗?
        汉斯哈哈哈:@色彩丶 跟纯代码没关系啊
      • 辛乐:Mark,学习了很好的思路,看评论区 要是用NSClassFromString(@“”) + KVC 也可以满足楼主的需求吧应该~~~
      • 751fc49dcbfd:url不是更好
      • 书生可笑:很不错
      • changxiaobin:假如服务端传递错误的classname,需要加上容错机制
        751fc49dcbfd:@changxiaobin 打算如何容错
      • 动感超人丶:假如已经根据服务器的信息,创建一个对象,也有了属性。怎么搭建界面呢?接下来怎么做,求思路。因为都是运行时产生的东西,以前固定的字段现在都是活的,不知道怎么给控件赋值了
        汉斯哈哈哈:@动感超人丶 去了解下jspatch :grin:
      • 动感超人丶:写的很不错,提供了好多思路
      • 编程小翁:思路还是不错的,不过会有以下缺点:
        1、虽然对客户端来说维护起来并不是太复杂,但是服务端就不一样了,服务端有可能需要支持WP、安卓、iOS多个平台,而且是同时推送的。iOS有自己的诉求,要求推送类名,安卓也可能会有诉求..服务端就显得厚重了
        2、如果用户已经在目标页了,点击APNs弹窗会再进一次,这样navigation堆栈就有两个目标页实例了,显然不对
        汉斯哈哈哈:@书桓 先判断下当前控制器是否目标页,然后再决定是否跳转
        shLuckySeven:@编程小翁 已经在目标页了还会再出现APNS弹框么?求解
        汉斯哈哈哈:@编程小翁 只是抛个思路,1.2都是可以优化
      • 屈涯:给力
      • a030ff2047b4:6666,给力!
      • 2e6d458c70a5:不错的思路
      • sun_sx:这样并不安全吧 只要修改请求就能跳转到任意界面
      • 常义:需要学习
      • qBryant:一种思路,学习了! :+1:
      • 奔跑的码农:不太现实,后期维护比较的难,实际中应该很少会用到!但是作者想法不错
      • fa14567f7979:这样做,如果只有iOS端还好,如果iOS,Android,wp都是一个接口调用的话就不灵活了
      • TIME_for:在我的一篇文章中引用了你的这部分知识。如有侵权联系我。立删。
      • qbk1989:你好,这个为什么不考虑用NSClassFromString?这个有什么弊端么?能讲讲么?
      • 5208b9425785:干的不要不要的 喝杯咖啡压压惊
      • 44d555da4ba6:mark.....
      • Figo_OU:其实这样推送过去还有个问题,就是如果程序在后台接收到推送。按推送打开时如果是在目的页面,你这么写又会在push一次
        东健FO_OF:那就需要在push的时候做下判断了,本身这个跳转也是简单写了下,不一定适用,只是一种方法
      • Jason_Hu:Class newClass = objc_getClass(className);
        if (!newClass)
        {
        // 创建一个类
        Class superClass = [NSObject class];
        newClass = objc_allocateClassPair(superClass, className, 0);
        // 注册你创建的这个类
        objc_registerClassPair(newClass);
        }

        这里对于没有找到的类名,创建一个新的类,有什么意义呢?
      • Joey_Xu:没太明白if (!newClass)的地方创建一个类有什么用呢?如果newClass==nil的话,应该是异常才对啊,为什么不直接return掉呢?
      • d0b8964e1f2d:app在关闭的情况下,收到apns通知,但是没有去点通知,而是直接点App启动,这样apns通知是获取不到的。界面也就没法跳转了吧
      • 73f8dbec2d8f:你应该试下规范的路由写法,具体可以参考https://github.com/Wasappli/WAAppRoutinghttps://github.com/usebutton/DeepLinkKit。虽然思路是对的,但目前服务端和客户端耦合的太厉害了,单靠服务端和客户端这种沟通和约定是不可靠的,这样的耦合会拖累服务端的。
        余不悟:@73f8dbec2d8f 真是两套好东西,我还用的轻量级的Routable
      • ColaBean:学习了
      • 买了否冷_:使用星数爆表!
        汉斯哈哈哈:@拉莫斯 😈
      • HJR:你好,用[[NSClassFromString(@"ClassName") alloc] init]; 创建一个类不是个更简单吗?
        qbk1989:我也想知道。。。。。。。。。。
        wazrx:这也是我想问的问题
        Yasin的简书:@HJR @汉斯哈哈哈 我也很想知道作者为什么不使用NSClassFromString,为什么要用runtime?
      • Joker_C:v 哥哥哥哥
      • ba08449e08cf:继续努力🍭
        汉斯哈哈哈:@丘丘123 :yum:
      • 61694105e95f:很有意思,我目前项目里就是根据推送过来的type然后switch跳转的,这个方法感觉更灵活一些。
        汉斯哈哈哈:@葛晓 哈,这需求很常见
      • liuwin7:如果使用swift,怎么来实现你的设计思路呢?
      • 煜寒了:确实不易维护 现在安卓和iOS都是同步推
        汉斯哈哈哈:@Jianer 只是抛个砖而已,提供一种思路。不限于推送。因为项目中难免会用到,如果确实觉得后期会造成一些问题,可作为备用。万一当急用时,后端想做一些操作时,那还是挺好用的。
      • ddaa8dae50b0:和服务端耦合太强, 后期迭代和维护比switch恐怖多了
        汉斯哈哈哈:@OrlAnd0 客户端代码不会随便去更改类名,属性名,方法,一般只做追加,特别是多人协作时,或接手新项目时,更加不能乱动。除非你重构代码,那就另外回事了,或代码本身觉得没写好。如果改动的话,本身就是个危险信号,因为项目中可能会很多地方用到该类。就算修改了,你要做的也就只是告诉服务端几个属性名而已!并不会你所说的不易维护。当然我这个只是特殊需求,项目中难免会用到,如果确实觉得后期会造成一些问题,可作为备用。万一当急用时,后端想做一些操作时,那还是挺好用的。
        ddaa8dae50b0:@汉斯哈哈哈 是的,需求很奇怪, 我还没有遇到需要跳转到任何页面的情况. 一般就是详情页, 活动, web, 这些就够用了. 维护难度在于客户端代码变动, 服务端要同时配合, 类名, 属性, 太多了. 往服务端加, 协调两边工作和单纯改客户端的工作差别很大.

        增加新的一页, 就要客户端和服务端两边改, 如果这页只是本地展示呢. 这个需求太笼统了. 提出来的时候就应该和策划商量, 需要哪几大类, 新的界面需求能不能包含在这里面, 后期扩展性如何等等. 之后进行分类管理.

        不管哪种方法做, 这种需求的成本都太大.
        汉斯哈哈哈:@OrlAnd0 一个个判断,根本无法实现我说的产品需求。至于你说的后期维护问题,我觉得只要写好文档注释就行,后期如果有特殊规则,再特殊解决!
      • e985b6f9827b:太干了,都喝好几杯水了,不行,再喝一杯去
        汉斯哈哈哈:@e985b6f9827b :joy:
      • 十一岁的加重:好东西 ,项目实用啊,不像网上有些人写得全是几百年前,培训界里的东西
        汉斯哈哈哈:@十一岁的加重 谢谢支持
      • Azen:哇哦~ 很干很干的干活哎。。待我喝点水去 :yum:
        汉斯哈哈哈:@Azen 哈哈哈
      • 叶舞清风:看了吗?
        ColaBean:@叶舞清风 测试一下评论
        叶舞清风:@汉斯哈哈哈 嘿嘿
        汉斯哈哈哈:@叶舞清风 啥?

      本文标题:iOS 万能跳转界面方法 (runtime实用篇一)

      本文链接:https://www.haomeiwen.com/subject/mtfwqttx.html