17.0. IntroductionNotifications

通知可以携带数据被广播到多个接收对象上。利用它可以很好的分解工作(code),但如果使用不当,也是很容易失控的。

3种类型的通知

一般通知(NSNotification):app可以发送这种通知,iOS也可以发送这种通知,比如键盘弹出,隐藏。利用这些通知可以更好的解耦代码,可以把复杂的iOS应用清楚的分成几个部分、

本地通知(UILocalNotification):你安排这种通知,使其在特定的时间通知你的应用。

你的应用将收到通知,即使是进入后台,或根本就没运行,如果没运行,你的应用将会启动起来。

如果你希望在某个特定时间,确保你的程序可以响应时,那么你可能就会设定这种通知

推送通知(Push notifications):APNS服务器发过来的推送通知

本地通知比较特别,他们可以是可视的,用户可以对他们进行操作。对于用户的操作,你的应用将得到iOS的通知。

而一般通知是不可视的项,只能在应用内部广播。用户不参与的。当然了,接收通知后,可以弹出UI让用户参与。

17.1. Sending Notifications

NSString *const kNotificationName =  @"NotificationNameGoesHere";
    NSNotification *notification = [NSNotification
notificationWithName:kNotificationName
object:self
userInfo:@{@"Key 1" : @"Value 1",
@"Key 2" : @2}];
    [[NSNotificationCenter defaultCenter] postNotification:notification];
 
 
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary

Name:消息的名称

object:一般设置为self,表示谁发送的消息,当然要设置成nil也可以。当有多个对象发送同一个消息时,接收者通过这个判断是谁发送的消息,如何处理

userInfo:是你能够在消息通多带一些数据过去。

17.2. Listening for and Reacting to Notifications

监听和处理消息

#import "AppDelegate.h"
 //#import "Person.h"
 
@interface Person : NSObject
 (strong,nonatomic) NSString * firstName;
 (strong,nonatomic) NSString * lastName;
 
 @end
 
 Person
- (void) handleSetPersonInfoNotification:(NSNotification
    NSLog(@"%s",__FUNCTION__);
 NSLog(@"userInfo=%@",paramNotification.userInfo);
    self.firstName = paramNotification.userInfo[kSetPersonInfoKeyFirstName];
    self.lastName = paramNotification.userInfo[kSetPersonInfoKeyLastName];
     
}
 
- (instancetype) init{
     
 self = [super  init];
 if (self !=  nil){
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
 addObserver:self  selector:@selector(handleSetPersonInfoNotification:)
                       name:kSetPersonInfoNotification
                     object:[[UIApplication sharedApplication] delegate]];
    }
    return  self;
}
 
- (void) dealloc{
    NSLog(@"%s",__FUNCTION__);
 
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 
 @end
 
NSString *const kSetPersonInfoNotification = @"SetPersonInfoNotification";
NSString *const kSetPersonInfoKeyFirstName = @"firstName";
NSString *const kSetPersonInfoKeyLastName = @"lastName";
 
 AppDelegate
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
{
    // Override point for customization after application launch.
 Person *steveJobs = [[Person  alloc] init];
    NSNotification *notification =[NSNotification
notificationWithName:kSetPersonInfoNotification
object:self
userInfo:@{kSetPersonInfoKeyFirstName : @"Steve",
kSetPersonInfoKeyLastName :  @"Jobs"}];
    /* The person class is currently listening for this notification. That class
      will extract the first name and last name from it and set its own first
      name and last name based on the userInfo dictionary of the notification. */
    [[NSNotificationCenter defaultCenter] postNotification:notification];
    /* Here is proof */
    NSLog(@"Person's first name = %@", steveJobs.firstName);
    NSLog(@"Person's last name = %@", steveJobs.lastName);
     
    return  YES;
}
 
 
 @end
 
 
打印:
2014-07-15 11:11:21.443 cookbook7_17_2[487:a0b] -[Person handleSetPersonInfoNotification:]
2014-07-15 11:11:21.444 cookbook7_17_2[487:a0b] userInfo={
    firstName = Steve;
    lastName = Jobs;
}

2014-07-15 11:11:21.444 cookbook7_17_2[487:a0b] Person's first name = Steve

2014-07-15 11:11:21.445 cookbook7_17_2[487:a0b] Person's last name = Jobs

2014-07-15 11:11:21.445 cookbook7_17_2[487:a0b] -[Person dealloc]

有没有发现,消息发出后,会先处理消息,处理完再回来继续往下走,单线程这样处理比较方便,也比较好处理不是吗。

千万不要以为消息发出后,就继续往下走,消息在未来的某个时间才被处理

17.3. Listening and Reacting to Keyboard Notifications

监听处理键盘消息

有时候键盘一弹出来就会覆盖掉你的UI,而你又希望你的UI在键盘弹出后能够被看见,比如说编辑框。要是被遮住了,那体验可真不好。

#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController
 
 (strong, nonatomic) IBOutlet UIScrollView *scrollView;
 (strong, nonatomic) IBOutlet UITextField *textField;
 (strong, nonatomic) IBOutlet UIImageView *imageView;
 
 
 @end
 
#import "ViewController.h"
 //#import <UIKit/UIKit.h>
 
@interface  ViewController () <UITextFieldDelegate>
 
 @end
 
@implementation
 
- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    self.scrollView.contentSize = self.imageView.frame.size;
}
 
