1.NSNotification|远程通知|本地通知|激光推送
1. 通知在iOS中应用非常广泛,合理的应用通知可以减少冗余代码,使我们的代码层次结构变了更加的清晰明了,利于程序猿读写。它主要分为不可见通知可见通知两种:
1.1 不可见通知,主要是有系统发出的,没有界面提示,用户无法用肉眼直接捕捉到的。如键盘弹起事件。
1.2 可见通知,主要是哪些在前台运行的应用程序发生了一些用户感兴趣的事,或者服务器端为了给用户发送某些特性的消息,给用户发出的一种推送通知。
其中可见通知又分为本地推送,不需要连接网络,在手机系统内部就能调用发出的通知,如起床闹铃。
远程推送:就是应用程序的服务器端通过苹果服务器查找用户的app ID给用户发送推送消息的一种形式。
下面就这三种通知方式做详细的介绍:
2. 不可见通知:
其本质,就是一个对象把消息(通知时间的名称,)发给通知中心(类似于广播📢),然后由广播再发出去,然后另外一个对象只要监听这个广播发出的对应的消息就能收到信息了。
NSNotificationCenter 为系统通知中心,它可以以广播的形式将通知发给很多对象,也可以通过addObserver方法添加指定的对象监听某个特定名字的通知。在控制器释放的时候记得讲通知中心的通知移除,减少内存开销。
NSNotification 为一个通知对象,它包括了通知的类容,通知的名字,以及发出通知的对象
+ (NSNotificationCenter *)defaultCenter; //单利方法,通过创建一个系统默认的通知中心对象,这个对象也叫做广播
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
参数1: observer , 观察者,如果不制定所有观察者都能监听到该消息,
参数2:表示监听到此通知时执行的方法
参数3:表示通知的名称,在此方法中常写系统内的通知
参数4:通知的内容,可以通过此对象附加一些内容到通知对象中,一般为nil
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
参数1:发出通知的名称
参数2:接收通知的对象,不指定代表所有的对象都能接收词通知
参数3:通知内容
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;
参数1:需要移除的观察着对象
参数2:需要移除的通知名字
参数3:需要移除的内容
下面一段代码为通知中心发送键盘弹起通知,然后当前控制器根据监听该通知,并用提取改通知的属性userInfo,并将此大字典中的信息提取出来,更改当前View的frame:
在合适的位置注册通知(一定要在事件可能发生之前)->实现通知触发的方法(根据通知的重要属性UserInfo解剖通知信息)->移除注册的方法。
#import "ViewController.h" #import "MessageCell.h" #import "Message.h" @interface ViewController ()<UITableViewDataSource,UITableViewDelegate> @property (weak, nonatomic) IBOutlet UITextField *textField; //工具条的底部约束 @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; @property(strong,nonatomic)NSMutableArray* allMessage; @property (weak, nonatomic) IBOutlet UITableView *tableView; @end @implementation ViewController -(NSMutableArray *)allMessage { if (!_allMessage) { _allMessage=[[Message fakeData]mutableCopy];} return _allMessage;} - (void)viewDidLoad { [super viewDidLoad]; //为了让tableView自适应高度,需要设置如下两个属性 self.tableView.estimatedRowHeight=70; //IOS8之后新增的,先动态给一个预估的行高 //由约束生成 self.tableView.rowHeight=UITableViewAutomaticDimension; //设置tableView的内边距,使得内容错位64点 self.tableView.contentInset=UIEdgeInsetsMake(64, 0, 0, 0); } -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1;} -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.allMessage.count;} -(MessageCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //故事版中已经注册了 //if frome ,注册cell1,否则注册cell2 MessageCell* cell=[tableView dequeueReusableCellWithIdentifier:@"cell1" forIndexPath:indexPath]; Message* message=self.allMessage[indexPath.row]; cell.messageLabel.text=message.content; return cell;} /**点击后收键盘*/ /*设置文本框的代理, shuoldReturnXXXX进行关闭键盘,另外增加手势对键盘进行处理,手势加载到tableView*/ /*手势添加在tableView上会有影响,与tableView会冲突,给tableView添加一个支持多手势并存的方法,return YES*/ - (IBAction)ReturnKey:(UITextField *)sender { NSString* newContent=self.textField.text; if (newContent.length==0) { return;} //构建消息 Message* newMessage=[[Message alloc]init]; newMessage.fromMe=YES; //将数据保存在数组中 [self.allMessage addObject:newMessage ]; //清空文本框 self.textField.text=@""; //显示数据模型 NSIndexPath* indexPath=[NSIndexPath indexPathForRow:self.allMessage.count-1 inSection:0]; [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } //将控制表视图滚动到最底部,scrollToTableViewLastRow -(void)scrollToTableViewLastRow { NSIndexPath* lastIndexPath=[NSIndexPath indexPathForRow:self.allMessage.count-1 inSection:0]; [self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; } //注册键盘通知 -(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(openKeyBoard:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(closeKeyBoard:) name:UIKeyboardWillHideNotification object:nil]; } -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; //刚出来就滚动到最后一条消息 [self scrollToTableViewLastRow]; } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil]; } -(void)openKeyBoard:(NSNotification*)notification { //读取弹起的键盘的高度: CGFloat keyboardHeight=[notification.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue].size.height; //读区动画的时长 CGFloat duration=[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]floatValue]; NSInteger option=[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey]integerValue]; //修改bottom的约束: self.bottomConstraint.constant=keyboardHeight; //与键盘同步运动,必须保持Duration一致,options选项的动画种类必须与键盘的种类一致。 [UIView animateWithDuration:duration delay:0 options:option animations:^{ [self.view layoutIfNeeded]; //弹起后一起滚动 [self scrollToTableViewLastRow]; } completion:nil]; } -(void)closeKeyBoard:(NSNotification*)notification { CGFloat duration=[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]floatValue]; NSInteger option=[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey]integerValue]; self.bottomConstraint.constant=0; [UIView animateWithDuration:duration delay:0 options:option animations:^{ [self.view layoutIfNeeded]; } completion:nil]; } @end
3. 本地通知:主要是为了当应用程序不在前台运行的时候,将发生的一些用户可能感兴趣的信息传递给用户,这种方式也叫做消息机制。 它的主要表现形式相信我们大家都不陌生,横幅提醒,锁屏提示,appIcon图标数字提示,通知中心提示。
它的表现特点:
app关闭的时候也能接收和显示通知。
app处于后台的时候能接收通知也能显示。
app处于前台的时候能接收,但不能显示.(这里说的不能显示,仅仅只对应这个一个app的通知事件,如果是其它app的通知事件它不会管其它引用程序是否有没正在运行)。 比喻你在玩天天飞车PK的时候,你的好友一直给你qq发个消息和抖动,是不是很无语。
具体的创建方法:
-》创建一个本地通知对象UILocalNotification
-》设置fireDate,AlertBody,AlertAction,soundName,applicationBadgeNumber,repeatInterval,alertLanuchImage属性
-》配置通知参数,userInfo. 及通知的内容。我们可以在接收通知的方法中获取改对象。
-》调用通知,使用UIApplication的单利对象scheduleLocalNotificaiton按照计划启动通知
此处需要注意的是自从iOS8之后需要征求用户的通知,如果同意则创建UIUerNotificationSettings,然后 registerUserNotificationSettings
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. float iosVersion = [UIDevice currentDevice].systemVersion.floatValue; if (iosVersion >= 8.0) { //在启动appDelegate中设定通知的弹出形式,征求用户是否同意注册通知.小于8.0则不用注册 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound| UIUserNotificationTypeAlert categories:nil]; [[UIApplication sharedApplication]registerUserNotificationSettings:settings]; } // NSLog(@"app start %@",launchOptions); UILabel *label = [[UILabel alloc]init]; label.backgroundColor = [UIColor redColor]; label.frame = CGRectMake(0, 130, 300, 150); label.font = [UIFont systemFontOfSize:11]; label.numberOfLines = 0; [self.window.rootViewController.view addSubview:label]; if (launchOptions) { label.text = @"进入详细界面"; // NSLog(@"进入内容详细界面"); }else{ // NSLog(@"进入内容主界面"); label.text = @"进入主界面"; } return YES; } /** 如果app 没有关闭 */ - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { application.applicationIconBadgeNumber = 0; NSLog(@"接收到本地通知"); NSLog(@"%@",notification); NSLog(@"%@:%@",notification.userInfo[@"username"],notification.userInfo[@"userweight"]); /** 判断程序是否在前台运行 app处于激活状态 */ if (application.applicationState == UIApplicationStateActive) { NSLog(@"轻轻的我走了 正如我轻轻来"); return; }else{ NSLog(@"进入内容详细界面"); } } UILocalNotification *localNoti = [UILocalNotification new]; localNoti.fireDate = [NSDate dateWithTimeIntervalSinceNow:10]; localNoti.alertBody = @"学习本地通知"; localNoti.alertTitle = @"本地通知"; localNoti.alertAction = @"想想"; localNoti.soundName = @"voiceb.wav"; localNoti.applicationIconBadgeNumber = 321; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[@"username"] = @"zhangxiang"; userInfo[@"userweight"] = @(110); localNoti.userInfo = userInfo; [[UIApplication sharedApplication]scheduleLocalNotification:localNoti];
注意事项:
-》当用户点击通知后,会自动打开发的通知的app;如果app没有关闭会走didrevieveLocalNotification方法。
-》如果应用程序是彻底退出的,并且是通过点击通知进入app的,则lauchOptions是非空的,通过UIApplicationLauchOptionsNotificationKey来获取通知内容。
-》在对通知进行非空判断和app当前的运行进行判定后,在didfinishLaunch方法和didRecieveNotificaiton方法中可以实现跳转。 通过description可方法对字典中的数据进行描述。
-》进入app后,将application.applicationBadgeNumber= 0,取消通知的号码。
-》取消通知,通过UIApplicaiton单利对象获取到所有应用程序的所有通知,再调用cancelAllLocaitonNotifications
4 .远程通知:
客户端对用户信息机型筛选后发送给苹果服务器,经过苹果APNS(苹果推送服务器审核后)再根据deviceToken发送到指定用户手机上的应用。
具体的5大实现步骤:
-》 app向操作系统注册一个推送通知,手机iOS系统向APNS要一个deviceToken的设备标记
-》APNS讲DeviceToken返回给iOS,然后再传递给app
-》app将DeviceToken(其实就是手机标示和app标示一起组成)发个app服务器。
-》开发者根据苹果公司使用的服务器根据需求挑选特定的deviceToken生成通知,然后pushNotification给苹果APNS
-》然后由苹果APNS根据push服务器的请求推送远程通知到客户app。
具体的代码实现部分,和本地通知的注册方式差不多,省略了很多内容。不同的是这个通知信息内容最终是由应用程序的app发出的。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
float systemVersion = [UIDevice currentDevice].systemVersion.floatValue;
if (systemVersion < 8.0) { //版本号判定,小于8.0直接注册
[application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound];
}else{
[application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
[application registerForRemoteNotifications]; //大于8.0请求注册
}
return YES;
}
/** 获取devicetoken,调用app此监听方法获取deviceToken方法,然后发送给远程服务器 */
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"%@",deviceToken);
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error
{
NSLog(@"register error:%@",error);
}
备忘录:
5. 其他推送补充说明:我们通常在程序调试阶段需要进行通知的演示,这里可以使用三方的工具进行推送掩饰,以pushmeBaby这款软件充公司的push服务器。
主要步骤如下:
https://developer.apple.com 此网站申请一个APNS证书。
-》申请一个CSR的请求签名证书,它是基于电脑UUID的唯一表示进行生成的,所有的证书都要依赖于此证书, 点击电脑的钥匙串访问->证书助理->从证书的颁发者机构颁发请求证书
-》生成开发者证书,先登录苹果开发者网站(必须用交费账号),按要求下一步下一步,傻瓜式操作及可。
-》生成推送证书,先申请一个app id,注意申请时 尽量填写以后将要上架的 id,免得到时候又要重新生成很多证书,所以这里直接选用bundle id。
-》采用app id再生成一个推送证书,把这个证书按装到要测试的设备上.
-》把推送证书拖入到pushmeBaby这个程序中,这个应用中appDelegate证书的名字换成我们自己的证书。
6. 激光推送:
http://docs.jpush.io/guideline/ios_guide/ 详细介绍
Jpush极光推送一个端到端的服务,使用服务器端的消息能够及时的推送到客户的应用上,让开发者积极的保持与用户之间的联系。
它的主要功能:
为JPush Server上报DeviceToken,免除开发者管理DeviceToken的烦恼
支持iOSAPNS推送
前台运行的时候可由JPush下发的自定义消息
灵活接受和管理用户,tag ,alias(用户别名) ,registrantionID(设备注册id)
SDK集成步骤( 软件开发工具包(外语首字母缩写:SDK、外语全称:Software Development Kit)) :
先在极光网上注册账号,然后创建一个应用
->上传开发者证书, 在电脑的钥匙串🔑中找到Development iOS证书,并导出成.p12文件(此时电脑要求你输入的密码就是开发者密码);p12文件是一种加密格式的文件
-》上传生产者证书,同上,在电脑🔑中找到App Push Service证书的.p12文件(导出时自定义的密码就位生产证书密码)
以上两部完成之后极光会给我们生成一个应用程序的信息。
我门只关注appKey: 和Bundle ID ,注意一定要保证Bundle id和我们的项目里的Bundle ID保持一致,否则无法使用。
-》讲SDK包解压,按要求导入对应工程文件。
-> 导入必要的框架
- CFNetwork.framework
- CoreFoundation.framework
- CoreTelephony.framework
- SystemConfiguration.framework
- CoreGraphics.framework
- Foundation.framework
- UIKit.framework
- Security.framework
- Xcode7需要的是libz.tbd;Xcode7以下版本是libz.dylib
-》 创建并配置 PushConfig.list文件
如上图示:
CHANNEL ,指明应用程序包的下载渠道,为方便分渠道统计
APP_KEY 填写管理Portal上创建应用后自动生成的AppKey值。
APS_FOR_PRODUCTION 0 (默认值)表示采用的是开发证书,1 表示采用生产证书发布应用
-》然后再appDelegate调用激光SDK相应的方法,注册远程通知,向苹果索要DeviceToken,然后发送给激光服务器。
[APService registerDeviceToken:deviceToken];