一、前言
1、最近整理以前项目的资料,处理推送消息的时候显示只能在主页面,安卓那边就很好处理,可以根据不同的推送消息指定跳转到任意控制器,那么iOS 能不能实现呢?答案是肯定的!
2、需要后台配合,传一个要跳转的控制器的名字以及指定控制器所需参数 原文 地址 ,简单说说这种方式出现的问题吧
问题一
没有封装工具类使用,delegate里面实现并处理逻辑,这显然不应该是delegate去做,造成delegate 冗余
问题二
面向字典开发,可复用性差,处理情况单一(json格式定死),逻辑处理不完善,数据获取麻烦且容易出错(需要写key,字符串容易出错,如果需要多次使用就更麻烦)
二、完善
1、封装工具类,继承NSObject,解耦合,移植性、复用性、拓展性强,对外提供一个方法,专门处理推送消息,自动跳转控制器,交给专业的人去做,更加面向对象
2、面向模型,后台返回json数据,转模型存储数据,存储方便,不容易出错,而且如果后台返回的json格式不一样了,我依然可以创建子类,重写构造方法进行特殊处理,而且面向模型开发,直观
三、API 设计
1、工具类一般弄成单例,需要一个单例创建方法,还需要对外提供一个方法,接收到外界数据后,处理逻辑并跳转到指定控制器
2、数据当然从模型里面那,因此还需要一个模型类,专门存储消息数据,在工具类中创建,统一管理,最关键、最重要的是claseName属性,后台必须返回一个需要跳转的控制器的名字给我,其他不同控制器对应的属性,就交给子类去添加,有属性当然就需要自定义构造方法,字典转模型嘛
FLRemotePushModel 模型
子类继承,对应不同的控制器所需的参数,添加对应的属性到子类中,默认层级都是同一级,子类可重写fl_remotePushModel方法,对应不同的字典转模型
注意一点:子类中所添加的属性,应该都为对象,基本数据类型不行喔,而且后台返回的都是对象,基本数据用NSNumber包装
/**
* @author Clarence-lie, 16-09-08 22:09:01
*
* 需要跳转的类名,后台返回
*/
@property (nonatomic,copy)NSString *className;
/**
* @author Clarence-lie, 16-09-08 22:09:38
*
* 字典转模型,可重写,根据不同格式转模型(如果重写,需要调用父类)
*
* @param dict 后台返回的字典(json)
*
* @return 返回一个模型
*/
+ (instancetype)fl_remotePushModel:(NSDictionary *)dict;
FLRemotePushManager 管理工具
/**
* @author Clarence-lie, 16-09-08 22:09:59
* 创建单例对象,方便管理
*/
+ (instancetype)fl_shareInstance;
/**
* @author Clarence-lie, 16-09-08 22:09:59
*
* 根据模型内容跳转到指定界面
*
* @param remotePushModel FLRemotePushModel子类
*/
- (void)fl_pushWithRemotePushModel:(FLRemotePushModel *)remotePushModel;
四、实现
大概思路:根据json数据创建模型 -> 工具类获取模型数据 -> 根据类名创建控制器实例对象 -> 获取模型对象属性名并存储到数组中 -> 遍历模型属性名数组并判断控制器实例对象是否有对应属性 -> 有对应属性则通过KVC赋值给控制器 -> 获取导航控制器进行跳转
定时器模拟推送消息
关键代码简析
1.获取对象的属性列表: class_copyPropertyList
unsigned int outCount, i;
// 获取对象里的属性列表
objc_property_t * properties = class_copyPropertyList([instance class], &outCount);
2.通过属性名创建对应属性的getter方法:NSSelectorFromString OC的getter方法就是属性的本身
/**
* @author Clarence-lie, 16-09-08 22:09:33
*
* 通过字符串来创建该字符串的getter方法
*
* @param propertyName 属性名
*
* @return 对应的getter方法
*/
- (SEL)fl_creatGetterWithPropertyName: (NSString *) propertyName{
//1.返回get方法: oc中的get方法就是属性的本身
return NSSelectorFromString(propertyName);
}
3.获取属性名对应的属性值:获得方法签名(getter方法)-> 签名中获取调用对象 -> 设置对象的target -> 设置对象的方法 -> 发送消息 -> 接收返回值(就是属性值)
/**
* @author Clarence-lie, 16-09-08 22:09:49
*
* 获取指定属性名对应的值
*
* @param propertyName 属性名
*
* @return 属性值
*/
- (id)fl_getValueByPropertyName:(NSString *)propertyName{
//获取get方法
SEL getSel = [self fl_creatGetterWithPropertyName:propertyName];
if ([self respondsToSelector:getSel]) {
//获得方法的签名
NSMethodSignature *signature = [self methodSignatureForSelector:getSel];
//从签名获得调用对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//设置target
[invocation setTarget:self];
//设置selector
[invocation setSelector:getSel];
//接收返回的值
NSObject *__unsafe_unretained returnValue = nil;
//发消息
[invocation invoke];
//接收返回值
[invocation getReturnValue:&returnValue];
return returnValue;
}
else{
return nil;
}
}
五、调用
调用起来很简单,只需要创建好对应的推送模型,然后执行 fl_pushWithRemotePushModel 方法就行
- (void)pushSecond
{
// 跟服务端沟通好跳转对应的界面需要对应的参数以及格式
NSDictionary *userInfo = @{
@"className": @"SecondViewController",
@"id": @"12",
@"content": @"测试推送内容消息"
};
[[FLRemotePushManager fl_shareInstance] fl_pushWithRemotePushModel:[FLSecondVcRemoteModel fl_remotePushModel:userInfo]];
}
- (void)pushThird{
// 非默认同一级的json格式,子类重写构造方法即可
NSDictionary *userInfo = @{
@"className": @"ThirdViewController",
@"msg" : @{
@"name": @"clarence",
@"age": @12
}
};
[[FLRemotePushManager fl_shareInstance] fl_pushWithRemotePushModel:[FLThirdVcRemoteModel fl_remotePushModel:userInfo]];
}
六、总结
** 1、注意:必须跟后台配合,定好规则,哪条推送消息需要跳转到哪个控制器、以及该控制器中请求后台所需的参数都要返回给客户端,归根到底还是利用了OC的运行时特点,可以获取对象以及对象属性、方法**
** 2、如果推送页面都是固定的,可以直接写死,只需要传入不同参数就行,但是如果是不固定的,当然,可以传入不同的type 然后前台判断,跳转不同的控制器,此时相对就没那么直观,但确安全很多,因为控制器名是字符串,写错了就没办法,当然,框架中处理了,如果没找到指定的控制器 ,就会创建新的,看需求**
** 3、实现比较简单,关键还是思路,详细的Demo都在 GitHub 上面,欢迎大家关注我,喜欢给个like 和 star **