- (void) viewWillAppear:(BOOL)paramAnimated{
super viewWillAppear:paramAnimated];
     
    NSLog(@"contentsize=%@",NSStringFromCGSize(self.scrollView.contentSize));
    NSLog(@"self.scrollView.frame=%@",NSStringFromCGRect(self.scrollView.frame));
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
 addObserver:self  selector:@selector(handleKeyboardWillShow:)
                   name:UIKeyboardWillShowNotification object:nil];
 addObserver:self  selector:@selector(handleKeyboardWillHide:)
                   name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)paramAnimated{
super viewWillDisappear:paramAnimated];
     
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
     
     
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
- (void) handleKeyboardWillShow:(NSNotification
    NSLog(@"%s",__FUNCTION__);
 NSDictionary *userInfo = paramNotification.userInfo;
     
    /* Get the duration of the animation of the keyboard for when it
      gets displayed on the screen. We will animate our contents using
      the same animation duration */
    NSValue *animationDurationObject = userInfo[UIKeyboardAnimationDurationUserInfoKey];
 NSValue *keyboardEndRectObject = userInfo[UIKeyboardFrameEndUserInfoKey];
 double animationDuration = 0.0;
 CGRect keyboardEndRect = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f);
getValue:&animationDuration];
getValue:&keyboardEndRect];
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
     
    /* Convert the frame from window's coordinate system to
      our view's coordinate system */
self.view convertRect:keyboardEndRect  fromView:window];
 NSLog(@"keyboardEndRect=%@",NSStringFromCGRect(keyboardEndRect));
 //    [NSString stringwith]
     
    /* Find out how much of our view is being covered by the keyboard */
 CGRect intersectionOfKeyboardRectAndWindowRect =  CGRectIntersection(self.view.frame, keyboardEndRect);
 NSLog(@"intersectionOfKeyboardRectAndWindowRect=%@",NSStringFromCGRect(intersectionOfKeyboardRectAndWindowRect));
     
    /* Scroll the scroll view up to show the full contents of our view */
    [UIView animateWithDuration:animationDuration animations:^{
 self.scrollView.contentInset
        UIEdgeInsetsMake(0.0f,
0.0f,
size.height,
0.0f);
        NSLog(@"contentInset=%@",NSStringFromUIEdgeInsets(self.scrollView.contentInset));
        [self.scrollView scrollRectToVisible:self.textField.frame animated:NO];
    }];
}
 
- (void) handleKeyboardWillHide:(NSNotification
    NSLog(@"%s",__FUNCTION__);
     
 NSDictionary *userInfo = [paramSender userInfo];
 NSValue
    [userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey];
 double animationDuration = 0.0;
getValue:&animationDuration];
    [UIView animateWithDuration:animationDuration animations:^{
        self.scrollView.contentInset = UIEdgeInsetsZero;
    }];
}
 
