推送通知和NSNotification有区别
- NSNotification是抽象的,不可见的
- 推送通知是能用眼睛看到的
两种推送通知
- 本地通知(Local Notification):手机应用本身的通知,比如:闹钟。就是用来提醒用户完成一些任务。
- 特点:不需要联网就能发出推送通知(不需要服务器的支持)。
- 远程(推送)通知(Remote Notification):远程服务器联网推送给客户端的通知,以下列出5个常见的远程通知。
- 1.qq在后台,屏幕顶部出现横幅,横幅的内容是别人给你发的信息,但是一会就消失了,这个就是远程通知
- 2.UIAlertView弹框提示也是远程通知
- 3.锁屏状态下能收到别人给你发的信息也是远程通知
- 4.下拉屏幕出现的通知选项也是远程通知
- 5.图标右上角出现的1,2,3... 也是远程通知
本地通知和远程(推送)通知的区别
本地通知的局限性:只要用户关闭了app,就无法跟app的服务器沟通,无法从服务器获得最新的数据内容
远程通知的优越性:用户关闭了app,只要联网了,也能收到服务器推送的远程通知,能收到远程通知的原因就是建立了长连接
远程(推送)通知必须知道的知识点:
1.所有的苹果设备,在联网状态下,都会与苹果服务器建立长连接
2.长连接:只要联网,就一直建立连接,不联网,就无法建立连接.
3.长连接的作用:时间校准、系统升级、查找我的iPhone.
4.长连接的好处:数据传输速度快、数据保持最新状态(实时更新).
Device Token = 手机的UDID + App的Bundle ID
远程推送服务:APNS(Apple Push Notification Services)。
APNS和远程推送通知的关系
远程推送通知要经过APNS才能推送到手机上。
device token,即设备令牌。根据自己的设备(UDID)和应用(Bundle ID)向APNS服务器发出请求来换取一个device token。
device token作用:如果应用需要发出通知给某个手机,但是应用发出的信息不是直接给手机的,而是必须统一交给APNS服务器。APNS服务器通过device token,知道应用要发的消息是给哪个手机设备的,并转发该消息给手机,手机再通知应用程序。
做远程(推送)通知的条件:
-
如果是真机调试阶段(常用),需要如下准备:
- 真机调试的证书
- 远程推送通知的开发证书
- Bundle ID
- 真机设备
- 根据上面4个生成的描述文件
-
如果是发布阶段(不经常用),需要如下准备:
- 发布阶段的证书
- 远程推送通知的生产证书
- Bundle ID
- 真机设备
- 根据上面4个生成的描述文件
-
总结:
- 类似真机调试\打包测试\发布的做法.只不过限制电脑时,真机调试阶段的话必须选择Apple Push Notification service SSL(Sandbox),发布阶段必须选择Apple Push Notification service SSL (Sandbox & Production)
- 精华:看Idenfifiers-App IDs 选项的Development字段、Distribution字段下面是Enable还是Disabled,可以判断出这两个字段(开发阶段、发布阶段)的状态
真机调试+远程推送通知
1.创建请求文件(打开 keychain Access)把请求文件放到Certificates中安装,并下载一个cer后缀的证书
2.登录开发者账号,生成真机调试阶段需要的证书+远程推送通知的开发证书+Bundle ID+真机设备+描述文件
3安装真机调试的证书+安装远程推送通知的开发证书+安装描述文件
4.创建用于真机调试+远程推送通知阶段的xcode文件并在xcode中指定Bundle ID、描述文件、证书
5.在AppDelegate.m文件中写代码完成远程推送通知
6.利用PushMeBaby作为服务器,让真机拿到通知的内容显示在真机设备上
相关代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if ([UIDevice currentDevice].systemVersion.doubleValue <= 8.0) {
// 不是iOS8
UIRemoteNotificationType type = UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert;
// 当用户第一次启动程序时就获取deviceToke
// 该方法在iOS8以及过期了
// 只要调用该方法, 系统就会自动发送UDID和当前程序的Bunle ID到苹果的APNs服务器
[application registerForRemoteNotificationTypes:type];
}else
{
// iOS8
UIUserNotificationType type = UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type categories:nil];
// 注册通知类型
[application registerUserNotificationSettings:settings];
// 申请试用通知
[application registerForRemoteNotifications];
}
return YES;
}
/**
* 获取到用户对应当前应用程序的deviceToken时就会调用
*/
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"%@", deviceToken);
}
@end
利用极光实现##真机调试+远程推送通知
1.创建请求文件(打开 keychain Access)把请求文件放到Certificates中安装,并下载一个cer后缀的证书
2.登录开发者账号,生成真机调试阶段需要的证书+远程推送通知的开发证书+远程推送通知的生产证书+Bundle ID+真机设备+描述文件
3.安装真机调试的证书+安装远程推送通知的开发证书+安装远程推送通知的发展证书+安装描述文件+搭导出远程推送通知的开发证书的p12文件+导出远程推送通知的发展证书的p12文件
4.登录https://www.jiguang.cn+创建应用(加载步骤3导出的两个p12文件)
5.创建用于真机调试+远程推送通知阶段的xcode文件并在xcode中指定Bundle ID、描述文件、证书
6.下载极光的iOS SDK+查看极光的iOS SDK集成指南,按照集成指南来集成极光的iOS SDK
7.打开Xcode中的后台模式+开启手机的Wifi+运行添加到AppDelegate.m中的代码
8.打开极光,找到"发送通知"按钮,填写通知的内容,点击立即发送,手机进入后台,即可收到通知
获得Device Token时提示如下问题的解决办法
问题1:
You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.
问题1的解决办法
问题2:
Not get deviceToken yet. Maybe: your certificate not configured APNs
```
#####解决办法:手机联wifi即可解决问题
---
#####问题3:
```
Use of undeclared identifier 'ASIdentifierManager'
```
#####解决办法1:
```
导入#import <AdSupport/ASIdentifierManager.h>文件即可解决问题
```
#####解决办法2:
```
删除NSString *advertisingId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];代码
```
---
#####问题4:
```
/BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.12/UIApplication.m:3401
```
#####解决办法:
```
删除AppDelegate.m文件中的
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
```
---
#远程推送通知具体流程
---
![61-31.png](http://upload-images.jianshu.io/upload_images/2364940-163c1f70d2be6122.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
---
![61-32.png](http://upload-images.jianshu.io/upload_images/2364940-98b3f1b02fd63cee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
---
- 推送通知的调试阶段的证书+推送通知的发布阶段的证书+普通调试阶段的证书+描述文件+Bundle ID(开启通知功能)+真机设备=既可以实现真调试,也可以实现推送通知
- .a文件就是静态库,本质是.m文件,只不过将.m文件加密了,生成了.a文件
---
#本地通知
---
AppDelegate.m文件
```
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 注意: 在iOS8中, 必须提前注册通知类型
if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {
UIUserNotificationType type = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type categories:nil];
// 注册通知类型
[application registerUserNotificationSettings:settings];
}
return YES;
}
// 代理方法:接收到本地通知时就会调用.程序必须在前台/后台,如果程序完全退出则不调用
// 当程序在前台时, 会自动调用该方法
// 当程序在后台时, 只有用户点击了提示的通知才会调用
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
NSLog(@"%s", __func__);
// 点击"注册通知"按钮,5秒后将会显示灰色的UILabel,与此同时,通知的内容也会一同显示在灰色的UILabel上
static int count = 0;
count++;
UILabel *label = [[UILabel alloc] init];
label.frame = CGRectMake(self.window.frame.size.width/2-100, 300, 200, 200);
label.numberOfLines = 0;
label.textColor = [UIColor whiteColor];
label.text = [NSString stringWithFormat:@" %@", notification.userInfo];
label.font = [UIFont systemFontOfSize:21];
label.backgroundColor = [UIColor grayColor];
[self.window.rootViewController.view addSubview:label];
}
@end
```
ViewController.m文件
```
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
/*
@property(nonatomic,copy) NSDate *fireDate; 指定通知发送的时间
@property(nonatomic,copy) NSTimeZone *timeZone; 指定发送通知的时区
@property(nonatomic) NSCalendarUnit repeatInterval; 重复的周期, 枚举
@property(nonatomic,copy) NSCalendar *repeatCalendar; 重复的周期 , 2014.12.12
@property(nonatomic,copy) NSString *alertBody; 通知内容
@property(nonatomic) BOOL hasAction; 是否需要华东事件
@property(nonatomic,copy) NSString *alertAction; 锁屏状态的标题
@property(nonatomic,copy) NSString *alertLaunchImage; 点击通知之后的启动图片
@property(nonatomic,copy) NSString *soundName; 收到通知播放的音乐
@property(nonatomic) NSInteger applicationIconBadgeNumber; 图标提醒数字
@property(nonatomic,copy) NSDictionary *userInfo; 额外的信息
*/
#warning 验证程序的四大功能
#warning 功能1:点击"注册通知"按钮,等5秒看屏幕文字的变化.
#warning 功能2:点击"注册通知"按钮,立刻将程序进入后台,然后点击收到的通知进入程序,看屏幕文字的变化 .
#warning 功能3:点击"注册通知"按钮,立刻将程序关闭,然后点击收到的通知进入程序,看屏幕文字的变化。
#warning 功能4:点击"注册通知"按钮,然后立刻按住Command+L锁屏,然后再次按住Command+L看到滑动解锁的字样,此时你看锁屏界面的变化。
- (IBAction)AddNotification:(UIButton *)sender{
NSLog(@"%s", __func__);
// 1.创建本地通知对象
UILocalNotification *note = [[UILocalNotification alloc] init];
// 指定通知发送的时间(指定5秒之后发送通知,didReceiveLocalNotification代理方法5秒后才调用)
note.fireDate = [NSDate dateWithTimeIntervalSinceNow:10];
// 注意: 在真实开发中一般情况下还需要指定时区(让通知的时间跟随当前时区)
note.timeZone = [NSTimeZone defaultTimeZone];
// 指定通知内容
note.alertBody = @"Hello,我是通知的内容";
// 设置通知重复的周期(1分钟通知一期)
// note.repeatInterval = NSCalendarUnitSecond;
// 指定锁屏界面的信息(Command+L)
note.alertAction = @"Hello,我是锁屏界面的信息";
// 设置点击通知进入程序时候的启动图片
note.alertLaunchImage = @"Default";
// 收到通知播放的音乐
note.soundName = @"m_07.wav";
// 设置应用程序的提醒图标
note.applicationIconBadgeNumber = 345;
// 注册通知时可以指定将来点击通知之后需要传递的数据
note.userInfo = @{@"name":@"zb",
@"age":@"25",
@"phone": @"13324233321"};
// 2.注册通知(图片的名称建议使用应用程序启动的图片名称)
UIApplication *app = [UIApplication sharedApplication];
// 每次调用添加方法都会将通知添加到scheduledLocalNotifications数组中
[app scheduleLocalNotification:note];
}
- (IBAction)CancelNotification:(UIButton *)sender {
UIApplication *app = [UIApplication sharedApplication];
// 清空通知
[app cancelAllLocalNotifications];
}
@end
```
截图
![100.90.gif](http://upload-images.jianshu.io/upload_images/2364940-a4e7a5b4aff703ac.gif?imageMogr2/auto-orient/strip)
![100.91.gif](http://upload-images.jianshu.io/upload_images/2364940-a15d34a4e24871b3.gif?imageMogr2/auto-orient/strip)
![100.92.gif](http://upload-images.jianshu.io/upload_images/2364940-8c660d72798ce7fb.gif?imageMogr2/auto-orient/strip)
![100.93.gif](http://upload-images.jianshu.io/upload_images/2364940-550719b5d0bff3a0.gif?imageMogr2/auto-orient/strip)
---
#开源库、闭源库
---
- 开源库:能看到具体实现的代码(.m文件)。例如:AFNetworking、SDWebImage、MJRefresh
- 闭源库:实现的代码(.m)看不到,.m文件被加密成了.a文件。例如:Jpush、友盟、百度
- 闭源库分为:
- 静态库: 以.a结尾 和.framework结尾
- 动态库: 以.dylib结尾和.framework结尾
---
#MRC如何转成ARC
---
- 将MRC环境中的文件打包成静态库,直接就可以在ARC环境中使用了。
---
#打包静态库
---
#####1.Framework & Library-->Cocoa Touch Static Library-->创建成功,可看到小房子图标
![61-39.gif](http://upload-images.jianshu.io/upload_images/2364940-dab218ed8e5b59e0.gif?imageMogr2/auto-orient/strip)
#####2.在桌面新建一个文件夹,右键把文件名的后缀改为.bundle的,右键显示包内容将用到的图片放进包中,最后将包放到项目中
![61-40.gif](http://upload-images.jianshu.io/upload_images/2364940-ec27941f225a237c.gif?imageMogr2/auto-orient/strip)
#####3.创建类,写上代码,一定要选择Build Phases->Copy Files->添加类的头文件,让头文件参与编译,否则编译时不会生成.h文件
![61-41.gif](http://upload-images.jianshu.io/upload_images/2364940-3c2cbbc05952122d.gif?imageMogr2/auto-orient/strip)
#####4.打包静态库,Command+B编译模拟器状态的xcode项目,然后再Command+B编译真机状态的xcode项目,这样Products文件夹下的.a就会由红色变为黑色,右键.a文件,选择Show in Finder可以看到模拟机的文件夹和真机的文件夹,每个文件夹中都有静态库+声明文件
![61-42.gif](http://upload-images.jianshu.io/upload_images/2364940-fd8e51bb8f6197d9.gif?imageMogr2/auto-orient/strip)
#####5.将模拟器的静态库和真机的静态库合并成一个静态库,可以解决默认情况下真机文件夹下的静态库只能用于真机上,模拟器下的静态库只能用于模拟器下的问题。具体做法:终端输入cd xxx 切换到Xcode中的Products目录下,再输入lipo -create 模拟器的静态库1的路径 真机的静态库2的路径 -output 合并之后的文件名称,以后可以拿合并之后的文件名称,直接应用到模拟器上或者真机上都行。
![61-43.gif](http://upload-images.jianshu.io/upload_images/2364940-1e83811f96f108a3.gif?imageMogr2/auto-orient/strip)
#####6.将静态库(.a)+声明文件+项目中的.bundle文件放到一个Lib文件夹中,然后拖入到另一个项目中使用
![61-44.gif](http://upload-images.jianshu.io/upload_images/2364940-2421763fb0122df7.gif?imageMogr2/auto-orient/strip)
#####7.在另一个项目中导入Lib的类名,并调用类中的接口,具体实现的代码在加密的.a文件中
- 注意:调用loadLogoImage方法加载Bundle中的文件目录以及图片名时,一定要和真实的目录和图片名一致,如果有大小写字母不一致,那么在真机上是无法显示Bundle中的图片的,只是在模拟器中显示,因为苹果公司对手机app的代码的精确度要求的非常非常严。我的步骤7就是出现了Bundle文件夹的B我写成了小写的,就出现了上面提到的情况。
---
#####将真机的静态库文件放到模拟器中运行时会提示如下错误 或 者将模拟器的静态库文件放到真机中运行也会提示如下错误
![61-39.png](http://upload-images.jianshu.io/upload_images/2364940-0248326177b1cb49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####解决办法:
- 将模拟器的静态库和真机的静态库合并成一个静态库,可以解决默认情况下真机文件夹下的静态库只能用于真机上,模拟器下的静态库只能用于模拟器下的问题。
- 即:lipo -create 模拟器的静态库1的路径 真机的静态库2的路径 -output 合并之后的文件名称
---
#精态库其他知识点
---
- 1.打包的静态库默认是打包的是Debug阶段的静态库,也可以改成Release阶段的静态库,但是最终都会生成这两个阶段的模拟器文件夹和真机文件夹
- 真机的Debug版本
- 真机的Release版本
- 模拟器的Debug版本
- 模拟器的Release版本
- 2.Debug版本和Release版本的比较
- 调试版本会包含完整的符号信息,以方便调试
- 调试版本不会对代码进行优化
- 发布版本不会包含完整的符号信息
- 发布版本的执行代码是进行过优化的
- 发布版本的大小会比调试版本的略小
- 在执行速度方面,发布版本会更快些,但不意味着会有显著的提升
- 3.静态库的使用
- 新建项目时,是将编译生成的.a静态库和 .h文件拖到新建的项目中使用的。
- 动态库是不能上传到app store中的,静态库可以上传到app store
---
#打包动态库
---
#####1.Framework & Library-->Cocoa Touch Framework-->创建成功,可看到工具箱图标
![61-46.gif](http://upload-images.jianshu.io/upload_images/2364940-c32059198c791c0e.gif?imageMogr2/auto-orient/strip)
#####2.在桌面新建一个文件夹,右键把文件名的后缀改为.bundle的,然后右键显示包内容将用到的图片放进包中,最后将包放到项目中
![61-47.gif](http://upload-images.jianshu.io/upload_images/2364940-591bb3de5e48609d.gif?imageMogr2/auto-orient/strip)
#####3.创建类,写上代码,一定要选择Build Phases->Headers->将Project选项中的.h文件拖动到Public选项中,目的就是让刚才创建的类参与编译,否则编译时不会生成.h文件
![61-48.gif](http://upload-images.jianshu.io/upload_images/2364940-ca4ae4c743562e2c.gif?imageMogr2/auto-orient/strip)
#####4.打包动态库,Command+B编译模拟器状态的xcode项目,然后再Command+B编译真机状态的xcode项目,这样Products文件夹下的.a就会由红色变为黑色,右键.a文件,选择Show in Finder可以看到模拟机的文件夹和真机的文件夹,每个文件夹中动态库,动态库中存储着未加密的.h文件和加密d但是你看不见的.m文件以及存储着图片的Bundle文件(只要动态库项目中有Bundle文件,那么打包动态库时,动态库会将将Bundle文件自动存储在自己的库中)
![61-49.gif](http://upload-images.jianshu.io/upload_images/2364940-6646378426684d2d.gif?imageMogr2/auto-orient/strip)
#####5.将模拟器的动态库和真机的动态库合并成一个动态库,可以解决默认情况下真机文件夹下的动态库只能用于真机上,模拟器下的动态库只能用于模拟器下的问题。具体做法:终端输入cd xxx 切换到Xcode中的Products目录下,再输入lipo -create 模拟器的动态库1的路径 真机佛你还态库2的路径 -output 合并之后的文件名称,以后可以拿合并之后的文件名称Dy,直接应用到模拟器上或者真机上都行。注意:两个动态库的路径是和Info.plist在目录中
![61-50.gif](http://upload-images.jianshu.io/upload_images/2364940-966aa6398d08ae8a.gif?imageMogr2/auto-orient/strip)
#####6.拷贝模拟器的动态库或者真机的动态库的任意一个就行,重用名为MergeDy,并将合并之后的文件Dy替换掉MergeDy中的Dy,然后拖入到另一个项目中使用
![61-51.gif](http://upload-images.jianshu.io/upload_images/2364940-fd525fda10d94091.gif?imageMogr2/auto-orient/strip)
#####7.运行则会提示image not found的错误,必须在General->Embedded Binaries中将动态库添加进去
![61-52.gif](http://upload-images.jianshu.io/upload_images/2364940-7fbd4c953e74f9ae.gif?imageMogr2/auto-orient/strip)
#####8.在另一个项目中导入#import <Dy/ZBTool.h>,并调用类中的接口即可
![61-53.gif](http://upload-images.jianshu.io/upload_images/2364940-4cc92d82a8111b02.gif?imageMogr2/auto-orient/strip)
---
#动态库其他知识点
---
- 1.默认创建的framework是动态库,改成静态库非常简单,即点击Build Settings-->Linking-->Mach-O Type->Static Library .
- 如何验证改成了静态库?
- 因为静态库不需要在General-->Embedded Binaries中导入framework就能运行成功,如果还是动态库的话,不导入framework会提示错误。所以,经过验证,不导入framework也能运行成功,说明是改为了静态库
- 2.添加的framework默认是动态库,必须在General->Embedded Binaries中将framework添加进来,否则提示image not found的错误
- 3.将模拟器的动态库和真机的动态库合并成一个动态库,可以解决默认情况下真机文件夹下的动态库只能用于真机上,模拟器下的动态库只能用于模拟器下的问题。