ios 通知注册在发送前面_ios 发送通知后多次响应通知方法 - CSDN
  • NotificationCocoa中是一个解藕的机制。假如你有一个对象,是一个网络监视器,此时你想告诉其他的对象,网络数据已经下载完毕。这个过程你可以使用很多方法来实现。 你可以下一个网络监听器的子类,并重载
    该文章的原创地址是:http://www.bignerdranch.com/blog/notifications-part-1-registering-and-posting/

    介绍

    Notification在Cocoa中是一个解藕的机制。假如你有一个对象,是一个网络监视器,此时你想告诉其他的对象,网络数据已经下载完毕。这个过程你可以使用很多方法来实现。

    你可以下一个网络监听器的子类,并重载handleDisConnect方法。你也可以用target/action的方式去建立一个网络监听器,并关联自己的实现方法。你也可以用责任链的方式,或者用delegate的方式。还可以用这篇所介绍的方式:Notification。

    许多实现方式都是1对1的方式,一种更好的方法是通过notification的方式在对象之间建立间接的关系,他们之间通过广播的方式进行通知。Notification center就像是在对象之间建立一个转发机构,当一个通知被发送到Notification center中,所有想要接受消息的对象就能接收到该消息。


    右侧的三个对象(App Delegate,data viewer和logging system)都告诉Notification center他们对网络下载通知感兴趣。当网络下载完毕后,网络监听器告诉Notification center把这个消息通知给感兴趣的对象。通知方和接收方之间没有直接的关联,他们是通过广播的形式进行的。这种方式也被称作发现者模式

    notification center像是一个中间人,对象可以通过它自由的像外界发送通知。这些发送通知的类不需要去继承特殊的类,也不需要实现特殊的接口。得益于OC的runtime机制,接收通知的类可以自定义任意selector去处理这些通知,而不需要实现任何接口和callback函数。

    注册Notification

    接收通知的类需要告诉notification center他们对某些感兴趣。notification center就会讲这些类和他们的处理函数纪录在一个内部的列表中,就像是一个电话本。当你开始编程前,这个列表基本上就是一个空的。


    然后在一个对象中写上注册notification的表达式,告诉notification center它对某一个通知感兴趣。

    [code]

    // AppDelegate
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver: self
            selector: @selector(networkByeBye:)
            name: kNetworkWentByeByeNotification
            object: self.networkMonitor];

    上面代码中,AppDelegate告诉notification center,当网络中断的时候,它想要接受到通知,也许它可能尝试重新连接。注意,代码中,AppDelegate对一个名叫kNetworkWentByeByeNotification的通知感兴趣,这是一个NSString类型的对象。如果networkMonitor发送一个名叫kNetworkWentByeByeNotification的通知,则AppDelegate就会用networkByeBye:去处理这个通知。Notification center会记录下AppDelegate,networkMonitor和selector的地址,并用kNetworkWentByeByeNotification来进行标注。


    此时,对象DataViewer对象想要接收网络下载完毕的通知,然后可以改变用户界面,它也用相似的方式注册通知。

    [code]

    // DataViewer
    [center addObserver: self
            selector: @selector(handleNetworkChange:)
            name: kNetworkWentByeByeNotification
            object: nil];

    上面代码中Object传的参数是nil。这说明只要有任何对象发出kNetworkWentByeByeNotification的通知,它都能接收到,这点与AppDelegate不同,他只能接收来自networkMonitor的通知。

    Notification center这个时候会在kNetworkWentByeByeNotification这个标签下增加一条记录,改记录中存储了DataViewer和selector的地址。


    最后,logging system也想接受通知,这次使用使用一个较现代的API来注册通知,其中使用到了block和operation queue的技术。

    [code]

    // LogOTron
    // _token is an instance variable of type 'id'
    _token = [center addObserverForName: kNetworkWentByeByeNotification
                     object: nil
                     queue: [NSOperationQueue mainQueue]
                     usingBlock: ^(NSNotification *notification) {
                         NSLog (@"Network went down: %@", notification);
                     }];

    上述代码是要告诉notification center,“当任何对象发送kNetworkWentByeByeNotification通知,就将block放到这个queue中去执行”,notification center会将这和对象的注册信息像前两次一样进行记录。


    这个现代API与前两种最大的不同就是,没有对应的类和对象来处理这个notitification,前两种注册方式必须有一个对应的类或者对象来处理这个notification,而最后一种只需要提供一个block就就可以了,通过notification center中的记录信息也能看到最后一种方式的特殊之处。

    发送Nofication

    现在已经写好了注册通知的信息,但是什么效果都没有看到。notification center只是记录了有哪些对象对哪些通知感兴趣的信息,但是还没有对象来发送通知。

    netword下载完成后,向notification center发送通知。

    [code]

    // NetworkMonitor
    - (void) networkWentDown {
        // Collect the error
        // Pack the error and other information into a dictionary
        NSDictionary *userInfo = ...;
    
        // Post notification.
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center postNotificationName: kNetworkWentByeByeNotification
                object: self
                userInfo: userInfo];
    
    } // networkWentDown

    最后一行代码,告诉notification center发送一个名为kNetworkWentByeByeNotification的通知给感兴趣的对象。notification center接收到消息之后,通知kNetworkWentByeByeNotification标签下面的对象来接受通知,这个对象接收到通知之后,用之前注册好的selector或者queue来处理通知。

    AppDelegate接收到消息之后,会用networkByeBye来处理通知。Logging system接受到通知后,将block放进main queue中执行。

    注销通知

    当一个对象对某一个通知不感兴趣之后,它应该从notification center中将注册信息注销掉。注销后,notification center就会删除关于改对象的记录信息,并断开和改selector的弱引用,但是如果使用的是block PAI的话,它还会保留对block的引用。所以这个时候要注意是否存在循环引用。当不再接收某个通知的时候,应该尽快的注销掉,或者在对象的dealloc时注销通知。

    注销通知非常容易:

    [code]

    [center removeObserver: self];

    上面的代码是将self中的所有注册的通知都注销掉,为了能比较安全的进行注销,因为指定注销特定的通知,因为有时候有些通知是深藏于Coca中的,此时如果注销掉可能会造成系统异常。

    注销制定的通知

    [code]

    [center removeObserver: self
            name: kNetworkWentByeByeNotification
            object: nil];

    上面的代码只是将kNetworkWentByeByeNotification通知注销掉的,但是其他的仍然保留。

    Logging system中的通知注销要稍微麻烦一些,因为这个通知和改对象没有任何关联,用上面两种方式是无法注销的。所以需要在注册block型的通知时,要将注册信息保留下来,然后在你不需要的时候,在通过这个保留的信息将通知注销掉,上面代码中,是用了一个token的变量保留block API的,这个token是一个notificationcenter类型的变量。

    这种block类型的API应该用下面这种方式进行注销:

    [code]

    [center removeObserver: _token];

    展开全文
  • iOS通知的例子

    2019-03-13 17:21:49
    通知iOS中的一种消息传递方式,通过消息中心(NSNotificationCenter)对消息的监听,当某些类发送出消息的时候,消息中心监听到这些消息,然后进行相应的操作,这些操作对于发送出这些消息的类来说是相同的。...

    一、通知

    通知是iOS中的一种消息传递方式,通过消息中心(NSNotificationCenter)对消息的监听,当某些类发送出消息的时候,消息中心监听到这些消息,然后进行相应的操作,这些操作对于发送出这些消息的类来说是相同的。

    下面通过一个demo来说明通知的实现

                                     

    就是点击按钮,弹出弹窗,然后点击弹窗中的按钮打印出一些信息。 这个信息是由前面自定义的alertView发出给到控制器的,我们用通知来实现这之间的数据传递。

    
    //viewController.m
    - (IBAction)buttonDidClicked:(UIButton *)sender {
        
        LSRAlertView * view = [LSRAlertView alertWithTitle:@"操作完成" andTitleImageName:@"gou"];
        [view addTopButtonWithTitle:@"确定"];
        [view addBottomButtomWithTitle:@"取消"];
        [view show];
        //这里向消息中心注册消息
        //obsever:观察者,谁来监听这个消息
        //selector:监听到发出的对应的消息后要做什么
        //name:监听的消息的名称
        //obeject:保存在消息中心的数据,一般传一个nil
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(sureButtonDidClicked) name:@"SureButtonClickedNotificationName" object:nil];
        //取消按钮我们需要传递一些参数
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(cancelButtonClicked:) name:@"CancelButtonClickedNotificationName" object:nil];
        
    }

    首先在控制器中添加一个按钮点击事件,然后设置弹窗视图(至于如何自定义弹窗视图,请查看我的上一篇文章),接着我们在这里向消息中心注册消息,为什么在这里注册呢?因为我们是要在控制器中获取到弹窗视图的数据,在控制器中注册相应的消息后,控制器就会监听发出我们注册消息的对象,然后执行相应的方法,所以在控制器中注册消息。

    接着我们在自定义弹窗视图的类写发送消息的代码,那么又该在哪里写呢?应该在弹窗视图上的按钮被点击了之后写,所以:

    
    //LSRAlertView.m
    - (IBAction)topButonClicked:(UIButton *)sender {
         [self dismiss];
        //这里按钮被点击了,此时需要发出消息
        //name:消息的名称,必须和所监听的消息的名称相同
        //object:需要回调的数据
        //userInfo:也是需要回调的数据
        [[NSNotificationCenter defaultCenter]postNotificationName:@"SureButtonClickedNotificationName" object:nil userInfo:nil];
       
    }
    - (IBAction)bottomButtonClicked:(UIButton *)sender {
        //发出消息的时候回调参数
        [self dismiss];
        [[NSNotificationCenter defaultCenter]postNotificationName:@"CancelButtonClickedNotificationName" object:@"object" userInfo:@{@"name":@"jack"}];
        
    }

    发送消息我们用postNotificationName,这里需要注意的是发送消息的名称一定要和之前在控制器中注册的消息名对应起来,另外如果我们需要传递一些数据,就将传递的数据写在 object 或者  userInfo 中,前者是一个字符串类型,后者是一个字典类型。

    然后是我们在控制器监听到弹窗视图发出的消息后执行的操作:

    //ViewController.m
    -(void)sureButtonDidClicked{
        //这里不需要回调参数
        NSLog(@"确定按钮被点击了");
        
        //在这里移除消息
        [[NSNotificationCenter defaultCenter]removeObserver:self name:@"SureButtonClickedNotificationName" object:nil];
    } 
    -(void)cancelButtonClicked:(NSNotification *)notification{
        //这里将传递过来的参数打印出来
        NSLog(@"%@",notification.object);
        NSLog(@"%@",notification.userInfo);
        
        //同样在这里移除消息
        [[NSNotificationCenter defaultCenter]removeObserver:self name:@"CancelButtonClickedNotificationName" object:nil];
        
    }

    这里需要注意两点,一是如何取出消息中的数据,我们使用notification的属性object和userInfo来取得相应的数据。第二点也是非常重要的就是一定要将消息中的消息移除,一定要将消息中的消息移除,一定要将消息中的消息移除。如果不移除就会导致点击一次按钮触发多次事件,因为有多个相同的消息同时注册了。

    最后是打印的信息:

    展开全文
  • 通知相关系列文章 iOS10 之前通知使用介绍 [iOS] 通知详解: UIUserNotification iOS10 相关API [iOS] 通知详解:iOS 10 UserNotifications API iOS10 本地/远程通知 [iOS] 通知详解: iOS 10 ...

    通知相关系列文章
    iOS10 之前通知使用介绍
    [iOS] 通知详解: UIUserNotification
    iOS10 相关API
    [iOS] 通知详解:iOS 10 UserNotifications API
    iOS10 本地/远程通知
    [iOS] 通知详解: iOS 10 UserNotifications
    iOS10 通知附加包
    [iOS] 通知详解: iOS 10 UserNotifications – 附加包Media Attachments
    iOS10 自定义UI
    [iOS] 通知详解: iOS 10 UserNotifications – 自定义通知UI

    新建 Notification content extension

    通知UI的自定义使用到了Notification content extension,同创建Notification Service Extension一样,我们需要创建一个新的 Target ,只不过这次选择Notification content extension

    下一步,为这个Target起一个名字,完成即可!

    可以看到多了下面几个文件:

    这里的NotificationViewController就是我们编写自定义UI逻辑的控制器,他和一般的控制器一样,MainInterface.storyboard是与其绑定的,可以在此往storyboard添加控件。Info.plist为其相关的配置文件,有些操作需要在这里配置一些设置后,才能看到预期的效果,下面关于此部分的所有配置,都是在这里进行的。

    NotificationViewController中,实现了UNNotificationContentExtension协议,他有两个协议方法

    // 必须实现,用来处理自定义UI的内容
    public func didReceive(_ notification: UNNotification)
    // 选择实现,用来处理action的事件
    optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)
    
    

    第一个是必须要实现的,在NotificationViewController默认已经实现了,主要是处理当通知来的时候,布局自定义的UI内容以及相关的处理逻辑的地方;
    第二个方法,当前发送的通知带有快捷操作action的时候(UNNotificationAction),来处理相关的点击事件。

    因为我们自定义的任何View都是无法交互的,只能借助添加的action来处理相关的事件。

    绑定 Category

    Notification content extension添加完成后,在通知界面是看不到我们自定义的UI的,还需要绑定相关的 Category,即在创建通知的时候,我们添加的UNNotificationCategory,如果没有需要交互的action,可以传个空数组:

    let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
            UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
    
    

    然后在该扩展下的Info.plist中添加该Categoryidentifier,对应着UNNotificationExtensionCategory字段:

    注意:这里的UNNotificationExtensionCategory可以修改为数组类型,如果我们有多个Category公用一套UI,可以将此值修改为Array类型,然后在数组里添加多个 Category 的identifier。

    再去发送通知,注意此时的Payload中要添加category字段:

    {
    "aps":
        {
            "alert":
            {
                "title":"iOS10远程推送标题",
                "subtitle" : "iOS10 远程推送副标题",
                "body":"这是在iOS10以上版本的推送内容,并且携带来一个图片附件"
            },
    
    "category":"categoryidentifier",
            "badge":1,
            "mutable-content":1,
            "sound":"default",
    "image":"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3078873712,1340878922&fm=26&gp=0.jpg"
            
        }
    }
    
    

    弹框和锁屏页显示的内容和之前一样,打开通知或者下拉弹框,就会看到我们自定义的页面了:

    比较丑的那部分就是我们自定义的UI了,可以看到真的很丑,大小还不合适,而且和系统默认的也重复的。

    如果我们想要隐藏系统默认的内容页面,也就是下面的那部分,头是隐藏不了的;只需要在Info.plist里添加字段UNNotificationExtensionDefaultContentHidden,bool类型并设置其值为YES;

    关于页面太大的问题,有的说通过修改其宽高比UNNotificationExtensionInitialContentSizeRatio的值,如果你的UI是固定的,可以通过适配大部分屏幕后,通过修改此值来得到合适的宽高比视图,但其值也是需要各种尝试的。
    另外也可以使用autolayout,如果是在storyboard里添加的实图,顺便添加相应的约束即可;然后重新发送消息,大概就是这个样子:

    这样,通知页面会先显示一个大的页面,然后再resize到约束后的页面大小,这样就会一个缩放的动画,这是因为在通知即将展示的时候,系统还没有调用我们的约束代码,也就是约束还没有起效,所以会有个resize的动画过渡。
    为解决这个问题,只能在自定义UI的时候配合UNNotificationExtensionInitialContentSizeRatio设置合适页面大小,即采用固定的样式来展示通知内容。

    显示附加包(attachment)的内容

    如果我们的通知是携带附加包的,例如一张图片,添加自定义的UI后,打开通知或者下拉弹框会发现,大图不显示了,我们可以把相关的内容显示到自定义的UI上,还是以图片为例,在didReceive方法里添加以下获取附加包数据的代码:

    if let att = notification.request.content.attachments.first {
    
                if att.url.startAccessingSecurityScopedResource() {
                    self.coverImage.image  = UIImage(contentsOfFile: att.url.path)
                    att.url.stopAccessingSecurityScopedResource()
                }
            }
    
    

    这里需要说一下startAccessingSecurityScopedResourcestopAccessingSecurityScopedResource方法:
    因为attachment是由系统单独管理的,所以这里我们在使用attachment之前,需要告诉iOS系统,我们需要使用它,并且在使用完毕之后告诉系统我们使用完毕了。对应上述代码就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操作。当我们获取到了attachment的使用权之后,我们就可以使用那个文件获取我们想要的信息了。

    再去发送上面的Payload,打开后就是这样了:

    意思是那么个意思,但是加载的图片好像不太完整,上面我们是从attachment里面获取的,目前不清楚出现这个情况的原因,可能原数据被压缩了导致数据不全。所以,我们可以从发送的Payload中来获取数据:

    if let aps = notification.request.content.userInfo["aps"] as? [String: Any] {
    
                if let imagePath = aps["image"] as? String {
    
                    if let url = URL(string: imagePath) {
    
                        if let data = try? Data.init(contentsOf: url) {
    
                            self.coverImage.image = UIImage(data: data)
                        }
                    }
                }
            }
    

    这样就能正常显示了:

    处理action事件

    如果我们添加的category是带有action的,并且action的点击事件要响应到我们自定义的UI里面,例如点击的时候更换一个图片, 就需要UNNotificationContentExtension协议的另一个协议方法了:

    // response:可以拿到点击的action,和通知的内容
    // completion:处理完成后需要告诉系统,接下来该如何处理该通知
    optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)
    
    

    UNNotificationContentExtensionResponseOption 是一个枚举,他有三个值:

    @available(iOS 10.0, *)
    public enum UNNotificationContentExtensionResponseOption : UInt {
    
        // 通知页面不会消失,例如更新UI,显示出来
        case doNotDismiss
    // 关闭当前通知页面
        case dismiss
    // 将此action事件传递给app,在通知中心的代理方法里继续处理该事件
        case dismissAndForwardAction
    }
    
    

    需要注意的是,如果实现了此方法,就需要对所有添加的action进行处理,而不能只处理某个action

    例如我们这样处理点击事件:

    func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
    // 改变标题
            self.label?.text = self.label?.text ?? "" + "点击了 "
            
            if response.actionIdentifier == "okidentifier" {
                // 点击了查看按钮,这里改变了标题的颜色
    
                self.label?.textColor = UIColor.red
                
                completion(.doNotDismiss)
            } else if response.actionIdentifier == "cancelidentifier" {
                // 点击了关闭,直接关闭通知
                completion(.dismiss)
            } else {
                // 如果还有其他的按钮,交给app处理
                completion(.dismissAndForwardAction)
            }
        }
    

    然后,在创建通知的时候,添加相应的action:

    let okAction = UNNotificationAction(identifier: "okidentifier", title: "查看", options: UNNotificationActionOptions.foreground)
       
            let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
            
            let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
            UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
    

    再次发生Payload,点击通知的查看action,会发现标题和标题的颜色都修改了。

    处理快捷回复(输入文字)

    前面知道,我们可以在通知中心进行快捷回复,只需要创建UNTextInputNotificationAction的action,添加到对应的category即可:

    let okAction = UNTextInputNotificationAction(identifier: "okidentifier", title: "回复", options: .foreground, textInputButtonTitle: "快捷回复", textInputPlaceholder: "请输入。。。")
            
            
            let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
            
            let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
            UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
    
    

    这里,我们需要这样来处理接收到的反馈:

    func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
            self.label?.text = "点击了 "
            
            if response.actionIdentifier == "okidentifier" {
                
    
                // 这里处理输入框的事件
                if let txRes = response as? UNTextInputNotificationResponse {
                    // 如果是输入框的反馈,获取输入内容,显示出来
                    let text = txRes.userText
                    self.label?.text = text
                }
                
                // 点击了查看按钮,这里改变了标题的颜色
                self.label?.textColor = UIColor.red
                
                completion(.doNotDismiss)
            } else if response.actionIdentifier == "cancelidentifier" {
                // 点击了关闭,直接关闭通知
                completion(.dismiss)
            } else {
                // 如果还有其他的按钮,交给app处理
                completion(.dismissAndForwardAction)
            }
        }
    

    然后在通知中心点击回复按钮的时候会弹出输入框,输入结束后,通知中心即显示了输入的内容:

    到此,断断续续,总算是把通知相关的内容整体过了一遍,虽然感觉上还是有些逻辑混乱,基本上能够体现通知的一些使用方法。

    参考文章
    iOS10-UserNotifications
    WWDC2016 Session笔记 - iOS 10 推送Notification新特性

    展开全文
  • 通知:NSNotification,是iOS开发中一种重要的设计模式,它的实质是程序内部提供的一种广播机制。把接受到的消息根据内部消息转发表,将消息转发给需要的对象。 通知这种设计模式,开发中常用来不同类之间的通信...

    通知:NSNotification,是iOS开发中一种重要的设计模式,它的实质是程序内部提供的一种广播机制。把接受到的消息根据内部消息转发表,将消息转发给需要的对象。

    通知这种设计模式,在开发中常用来不同类之间的通信,也就是常说的页面之间的传值。当然它不仅仅只有这一种应用场景,还有一种常用场景是用来控制一些属性或者控件,使得这些属性或控件在不同情况下发生响应的变化。在我以前的一个项目中就有这样的场景出现:

    项目需求:每次打开app和每次需要下载时监控网络状态变化,当时WiFi状态是打开自动下载开关,非WiFi状态时关闭自动下载开关。

    对于这样的需求,就可以使用通知去实现,而且很简单,只需要在需要发送通知的地方,注册、发送通知,在开关存在的地方接受通知消息,根据通知去控制开关是开启还是关闭。

    那么,下面介绍如何使用通知。

    首先先介绍一下几个类:

    1、NSNotification

    这个类是通知类,由这个类创建的对象是一个通知对象,也可以理解为是一个消息对象。类中有三个成员变量:

    name:是消息对象的唯一标识,接受通知消息时用来辨别

     

    @property (readonly,copy)NSNotificationName name;

    object:一个对象,可以理解为针对某个对象的消息

    @property (nullable,readonly,retain)id object;

    userInfo:一个字典,用来传值

    @property (nullable,readonly,copy)NSDictionary *userInfo;

     

    NSNotification初始化:

    对象方法初始化一个通知对象,并给通知对象的属性赋值

     

    - (instancetype)initWithName:(NSNotificationName)name object:(nullableid)object userInfo:(nullableNSDictionary *)userInfoNS_AVAILABLE(10_6, 4_0) NS_DESIGNATED_INITIALIZER;

    类方法创建一个通知对象,这个方法没有userInfo这个属性的初始赋值,所以用来发送无传值的通知

    + (instancetype)notificationWithName:(NSNotificationName)aName object:(nullableid)anObject;

    类方法初始化一个通知对象,并给通知对象的属性赋值

    + (instancetype)notificationWithName:(NSNotificationName)aName object:(nullableid)anObject userInfo:(nullableNSDictionary *)aUserInfo;

     

    注意:NSNotification不可以使用init进行初始化。

     

    2、NSNotificationCenter

    这个类是通知中心类,内部实现是单利模式,每个程序都有一个默认的通知中心,用来调度通知的发送和接受。

    通知中心类相关方法:

    a、接收通知的方法:

    添加观察者,可以指定一个方法、名称和对象,接受到通知时执行这个指定的方法。这里的name就是通知类的name,只有对应才能接受到通知。

     

    - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullableNSNotificationName)aName object:(nullableid)anObject;

     

    b、发送通知的方法:

     

    发送通知,参数是一个通知对象

     

    - (void)postNotification:(NSNotification *)notification;

    发送通知,参数是通知的名称,指定的对象

    - (void)postNotificationName:(NSNotificationName)aName object:(nullableid)anObject;

    发送通知,参数是通知的名称,指定的对象和传递的参数

    - (void)postNotificationName:(NSNotificationName)aName object:(nullableid)anObject userInfo:(nullableNSDictionary *)aUserInfo;

    上面这三个方法虽然写法不同,但是功能一样,使用哪一个方法取决于NSNotification类如何创建对象。后两种方法其实就是初始化通知并发送通知,将通知对象的初始化和发送方法结合。

     

    c、移除通知的方法:

    移除该检测对象(observer)下的所有通知

     

    - (void)removeObserver:(id)observer;

    根据通知名称(aName),移除该检测对象(observer)下的一个通知

    - (void)removeObserver:(id)observer name:(nullableNSNotificationName)aName object:(nullableid)anObject;

     

     

     

    一个通知的初始化,发送,接收。就是对上面这些方法的使用。

    下面,示例说明:

     

     

    #import "ViewController.h"

     

    @interface ViewController ()

    {

        UILabel *label;

        UIButton *button;

    }

    @end

     

    @implementation ViewController

     

    - (void)viewDidLoad {

        [superviewDidLoad];

        

        //创建视图方法

        [selfsetUI];

        //创建通知中心对象

        NSNotificationCenter *center = [NSNotificationCenterdefaultCenter];

        //注册、接收通知

        [center addObserver:selfselector:@selector(chanegeLabelText:)name:@"notification"object:nil];

    }

     

    - (void)setUI{

        label = [[UILabelalloc]initWithFrame:CGRectMake(100, 200, 200, 30)];

        label.backgroundColor = [UIColoryellowColor];

        label.text =@"没有接收到通知";

        [self.viewaddSubview:label];

        

        button = [UIButtonbuttonWithType:UIButtonTypeCustom];

        button.frame =CGRectMake(100, 300, 200, 30);

        [buttonsetTitle:@"发送通知"forState:UIControlStateNormal];

        [buttonsetBackgroundColor:[UIColorblueColor]];

        [buttonaddTarget:selfaction:@selector(post)forControlEvents:UIControlEventTouchUpInside];

        [self.viewaddSubview:button];

    }

     

    //按钮点击方法

    - (void)post{

        //初始化一个通知对象,名称是 notification  没有指定对象  穿的值是一个字典@{@"key":@"an object"}

        NSNotification *notification = [NSNotificationnotificationWithName:@"notification"object:niluserInfo:@{@"key":@"接收到了通知"}];

        

        [[NSNotificationCenterdefaultCenter]postNotification:notification];

    }

     

    //接收通知后调用的方法

    - (void)chanegeLabelText:(NSNotification *)noti{

     

        //这个方法的参数就是发送通知postNotification:方法的参数发送过来额通知。当要使用传递的userInfo的时候,就要使用noti解析出userInfo中需要的字段

        label.text = [noti.userInfoobjectForKey:@"key"];

    }

     

     

    - (void)viewDidDisappear:(BOOL)animated{

       [super viewDidDisappear:animated];

        //在页面消失的回调方法中移除通知。

        [[NSNotificationCenterdefaultCenter]removeObserver:selfname:@"notification"object:nil];

    }

     

    @end

    点击按钮前:                      点击按钮后:

          

    在上面的显示中可以看到代码没有问题。

    上面的示例只是为了介绍通知的使用而写的,在实际开发中并不会有这样实现功能的做法。通知的使用是为了实现不同控制器,或者不同类之间的通信从而实现一些解耦。在同一个类中一般是没有信息传递的。

    而且,要牢记一点,通知既然是程序内部的一种广播机制,那么它的存在就是通信。

    关于通知的使用就是,注册、接收通知(addObserver)--->发送通知(postNotification)--->移除通知(removeObserver)。

    这里关于移除通知和前面的KVO那篇文章中移除KVO一样,要在合理的位置移除。

     

    展开全文
  • 两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。本地通知1.创建UILocalNotification。2.设置处理通知的时间fireDate。3.配置通知...

    iOS开发中的两种消息通知机制详解

    iOS中通知机制又叫消息机制,其包括两类:一类是本地通知;另一类是推送通知,也叫远程通知。两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。

    本地通知

    1.创建UILocalNotification。

    2.设置处理通知的时间fireDate。

    3.配置通知的内容:通知主体、通知声音、图标数字等。

    4.配置通知传递的自定义数据参数userInfo(这一步可选)。

    5.调用通知,可以使用scheduleLocalNotification:按计划调度一个通知,也可以使用presentLocalNotificationNow立即调用通知。

    下面就以一个程序更新后用户长期没有使用的提醒为例对本地通知做一个简单的了解。在这个过程中并没有牵扯太多的界面操作,所有的逻辑都在AppDelegate中:进入应用后如果没有注册通知,需要首先注册通知请求用户允许通知;一旦调用完注册方法,无论用户是否选择允许通知此刻都会调用应用程序的- (void)application:(UIApplication )application didRegisterUserNotificationSettings:(UIUserNotificationSettings )notificationSettings代理方法,在这个方法中根据用户的选择:如果是允许通知则会按照前面的步骤创建通知并在一定时间后执行。

    #import "AppDelegate.h"
    #import "KCMainViewController.h"
    @interface AppDelegate ()
    @end
    @implementation AppDelegate
    #pragma mark - 应用代理方法
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
        _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];
    
        //设置全局导航条风格和颜色
        [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
        [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
        KCMainViewController *mainController=[[KCMainViewController alloc]init];
        _window.rootViewController=mainController;
    
        [_window makeKeyAndVisible];
        //如果已经获得发送通知的授权则创建本地通知,否则请求授权(注意:如果不请求授权在设置中是没有对应的通知设置项的,也就是说如果从来没有发送过请求,即使通过设置也打不开消息允许设置)
        if ([[UIApplication sharedApplication]currentUserNotificationSettings].types!=UIUserNotificationTypeNone) {
            [self addLocalNotification];
        }else{
            [[UIApplication sharedApplication]registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound  categories:nil]];
        }
    
        return YES;
    }
    #pragma mark 调用过用户注册通知方法之后执行(也就是调用完registerUserNotificationSettings:方法之后执行)
    -(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
        if (notificationSettings.types!=UIUserNotificationTypeNone) {
            [self addLocalNotification];
        }
    }
    #pragma mark 进入前台后设置消息信息
    -(void)applicationWillEnterForeground:(UIApplication *)application{
        [[UIApplication sharedApplication]setApplicationIconBadgeNumber:0];//进入前台取消应用消息图标
    }
    #pragma mark - 私有方法
    #pragma mark 添加本地通知
    -(void)addLocalNotification{
    
        //定义本地通知对象
        UILocalNotification *notification=[[UILocalNotification alloc]init];
        //设置调用时间
        notification.fireDate=[NSDate dateWithTimeIntervalSinceNow:10.0];//通知触发的时间,10s以后
        notification.repeatInterval=2;//通知重复次数
        //notification.repeatCalendar=[NSCalendar currentCalendar];//当前日历,使用前最好设置时区等信息以便能够自动同步时间
    
        //设置通知属性
        notification.alertBody=@"最近添加了诸多有趣的特性,是否立即体验?"; //通知主体
        notification.applicationIconBadgeNumber=1;//应用程序图标右上角显示的消息数
        notification.alertAction=@"打开应用"; //待机界面的滑动动作提示
        notification.alertLaunchImage=@"Default";//通过点击通知打开应用时的启动图片,这里使用程序启动图片
        //notification.soundName=UILocalNotificationDefaultSoundName;//收到通知时播放的声音,默认消息声音
        notification.soundName=@"msg.caf";//通知声音(需要真机才能听到声音)
    
        //设置用户信息
        notification.userInfo=@{@"id":@1,@"user":@"Kenshin Cui"};//绑定到通知上的其他附加信息
    
        //调用通知
        [[UIApplication sharedApplication] scheduleLocalNotification:notification];
    }
    #pragma mark 移除本地通知,在不需要此通知时记得移除
    -(void)removeNotification{
        [[UIApplication sharedApplication] cancelAllLocalNotifications];
    }
    @end

    这里写图片描述

    1.请求获得用户允许通知的效果。

    2.应用退出到后弹出通知的效果。

    3.锁屏状态下的通知效果(从这个界面可以看到alertAction配置为“打开应用”)。

    注意:

    • 在使用通知之前必须注册通知类型,如果用户不允许应用程序发送通知,则以后就无法发送通知,除非用户手动到iOS设置中打开通知。

    • 本地通知是有操作系统统一调度的,只有在应用退出到后台或者关闭才能收到通知。(注意:这一点对于后面的推送通知也是完全适用的。 )

    • 通知的声音是由iOS系统播放的,格式必须是Linear PCM、MA4(IMA/ADPCM)、µLaw、aLaw中的一种,并且播放时间必须在30s内,否则将被系统声音替换,同时自定义声音文件必须放到main boundle中。

    • 本地通知的数量是有限制的,最近的本地通知最多只能有64个,超过这个数量将被系统忽略。

    • 如果想要移除本地通知可以调用UIApplication的cancelLocalNotification:或cancelAllLocalNotifications移除指定通知或所有通知。

    从上面的程序可以看到userInfo这个属性我们设置了参数,那么这个参数如何接收呢?

    在iOS中如果点击一个弹出通知(或者锁屏界面滑动查看通知),默认会自动打开当前应用。由于通知由系统调度那么此时进入应用有两种情况:如果应用程序已经完全退出那么此时会调用- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法;如果此时应用程序还在运行(无论是在前台还是在后台)则会调用-(void)application:(UIApplication )application didReceiveLocalNotification:(UILocalNotification )notification方法接收消息参数。当然如果是后者自然不必多说,因为参数中已经可以拿到notification对象,只要读取userInfo属性即可。如果是前者的话则可以访问launchOptions中键为UIApplicationLaunchOptionsLocalNotificationKey的对象,这个对象就是发送的通知,由此对象再去访问userInfo。为了演示这个过程在下面的程序中将userInfo的内容写入文件以便模拟关闭程序后再通过点击通知打开应用获取userInfo的过程。

    #import "AppDelegate.h"
    #import "KCMainViewController.h"
    @interface AppDelegate ()
    @end
    @implementation AppDelegate
    #pragma mark - 应用代理方法
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
        _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];
    
        //设置全局导航条风格和颜色
        [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
        [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
        KCMainViewController *mainController=[[KCMainViewController alloc]init];
        _window.rootViewController=mainController;
    
        [_window makeKeyAndVisible];
        //添加通知
        [self addLocalNotification];
        //接收通知参数
        UILocalNotification *notification=[launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
        NSDictionary *userInfo= notification.userInfo;
    
        [userInfo writeToFile:@"/Users/kenshincui/Desktop/didFinishLaunchingWithOptions.txt" atomically:YES];
        NSLog(@"didFinishLaunchingWithOptions:The userInfo is %@.",userInfo);
    
        return YES;
    }
    #pragma mark 接收本地通知时触发
    -(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
        NSDictionary *userInfo=notification.userInfo;
        [userInfo writeToFile:@"/Users/kenshincui/Desktop/didReceiveLocalNotification.txt" atomically:YES];
        NSLog(@"didReceiveLocalNotification:The userInfo is %@",userInfo);
    }
    #pragma mark 调用过用户注册通知方法之后执行(也就是调用完registerUserNotificationSettings:方法之后执行)
    -(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
        if (notificationSettings.types!=UIUserNotificationTypeNone) {
            [self addLocalNotification];
        }
    }
    #pragma mark 进入前台后设置消息信息
    -(void)applicationWillEnterForeground:(UIApplication *)application{
        [[UIApplication sharedApplication]setApplicationIconBadgeNumber:0];//进入前台取消应用消息图标
    }
    #pragma mark - 私有方法
    #pragma mark 添加本地通知
    -(void)addLocalNotification{
    
        //定义本地通知对象
        UILocalNotification *notification=[[UILocalNotification alloc]init];
        //设置调用时间
        notification.fireDate=[NSDate dateWithTimeIntervalSinceNow:10.0];//通知触发的时间,10s以后
        notification.repeatInterval=2;//通知重复次数
        //notification.repeatCalendar=[NSCalendar currentCalendar];//当前日历,使用前最好设置时区等信息以便能够自动同步时间
    
        //设置通知属性
        notification.alertBody=@"最近添加了诸多有趣的特性,是否立即体验?"; //通知主体
        notification.applicationIconBadgeNumber=1;//应用程序图标右上角显示的消息数
        notification.alertAction=@"打开应用"; //待机界面的滑动动作提示
        notification.alertLaunchImage=@"Default";//通过点击通知打开应用时的启动图片
        //notification.soundName=UILocalNotificationDefaultSoundName;//收到通知时播放的声音,默认消息声音
        notification.soundName=@"msg.caf";//通知声音(需要真机)
    
        //设置用户信息
        notification.userInfo=@{@"id":@1,@"user":@"Kenshin Cui"};//绑定到通知上的其他额外信息
    
        //调用通知
        [[UIApplication sharedApplication] scheduleLocalNotification:notification];
    }
    @end

    上面的程序可以分为两种情况去运行:一种是启动程序关闭程序,等到接收到通知之后点击通知重新进入程序;另一种是启动程序后,进入后台(其实在前台也可以,但是为了明显的体验这个过程建议进入后台),接收到通知后点击通知进入应用。另种情况会分别按照前面说的情况调用不同的方法接收到userInfo写入本地文件系统。有了userInfo一般来说就可以根据这个信息进行一些处理,例如可以根据不同的参数信息导航到不同的界面,假设是更新的通知则可以导航到更新内容界面等。

    推送通知

    和本地通知不同,推送通知是由应用服务提供商发起的,通过苹果的APNs(Apple Push Notification Server)发送到应用客户端。下面是苹果官方关于推送通知的过程示意图:
    这里写图片描述

    推送通知的过程可以分为以下几步:

    1.应用服务提供商从服务器端把要发送的消息和设备令牌(device token)发送给苹果的消息推送服务器APNs。

    2.APNs根据设备令牌在已注册的设备(iPhone、iPad、iTouch、mac等)查找对应的设备,将消息发送给相应的设备。

    3.客户端设备接将接收到的消息传递给相应的应用程序,应用程序根据用户设置弹出通知消息。

    当然,这只是一个简单的流程,有了这个流程我们还无从下手编写程序,将上面的流程细化可以得到如下流程图(图片来自互联网),在这个过程中会也会提到如何在程序中完成这些步骤:
    这里写图片描述

    1.应用程序注册APNs推送消息。

    说明:

    a.只有注册过的应用才有可能接收到消息,程序中通常通过UIApplication的registerUserNotificationSettings:方法注册,iOS8中通知注册的方法发生了改变,如果是iOS7及之前版本的iOS请参考其他代码。

    b.注册之前有两个前提条件必须准备好:开发配置文件(provisioning profile,也就是.mobileprovision后缀的文件)的App ID不能使用通配ID必须使用指定APP ID并且生成配置文件中选择Push Notifications服务,一般的开发配置文件无法完成注册;应用程序的Bundle Identifier必须和生成配置文件使用的APP ID完全一致。

    2.iOS从APNs接收device token,在应用程序获取device token。

    说明:

    a.在UIApplication的-(void)application:(UIApplication )application didRegisterForRemoteNotificationsWithDeviceToken:(NSData )deviceToken代理方法中获取令牌,此方法发生在注册之后。

    b.如果无法正确获得device token可以在UIApplication的-(void)application:(UIApplication )application didFailToRegisterForRemoteNotificationsWithError:(NSError )error代理方法中查看详细错误信息,此方法发生在获取device token失败之后。

    c.必须真机调试,模拟器无法获取device token。

    3.iOS应用将device token发送给应用程序提供商,告诉服务器端当前设备允许接收消息。

    说明:

    a.device token的生成算法只有Apple掌握,为了确保算法发生变化后仍然能够正常接收服务器端发送的通知,每次应用程序启动都重新获得device token(注意:device token的获取不会造成性能问题,苹果官方已经做过优化)。

    b.通常可以创建一个网络连接发送给应用程序提供商的服务器端, 在这个过程中最好将上一次获得的device token存储起来,避免重复发送,一旦发现device token发生了变化最好将原有的device token一块发送给服务器端,服务器端删除原有令牌存储新令牌避免服务器端发送无效消息。

    4.应用程序提供商在服务器端根据前面发送过来的device token组织信息发送给APNs。

    说明:

    a.发送时指定device token和消息内容,并且完全按照苹果官方的消息格式组织消息内容,通常情况下可以借助其他第三方消息推送框架来完成。

    5.APNs根据消息中的device token查找已注册的设备推送消息。

    说明:

    a.正常情况下可以根据device token将消息成功推送到客户端设备中,但是也不排除用户卸载程序的情况,此时推送消息失败,APNs会将这个错误消息通知服务器端以避免资源浪费(服务器端此时可以根据错误删除已经存储的device token,下次不再发送)。

    下面将简单演示一下推送通知的简单流程:

    首先,看一下iOS客户端代码:

    #import "AppDelegate.h"
    #import "KCMainViewController.h"
    @interface AppDelegate ()
    @end
    @implementation AppDelegate
    #pragma mark - 应用程序代理方法
    #pragma mark 应用程序启动之后
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
        _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
        _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];
    
        //设置全局导航条风格和颜色
        [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
        [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
        KCMainViewController *mainController=[[KCMainViewController alloc]init];
        _window.rootViewController=mainController;
    
        [_window makeKeyAndVisible];
    
        //注册推送通知(注意iOS8注册方法发生了变化)
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
        [application registerForRemoteNotifications];
    
        return YES;
    }
    #pragma mark 注册推送通知之后
    //在此接收设备令牌
    -(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
        [self addDeviceToken:deviceToken];
        NSLog(@"device token:%@",deviceToken);
    }
    #pragma mark 获取device token失败后
    -(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
        NSLog(@"didFailToRegisterForRemoteNotificationsWithError:%@",error.localizedDescription);
        [self addDeviceToken:nil];
    }
    #pragma mark 接收到推送通知之后
    -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
        NSLog(@"receiveRemoteNotification,userInfo is %@",userInfo);
    }
    #pragma mark - 私有方法
    /**
     *  添加设备令牌到服务器端
     *
     *  @param deviceToken 设备令牌
     */
    -(void)addDeviceToken:(NSData *)deviceToken{
        NSString *key=@"DeviceToken";
        NSData *oldToken= [[NSUserDefaults standardUserDefaults]objectForKey:key];
        //如果偏好设置中的已存储设备令牌和新获取的令牌不同则存储新令牌并且发送给服务器端
        if (![oldToken isEqualToData:deviceToken]) {
            [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:key];
            [self sendDeviceTokenWidthOldDeviceToken:oldToken newDeviceToken:deviceToken];
        }
    }
    -(void)sendDeviceTokenWidthOldDeviceToken:(NSData *)oldToken newDeviceToken:(NSData *)newToken{
        //注意一定确保真机可以正常访问下面的地址
        NSString *urlStr=@"http://192.168.1.101/RegisterDeviceToken.aspx";
        urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url=[NSURL URLWithString:urlStr];
        NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
        [requestM setHTTPMethod:@"POST"];
        NSString *bodyStr=[NSString stringWithFormat:@"oldToken=%@&newToken=%@",oldToken,newToken];
        NSData *body=[bodyStr dataUsingEncoding:NSUTF8StringEncoding];
        [requestM setHTTPBody:body];
        NSURLSession *session=[NSURLSession sharedSession];
        NSURLSessionDataTask *dataTask= [session dataTaskWithRequest:requestM completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            if (error) {
                NSLog(@"Send failure,error is :%@",error.localizedDescription);
            }else{
                NSLog(@"Send Success!");
            }
    
        }];
        [dataTask resume];
    }
    @end

    iOS客户端代码的代码比较简单,注册推送通知,获取device token存储到偏好设置中,并且如果新获取的device token不同于偏好设置中存储的数据则发送给服务器端,更新服务器端device token列表。

    其次,由于device token需要发送给服务器端,这里使用一个Web应用作为服务器端接收device token,这里使用了ASP.NET程序来处理令牌接收注册工作,当然你使用其他技术同样没有问题。下面是对应的后台代码:

    using System;
    using System.Collections.Generic;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using CMJ.Framework.Data;
    namespace WebServer
    {
        public partial class RegisterDeviceToken : System.Web.UI.Page
        {
            private string _appID = @"com.cmjstudio.pushnotification";
            private SqlHelper _helper = new SqlHelper();
            protected void Page_Load(object sender, EventArgs e)
            {
                try
                {
                    string oldToken = Request["oldToken"] + "";
                    string newToken = Request["newToken"] + "";
                    string sql = "";
                    //如果传递旧的设备令牌则删除旧令牌添加新令牌
                    if (oldToken != "")
                    {
                        sql = string.Format("DELETE FROM dbo.Device WHERE AppID='{0}' AND DeviceToken='{1}';", _appID, oldToken);
                    }
                    sql += string.Format(@"IF NOT EXISTS (SELECT ID FROM dbo.Device WHERE AppID='{0}' AND DeviceToken='{1}')
                                            INSERT INTO dbo.Device ( AppID, DeviceToken ) VALUES ( N'{0}', N'{1}');", _appID, newToken);
                    _helper.ExecuteNonQuery(sql);
                    Response.Write("注册成功!");
                }
                catch(Exception ex)
                {
                    Response.Write("注册失败,错误详情:"+ex.ToString());
                }
            }
        }
    }

    这个过程主要就是保存device token到数据库中,当然如果同时传递旧的设备令牌还需要先删除就的设备令牌,这里简单的在数据库中创建了一张Device表来保存设备令牌,其中记录了应用程序Id和设备令牌。
    第三步就是服务器端发送消息,如果要给APNs发送消息就必须按照Apple的标准消息格式组织消息内容。但是好在目前已经有很多开源的第三方类库供我们使用,具体消息如何包装完全不用自己组织,这里使用一个开源的类库Push Sharp来给APNs发送消息 ,除了可以给Apple设备推送消息,Push Sharp还支持Android、Windows Phone等多种设备,更多详细内容大家可以参照官方说明。前面说过如果要开发消息推送应用不能使用一般的开发配置文件,这里还需要注意:如果服务器端要给APNs发送消息其秘钥也必须是通过APNs Development iOS类型的证书来导出的,一般的iOS Development 类型的证书导出的秘钥无法用作服务器端发送秘钥。下面通过在一个简单的WinForm程序中调用Push Sharp给APNs发送消息,这里读取之前Device表中的所有设备令牌循环发送消息:

    using System;
    using System.IO;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using PushSharp;
    using PushSharp.Apple;
    using CMJ.Framework.Data;
    using CMJ.Framework.Logging;
    using CMJ.Framework.Windows.Forms;
    namespace PushNotificationServer
    {
        public partial class frmMain : PersonalizeForm
        {
            private string _appID = @"com.cmjstudio.pushnotification";
            private SqlHelper _helper = new SqlHelper();
            public frmMain()
            {
                InitializeComponent();
            }
            private void btnClose_Click(object sender, EventArgs e)
            {
                this.Close();
            }
            private void btnSend_Click(object sender, EventArgs e)
            {
                List<string> deviceTokens = GetDeviceToken();
                SendMessage(deviceTokens, tbMessage.Text);
            }
            #region 发送消息
            /// <summary>
            /// 取得所有设备令牌
            /// </summary>
            /// <returns>设备令牌</returns>
            private List<string> GetDeviceToken()
            {
                List<string> deviceTokens = new List<string>();
                string sql = string.Format("SELECT DeviceToken FROM dbo.Device WHERE AppID='{0}'",_appID);
                DataTable dt = _helper.GetDataTable(sql);
                if(dt.Rows.Count>0)
                {
                    foreach(DataRow dr in dt.Rows)
                    {
                        deviceTokens.Add((dr["DeviceToken"]+"").TrimStart('<').TrimEnd('>').Replace(" ",""));
                    }
                }
                return deviceTokens;
            }
    
            /// <summary>
            /// 发送消息
            /// </summary>
            /// <param name="deviceToken">设备令牌</param>
            /// <param name="message">消息内容</param>
            private void SendMessage(List<string> deviceToken, string message)
            {
                //创建推送对象
                var pusher = new PushBroker();
                pusher.OnNotificationSent += pusher_OnNotificationSent;//发送成功事件
                pusher.OnNotificationFailed += pusher_OnNotificationFailed;//发送失败事件
                pusher.OnChannelCreated += pusher_OnChannelCreated;
                pusher.OnChannelDestroyed += pusher_OnChannelDestroyed;
                pusher.OnChannelException += pusher_OnChannelException;
                pusher.OnDeviceSubscriptionChanged += pusher_OnDeviceSubscriptionChanged;
                pusher.OnDeviceSubscriptionExpired += pusher_OnDeviceSubscriptionExpired;
                pusher.OnNotificationRequeue += pusher_OnNotificationRequeue;
                pusher.OnServiceException += pusher_OnServiceException;
                //注册推送服务
                byte[] certificateData = File.ReadAllBytes(@"E:\KenshinCui_Push.p12");
                pusher.RegisterAppleService(new ApplePushChannelSettings(certificateData, "123"));
                foreach (string token in deviceToken)
                {
                    //给指定设备发送消息
                    pusher.QueueNotification(new AppleNotification()
                        .ForDeviceToken(token)
                        .WithAlert(message) 
                        .WithBadge(1)
                        .WithSound("default"));
                }
            }
            void pusher_OnServiceException(object sender, Exception error)
            {
                Console.WriteLine("消息发送失败,错误详情:" + error.ToString());
                PersonalizeMessageBox.Show(this, "消息发送失败,错误详情:" + error.ToString(), "系统提示");
            }
            void pusher_OnNotificationRequeue(object sender, PushSharp.Core.NotificationRequeueEventArgs e)
            {
                Console.WriteLine("pusher_OnNotificationRequeue");
            }
            void pusher_OnDeviceSubscriptionExpired(object sender, string expiredSubscriptionId, DateTime expirationDateUtc, PushSharp.Core.INotification notification)
            {
                Console.WriteLine("pusher_OnDeviceSubscriptionChanged");
            }
            void pusher_OnDeviceSubscriptionChanged(object sender, string oldSubscriptionId, string newSubscriptionId, PushSharp.Core.INotification notification)
            {
                Console.WriteLine("pusher_OnDeviceSubscriptionChanged");
            }
            void pusher_OnChannelException(object sender, PushSharp.Core.IPushChannel pushChannel, Exception error)
            {
                Console.WriteLine("消息发送失败,错误详情:" + error.ToString());
                PersonalizeMessageBox.Show(this, "消息发送失败,错误详情:" + error.ToString(), "系统提示");
            }
            void pusher_OnChannelDestroyed(object sender)
            {
                Console.WriteLine("pusher_OnChannelDestroyed");
            }
            void pusher_OnChannelCreated(object sender, PushSharp.Core.IPushChannel pushChannel)
            {
                Console.WriteLine("pusher_OnChannelCreated");
            }
            void pusher_OnNotificationFailed(object sender, PushSharp.Core.INotification notification, Exception error)
            {
                Console.WriteLine("消息发送失败,错误详情:" + error.ToString());
                PersonalizeMessageBox.Show(this, "消息发送失败,错误详情:"+error.ToString(), "系统提示");
            }
            void pusher_OnNotificationSent(object sender, PushSharp.Core.INotification notification)
            {
                Console.WriteLine("消息发送成功!");
                PersonalizeMessageBox.Show(this, "消息发送成功!", "系统提示");
            }
            #endregion
        }
    }
    

    服务器端消息发送应用运行效果:
    这里写图片描述

    iOS客户端接收的消息的效果:
    这里写图片描述

    到目前为止通过服务器端应用可以顺利发送消息给APNs并且iOS应用已经成功接收推送消息。

    补充–iOS开发证书、秘钥

    iOS开发过程中如果需要进行真机调试、发布需要注册申请很多证书,对于初学者往往迷惑不解,再加上今天的文章中会牵扯到一些特殊配置,这里就简单的对iOS开发的常用证书和秘钥等做一说明。

    证书

    iOS常用的证书包括开发证书和发布证书,无论是真机调试还是最终发布应用到App Store这两个证书都是必须的,它是iOS开发的基本证书。

    a.开发证书:开发证书又分为普通开发证书和推送证书,如果仅仅是一般的应用则前者即可满足,但是如果开发推送应用则必须使用推送证书。

    b.发布证书:发布证书又可以分为普通发布证书、推送证书、Pass Type ID证书、站点发布证书、VoIP服务证书、苹果支付证书。同样的,对于需要使用特殊服务的应用则必须选择对应的证书。

    应用标识

    App ID,应用程序的唯一标识,对应iOS应用的Bundle Identifier,App ID在苹果开发者中心中分为通配应用ID和明确的应用ID,前者一般用于普通应用开发,一个ID可以适用于多个不同标识的应用;但是对于使用消息推送、Passbook、站点发布、iCloud等服务的应用必须配置明确的应用ID。

    设备标识

    UDID,用于标识每一台硬件设备的标示符。注意它不是device token,device token是根据UDID使用一个只有Apple自己才知道的算法生成的一组标示符。

    配置简介

    Provisioning Profiles,平时又称为PP文件。将UDID、App ID、开发证书打包在一起的配置文件,同样分为开发和发布两类配置文件。

    秘钥

    在申请开发证书时必须要首先提交一个秘钥请求文件,对于生成秘钥请求文件的mac,如果要做开发则只需要下载证书和配置简介即可开发。但是如果要想在其他机器上做开发则必须将证书中的秘钥导出(导出之后是一个.p12文件),然后导入其他机器。同时对于类似于推送服务器端应用如果要给APNs发送消息,同样需要使用.p12秘钥文件,并且这个秘钥文件需要是推送证书导出的对应秘钥。

    展开全文
  • ios 本地和远程通知

    2018-05-08 17:52:14
    多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情。iOS通知机制又叫消息机制,其包括两类:一类是本地通知;另...
  • iOS10 本地通知

    2017-09-14 22:04:35
    本地通知是由iOS操作系统根据条件本机上触发的,例如闹钟就是基于时间触发提醒通知的。远程通知是第三方远程推送给用户的iOS设备的。这种通知常用于商家推销自家的产品。 下面主要讲述开发本地通知的过程: 请求...
  • 1. 第一个界面建立一个通知中心, 通过通知中心,注册一个监听事件 2. 第一个界面中,设置接收到通知的事件。 3. 第一个界面中的dealloc中, 将通知中心remove掉 4. 第三个界面中, 建立
  • 本文主要讲述了iOS的本地和远程通知的基本使用,以及某些不易注意的问题。 Note:文章有不少身旁同学提供了帮助,大量引用或转载本文请声明原文地址,多谢。 一:用户通知简介 用户通知是什么 iOS中存在三种常见的...
  • 对于很多初学者往往会把iOS中的本地通知、推送通知和iOS通知中心的概念弄混。其实二者之间并没有任何关系,事实上它们都不属于一个框架,前者属于UIKit框架,后者属于Foundation框架。 通知中心实际上是iOS程序...
  • m 概述 多数移动应用中任何时候都只能有一个应用程序处于活跃...两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。今天就和大
  • iOS 通知与消息机制

    2016-04-28 18:29:12
    概述 多数移动应用中任何时候都只能有一个...两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。今天就和大家一块去看一下如何i
  • 通知的管理和配置设置代理请求权限添加通知按钮自定义警报声音管理已发送通知三. 本地通知四. 远程通知 (使用APNs)原理准备工作流程1. 开启推送通知功能2. 生成APNs AuthKey3. 代码部分4. 模拟服务器发送通知5. ...
  • 如果一个类中想要执行另一个类中的方法可以使用通知 1.创建一个通知对象:使用notificationWithName:object: 或者 notificationWithName:object:userInfo:  NSNotification* notification = ...
  • 两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。 本地通知 1.创建UILocalNotification。 2.设置处理通知的时间fireDate...
  • 本地通知是由本地应用触发的,它是基于时间行为的一种通知形式,例如闹钟定时、待办事项提醒,又或者一个应用一段时候后不使用通常会提示用户使用此应用等都是本地通知。创建一个本地通知通常分为以下几个步骤: ...
  • 概述 多数移动应用中任何时候都只能有一个...两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。今天就和大家一块去看一下如何
  • iOS通知与消息机制

    2015-03-18 17:25:21
    概述 多数移动应用中任何时候都只能有一个...两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。今天就和大家一块去看一下如何
  • IOS:通知与消息机制

    2015-03-23 16:56:32
    概述 多数移动应用中任何时候都只能有一个...两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同。今天就和大家一块去看一下如何
1 2 3 4 5 ... 20
收藏数 10,349
精华内容 4,139
关键字:

ios 通知注册在发送前面