- (BOOL) textFieldShouldReturn:(UITextField
    NSLog(@"%s",__FUNCTION__);
resignFirstResponder];
    return  YES;
}

@end

打印:

2014-07-15 16:10:48.413 cookbook7_17_2[1209:c07] contentsize={320, 950}

2014-07-15 16:10:48.415 cookbook7_17_2[1209:c07] self.scrollView.frame={{0, 0}, {320, 460}}

2014-07-15 16:10:50.152 cookbook7_17_2[1209:c07] -[ViewController handleKeyboardWillShow:]

2014-07-15 16:10:50.152 cookbook7_17_2[1209:c07] keyboardEndRect={{0, 244}, {320, 216}}

2014-07-15 16:10:50.153 cookbook7_17_2[1209:c07] intersectionOfKeyboardRectAndWindowRect={{0, 244}, {320, 216}}

2014-07-15 16:10:50.153 cookbook7_17_2[1209:c07] contentInset={0, 0, 216, 0}

2014-07-15 16:10:52.386 cookbook7_17_2[1209:c07] -[ViewController textFieldShouldReturn:]

2014-07-15 16:10:52.388 cookbook7_17_2[1209:c07] -[ViewController handleKeyboardWillHide:]

悲催的是,当键盘弹出时,self.scrollView并没有滚动,self.textField也被键盘遮住了,不知道我哪里没写对

17.4. Scheduling Local Notifications

如果开发一个闹钟或日历应用,那么在程序没运行时,或者在后台时,通知用户(响闹钟等)

UILocalNotification,

[UIApplication sharedAp plication]

当应用没在运行,或是在后台运行时,本地通知local notification 自己会弹消息通知给用户

当应用在前台运行时,将不会弹出消息,而是调用app 代理。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
{
    // Override point for customization after application launch.
    UILocalNotification *notification = [[UILocalNotification alloc]  init];
    /* Time and timezone settings */
    notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:8.0];
timeZone = [[NSCalendar currentCalendar] timeZone];
    notification.alertBody = NSLocalizedString(@"A new item is downloaded.", nil);
    /* Action settings */
hasAction = YES;
alertAction = NSLocalizedString(@"View", nil);
    /* Badge settings */
    notification.applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber + 1;
    /* Additional information, user info */
userInfo = @{@"Key 1" : @"Value 1",
@"Key 2" : @"Value 2"};
    /* Schedule the notification */
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
    return  YES;
}

运行之后8秒,状态栏会弹出消息,app引用图片右上角增加消息条数标号

17.5. Listening for and Reacting to Local Notifications

监听和处理本地消息UILocalNotification

应用的状态不一样,消息的处理也不一样

应用在前台运行时:调用application:didReceiveLocalNotification:

应用在后台运行时:一旦用户点击通知,iOS将激活应用,然后调用application:didReceiveLocalNotification:

应用没运行:调用application:didFinishLaunchingWithOptions: Option参数的中UIApplicationLaunchOptionsLocalNotificationKey键值就是对应的消息。

#import "AppDelegate.h"
 
 AppDelegate
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
{
    NSLog(@"%s",__FUNCTION__);
    //如果是消息启动了程序,则处理消息,否则重建一个消息
    if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] != nil){
notification
self
 else
self
    }
    
    return  YES;
}
- (void) scheduleLocalNotification{
    NSLog(@"%s",__FUNCTION__);
 UILocalNotification *notification
    /* Time and timezone settings */
    notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:8.0];
timeZone
@"A new item is downloaded.", nil);
    /* Action settings */
hasAction = YES;
@"View", nil);
    /* Badge settings */
applicationIconBadgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber + 1;
    /* Additional information, user info */
@{@"Key 1" : @"Value 1",
@"Key 2" : @"Value 2"};
    /* Schedule the notification */
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
 
- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    NSLog(@"%s",__FUNCTION__);
 NSString *key1Value = notification.userInfo[@"Key 1"];
key2Value = notification.userInfo[@"Key 2"];
 if ([key1Value length] > 0 && [key2Value length] > 0){
        NSLog(@"Handling the local notification");
 UIAlertView *alert
UIAlertView alloc] initWithTitle:nil
@"Handling the local notification"
nil
cancelButtonTitle:@"OK"
nil];
        [alert show];
         
    }
}
 
 @end

运行程序后等待8秒,打印:

2014-07-15 17:45:06.543 cookbook7_17_4[1526:a0b] -[AppDelegate application:didFinishLaunchingWithOptions:]

2014-07-15 17:45:06.544 cookbook7_17_4[1526:a0b] -[AppDelegate scheduleLocalNotification]

2014-07-15 17:45:06.551 cookbook7_17_4[1526:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-15 17:45:14.546 cookbook7_17_4[1526:a0b] -[AppDelegate application:didReceiveLocalNotification:]

2014-07-15 17:45:14.547 cookbook7_17_4[1526:a0b] Handling the local notification

运行程序后,home键进入后台等待,弹出通知后点击通知

2014-07-15 17:45:48.846 cookbook7_17_4[1536:a0b] -[AppDelegate application:didFinishLaunchingWithOptions:]

2014-07-15 17:45:48.846 cookbook7_17_4[1536:a0b] -[AppDelegate scheduleLocalNotification]

2014-07-15 17:45:48.853 cookbook7_17_4[1536:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-15 17:45:52.860 cookbook7_17_4[1536:a0b] -[AppDelegate applicationWillResignActive:]

2014-07-15 17:45:52.862 cookbook7_17_4[1536:a0b] -[AppDelegate applicationDidEnterBackground:]

2014-07-15 17:45:59.613 cookbook7_17_4[1536:a0b] -[AppDelegate applicationWillEnterForeground:]

2014-07-15 17:45:59.614 cookbook7_17_4[1536:a0b] -[AppDelegate application:didReceiveLocalNotification:]

2014-07-15 17:45:59.615 cookbook7_17_4[1536:a0b] Handling the local notification

2014-07-15 17:45:59.672 cookbook7_17_4[1536:a0b] -[AppDelegate applicationDidBecomeActive:]

17.6. Handling Local System Notifications

监听系统通知

NSCurrentLocaleDidChangeNotification:修改应用场景(the user changes her locale),比如修改语言设置

NSUserDefaultsDidChangeNotification: iOS设置页面修改应用设置(如果有的话)

UIDeviceBatteryStateDidChangeNotification: 电池状态改变,比如充电

UIDeviceProximityStateDidChangeNotification:应该是距离感应吧( the state of the proximity sensor changes)

在程序进入后台后会发生很多事情,比如左横屏、右横屏、竖屏等。当程序再次进入前台时,iOS只会通知最后一次屏幕方向消息。

#import "ViewController.h"
 
@interface  ViewController ()
 
 @end
 
@implementation
 
- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
- (void) orientationChanged:(NSNotification
    NSLog(@"Orientation Changed ");
}
 
- (void)viewDidAppear:(BOOL)paramAnimated{
super viewDidAppear:paramAnimated];
    /* Listen for the notification */
    [[NSNotificationCenter defaultCenter]
     addObserver:self
selector:@selector(orientationChanged:)
     name:UIDeviceOrientationDidChangeNotification
object:nil];
}
- (void) viewDidDisappear:(BOOL)paramAnimated{
super viewDidDisappear:paramAnimated];
    /* Stop listening for the notification */
    [[NSNotificationCenter defaultCenter]
     removeObserver:self
     name:UIDeviceOrientationDidChangeNotification
object:nil];
}
 
 @end

模拟器启动应用后,左旋,然后右旋打印:

2014-07-16 11:45:54.945 cookbook7_17_4[496:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-16 11:46:02.222 cookbook7_17_4[496:a0b] Orientation Changed 

2014-07-16 11:46:04.943 cookbook7_17_4[496:a0b] Orientation Changed 

模拟器启动应用后,首页,左旋,右旋,点击应用回到前台,打印:

2014-07-16 11:47:09.302 cookbook7_17_4[508:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-16 11:47:14.267 cookbook7_17_4[508:a0b] -[AppDelegate applicationWillResignActive:]

2014-07-16 11:47:14.269 cookbook7_17_4[508:a0b] -[AppDelegate applicationDidEnterBackground:]

2014-07-16 11:47:25.610 cookbook7_17_4[508:a0b] -[AppDelegate applicationWillEnterForeground:]

2014-07-16 11:47:25.612 cookbook7_17_4[508:a0b] -[AppDelegate applicationDidBecomeActive:]

没有打印Orientation Changed,噢,又是一次失败的尝试,是模拟器的原因吗?

给应用增加一些设置,

Xcode -> new file -> iOS category -> resources subcategory -> setting bundle -> save

运行后会在系统的设置页增加应用的设置


#import "AppDelegate.h"
 
 AppDelegate
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
{
    // Override point for customization after application launch.
    [[NSNotificationCenter defaultCenter]
     addObserver:self
selector:@selector(handleSettingsChanged:)
     name:NSUserDefaultsDidChangeNotification
object:nil];
    return  YES;
}
 
- (void) handleSettingsChanged:(NSNotification
    NSLog(@"Settings changed");
NSLog(@"Notification Object = %@", paramNotification.object);
    NSLog(@"Notification userinfo = %@", paramNotification.userInfo);
}

- (void)applicationWillResignActive:(UIApplication
{
    NSLog(@"%s",__FUNCTION__);
}
 
- (void)applicationDidEnterBackground:(UIApplication
{
    NSLog(@"%s",__FUNCTION__);
}
 
- (void)applicationWillEnterForeground:(UIApplication
{
    NSLog(@"%s",__FUNCTION__);
}
 
- (void)applicationDidBecomeActive:(UIApplication
{
    NSLog(@"%s",__FUNCTION__);
}
 
- (void)applicationWillTerminate:(UIApplication
{
    NSLog(@"%s",__FUNCTION__);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 
 @end

2014-07-16 14:06:23.179 cookbook7_17_6[463:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-16 14:06:25.119 cookbook7_17_6[463:a0b] Settings changed

2014-07-16 14:06:25.120 cookbook7_17_6[463:a0b] Notification Object = <NSUserDefaults: 0x8943b70>

2014-07-16 14:06:25.121 cookbook7_17_6[463:a0b] Notification userinfo = (null)

2014-07-16 14:06:30.503 cookbook7_17_6[463:a0b] -[AppDelegate applicationWillResignActive:]

2014-07-16 14:06:30.505 cookbook7_17_6[463:a0b] -[AppDelegate applicationDidEnterBackground:]

2014-07-16 14:06:58.983 cookbook7_17_6[463:a0b] -[AppDelegate applicationWillEnterForeground:]

2014-07-16 14:06:58.985 cookbook7_17_6[463:a0b] -[AppDelegate applicationDidBecomeActive:]

2014-07-16 14:06:58.986 cookbook7_17_6[463:a0b] Settings changed

2014-07-16 14:06:58.986 cookbook7_17_6[463:a0b] Notification Object = <NSUserDefaults: 0x8943b70>

2014-07-16 14:06:58.987 cookbook7_17_6[463:a0b] Notification userinfo = (null)

17.7. Setting Up Your App for Push Notifications

推送消息配置

1,添加配置文件开启推送消息

2,注册消息推送

3,收集设备推送id,发送给服务器

想让应用能够接收推送消息,必须要有配置文件,配置过程参照:

1,登陆开发者中心
2,Certificates, Identifiers & Profiles
3,在Identifiers 部分,创建你的应用ID。如:com.pixolity.ios.cookbook.PushNotificationApp 
4,在appID的application serverces部分,确保 Push Notifications 为Enable。
5,提交操作
6,切到Provisioning Profiles 
7,为你的应用创建开发者证书,你可以创建ad hoc和app store版本的证书,不过现在,你只需要创建Development版本的。当你要发布到应用商店的时候你再来创建app store版本的。
请确保你你创建的证书关联了你刚才创建app id
8,下载证书并把它拖到iTunes来安装,请不要用双击来安装,这会改变安装的证书的文件名,以至于。。。。( Doing so will change the name of the installed profile’s filename to the MD5 hash name of the profile, which is very difficult to identify on disk. )
9,在Xcode编译设置那边,选择刚安装的证书(发布的时候,选择发布版本的)
10,拖拽证书到文件编辑器,会发现证书内容大致如下:
<key>Entitlements</key>
<dict> 
<key>application-identifier</key> <string>F3FU372W5M.com.pixolity.ios.cookbook.PushNotificationApp</string> 
<key>aps-environment</key>
<string>development</string>
<key>get-task-allow</key>
<true/>
<key>keychain-access-groups</key>
<array> 
<string>F3FU372W5M.*</string></array> 
</dict> 11,Xcode中创建新的plist文件,命名为Entitlements.plist.  open as -> Source Code  结果大致如下:
 
version="1.0">
 <dict/>
 </plist>

12,把证书的内容节点copy过来,结果:

version="1.0"> <dict>
 <key>application-identifier</key>
 <string>F3FU372W5M.com.pixolity.ios.cookbook.PushNotificationApp</string>
 <key>aps-environment</key>
    <string>development</string>
 <key>get-task-allow</key>
    <true/>
 <key>keychain-access-groups</key>
    <array>
 <string>F3FU372W5M.*</string>
    </array>
 </dict>
 </plist>

13,编译选项中,如果Entitlements.plist在target目录下则 Code Signing 输入$(SRCROOT)/$(TARGET_NAME)/Entitlements.plist ,如果在工程的目录下则输入$(SRCROOT)/Entitlements.plist ,路径要对,否则编译器会抱怨找不到文件的

14,编译程序,确保没报错,如果有,可能是你的证书有问题,或是证书路径问题

15,在应用代理里面注册远程消息通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary
{
    // Override point for customization after application launch.
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
     UIRemoteNotificationTypeAlert |
     UIRemoteNotificationTypeBadge |
     UIRemoteNotificationTypeSound];
return YES;
}

设备将发送一条推送消息的注册请求到APNS,别当心,他会先咨询用户的同意。

16,实现代理application:didRegisterForRemoteNotificationsWithDeviceToken:,当成功注册推送消息通知时,会获得一个token

17,实现代理application:didFailToRegisterForRemoteNotifications WithError:,注册失败,原因可能会是:证书不对,网络连接不成功等

ok that is all 

17.8. Delivering Push Notifications to Your App

推送消息到用户的设备

为了与APNS通讯,你需要Apple-issued SSL certificate,生成过程如下:

1,登陆开发者中心

2,Certificates, Identifiers & Profiles

3,找到对应App ID 编辑

4,在Push Notification项部分,点击【Create Certificate】创建证书,现在我们只需创建development ssl certificate,当你要发布的时候再创建Distribution all certificate 

5,下载证书到你的电脑上,双击以安装到钥匙串

6,钥匙串窗体中,钥匙串->登陆, 种类->我的证书,可以看到刚安装的证书

7,右键证书导出,选择.cer格式,命名 PushCertificate.cer. 

8,右键专用密钥导出,选择.p12格式,命名PushKey.p12,会提示输入密码,要记住密码

下面将用php发送一个简单的推送到设备为例

创建PEM文件,

假设PushKey.p12 PushCertificate.cer 文件在桌面上

1,打开Terminal

2,openssl x509 -in PushCertificate.cer -inform der -out PushCertificate.pem

3,openssl pkcs12 -nocerts -in PushKey.p12 -out PushKey.pem 

4,他会要求你输入密码,那个你从钥匙串那边导入时设定的密码。一旦密码校验通过,OpenSSL会要求你为PEN文件设定一个密码,而且至少4个字符,当然密码是要记住的

5,ok,现在我们有了两个pem文件了PushCertificate.pem PushKey.pem ,现在你需要把它们合并成一个。

cat PushCertificate.pem PushKey.pem > PushCertificateAndKey.pem

6,测试一下能不能用生成的.pem文件连接到apns

  • openssl s_client -connect gateway.sandbox.push.apple.com:2195 \
  • -cert PushCertificate.pem -key PushKey.pem

如果一切顺利,他将要求你输入密码。如果连接成功,将可以看到OpenSSL要求你输入一些字符来关闭连接。随便输几个字符回车就可以关闭。这意味着你用那个证书和专用密钥连接服务器成功了。

好了,是时候利用php脚本来给设备发送一条推送了。首先需要获得设备的token。

- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData
    /* Each byte in the data will be translated to its hex value like 0x01 or
      0xAB excluding the 0x part, so for 1 byte, we will need 2 characters to
      represent that byte, hence the * 2 */
    NSLog(@"%s",__FUNCTION__);
    NSMutableString *tokenAsString = [[NSMutableString alloc]
initWithCapacity:deviceToken.length * 2];
 char *bytes = malloc(deviceToken.length); [deviceToken getBytes:bytes];
 for (NSUInteger byteCounter =  0; byteCounter < deviceToken.length; byteCounter++){
 char
appendFormat:@"%02hhX", byte];
    }
 free(bytes);
 NSLog(@"Token = %@", tokenAsString);
}
 
打印的信息类似如下:
Token = 05924634A8EB6B84437A1E8CE02E6BE6683DEC83FB38680A7DFD6A04C6CC586E
 
php 脚本
<?php
     /* We are using the sandbox version of the APNS for development. For production
      environments, change this to ssl://gateway.push.apple.com:2195 */
    $apnsServer =  'ssl://gateway.sandbox.push.apple.com:2195';
     /* Make sure this is set to the password that you set for your private key
      when you exported it to the .pem file using openssl on your OS X */
$privateKeyPassword = '1234';
/* Put your own message here if you want to */    $message =  'Welcome to iOS 7 Push Notifications';
     /* Pur your device token here */
=
;
     /* Replace this with the name of the file that you have placed by your PHP
      script file, containing your private key and certificate that you generated
      earlier */
= 'PushCertificateAndKey.pem';
    $stream =  stream_context_create();
($stream,
,
,
);
($stream,
,
,
);
= 20;
    $connectionType = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
= stream_socket_client($apnsServer,
,
,
,
,
);
if (!$connection){
echo "Failed to connect to the APNS server. Error no = $errorNumber<br/>"; exit;} else  {
echo "Successfully connected to the APNS. Processing...</br>";}
$messageBody['aps'] = array('alert' => $message, 'sound' => 'default',
=> 2,
                                );
= json_encode($messageBody);
= chr(0) .
('n', 32)  .
('H*', $deviceToken) .
('n', strlen($payload)) .
;
= fwrite($connection, $notification, strlen($notification));
if (!$wroteSuccessfully){
echo "Could not send the message<br/>";}
else {echo "Successfully sent the message<br/>"; }
    fclose($connection);

替换你的token

如果万事大吉,那么用浏览器打开你的php脚本,就可以在手机上看到推送的消息。php脚本发送通知到apns服务器,apns再把通知传给手机。当推送通知传到app,而手机当时锁屏,则可在通知栏看到相应的通知。

17.9. Reacting to Push Notifications

如何处理推送消息

推送消息已经传到手机上了,如何处理呢?

实现 application:didReceiveRemoteNotification:  代理

当用户点击推送消息,而你的应用在前台或者后台,但不能是终止状态,这个代理将被调用。

如果你的应用处于终止状态,iOS将启动程序,并把消息封装到option参数中传给application:didFinishLaunchingWithOptions: 代理方法。通过UIApplicationLaunchOptionsRemoteNotificationKey可以获取到这个消息

didReceiveRemoteNotification参数是个NSDictionary.这个字典有个aps字典对象,这个字典对象可能包含以下值

badge: number类型,表示应用图标右上角的应设的徽章数值

alert: String类型,推送通知的消息。

sound: string ,标记着你该播放哪个音乐文件

content-available: number 类型,为1时,表示服务端有新内容,希望你应用能更新下。当然了,实际更不更新那是app的事情。