Apple推送通知服务教程

Apple推送通知服务教程 

生成APP ID和SSL证书

登录iOS Provisioning Portal页面

首先,我们将要新建一个App ID. 每一个推送APP都需要一个唯一的对应的App ID,推送的消息将被送达到这个ID对应的APP应用中(这里不能使用通配ID)。

iOS Provisioning Portal页面左侧选择 App IDs,然后点击 New App ID 的按钮。

 

在例子中,对应的表单项填的值如下:

· Description: PushChat

· Bundle Seed ID: Generate New (this is the default option)(这是默认值)

· Bundle Identifier: com.hollance.PushChat

我的例子APP中对应的Bundle ID值为 – com.yoursite.PushChat – 这里最好替换成你自己的。同样,你需要在 XCode中对应的工程的Bundle ID配置里指定为同一个值。

等一会,我们将生成一个SSL证书,让你的推送服务器可以创建一个安全连接至APNS。这个证书会链接到你的这个App ID,你的推送服务器只能推送通知到特定的APP,而不是其它APP。:

 

在制作App ID后,应该显示成像下面这样:

在“Apple Push Notification service”这一栏中,有两个橙灯,分别对应配置APP推送功能的“开发版”和“产品版”,这也就是说,这个App ID能接收推送消息,但是我们仍然需要设置好才可以。点击Configure 链接打开对应的配置页面。

勾选“Enable for Apple Push Notification service ”的复选框,接下来点击Development Push SSL Certificate右边的Configure 按钮,会弹出“Apple推送通知服务SSL证书助手”的页面。

第一件事让你生成一个证书签名的请求,这一步我们已经做过了,所以点击Continue。下一步是上传CSR文件。选择在前面步骤中我们已生成好的CSR文件,然后点击Generate按钮。

等待数秒钟后便会生成SSL证书。当证书生成完成后,点击Continue按钮。

现在点击Download 按钮下载这个生成好的文件名为“aps_developer_identity.cer”的证书。点击Done 关闭证书助手,回到配置App ID的界面。

现在可以看到,我们已经生成了一个可用的证书,现在推送通知功能已经为接下来的开发准备好了(开发版)。你可以在需要的时候重复下载这个证书文件。需要注意的是,这个开发证书只有3个月的有效期

当你准备好发布正式版APP时,需要在产品版的Configure里重复上述操作,步骤是一样的。

注:产品版的证书有效期是一年,但如果你为了在失效期之前想重新生成这个证书,请事先确认这个app本身没有过期。

虽然你可以通过双击下载下来的aps_developer_identity.cer文件,将这个证书加到你的钥匙串中,但你并不一定需要这么做。当然如果你这么做了,你会发现这个证书关联了那个私有密匙。

生成一个PEM文件

现在我们有3个相关文件

· CSR文件

· P12私有密钥文件,PushChatKey.p12

· SSL证书文件,aps_developer_identity.cer

把这三个文件存到一个安全的地方。你可以扔掉CSR但我的意见是保留它更容易。当你证书过期时,你可以用相同的CSR产生一个新的。如果你想产生一个新的CSR,你也要产生一个新的私钥。当你重复使用CSR时你继续使用存在的私钥,仅仅.cer文件将会改变。

我们必须转换证书和私钥成一个对我们更有用的格式。因为我们服务器的推送部分是用php写的,我们将要把证书和私钥整合到一个单独PEM格式的文件里。

PEM的内容并不是很重要(事实上,我也不知道),但是它让PHP使用我们的证书变得非常方便。如果你想用其他语言写你的推送服务,以下的步骤对你来说也许会没有作用

我们将要使用命令行OpenSSL工具来做这些,打开命令行然后执行以下的步骤。

前往你下载好文件的文件夹,我是放在Desktop上

$ cd /Users/matthijs/Desktop

把.cer文件转换成.pem文件:

$ openssl x509 -in aps_developer_identity.cer -inform der

-out PushChatCert.pem

把私钥.p12文件转换成.pem文件:

$ openssl pkcs12 -nocerts -out PushChatKey.pem -in PushChatKey.p12

Enter Import Password:

MAC verified OK

Enter PEM pass phrase:

Verifying – Enter PEM pass phrase:

你首先需要为.p12文件输入passphrase密码短语,这样OpenSSL可以读它。然后你需要键入一个新的密码短语来加密PEM文件。还是使用”pushchat”来作为PEM的密码短语。你需要选择一些更安全的密码短语。

注意:如果你没有键入一个PEM passphrase,OpenSSL将不会返回一个错误信息,但是产生的.pem文件里面将不会含有私钥。

最后。把私钥和证书整合到一个.pem文件里:

$ cat PushChatCert.pem PushChatKey.pem > ck.pem

为了测试证书是否工作,执行下面的命令

$ telnet gateway.sandbox.push.apple.com 2195

Trying 17.172.232.226…

Connected to gateway.sandbox.push-apple.com.akadns.net.

Escape character is ‘^]’.

它将尝试发送一个规则的,不加密的连接到APNS服务。如果你看到上面的反馈,那说明你的MAC能够到达APNS。按下Ctrl+C 关闭连接。如果得到一个错误信息,那么你需要确保你的防火墙允许2195端口。

然后再次连接,这次用我们的SSL证书和私钥来设置一个安全的连接:

$ openssl s_client -connect gateway.sandbox.push.apple.com:2195

-cert PushChatCert.pem -key PushChatKey.pem

Enter pass phrase for PushChatKey.pem:

你会看到一个完整的输出,让你明白OpenSSL在后台做什么。如果连接是成功的,你可以键入一些字符。当你按下回车后,服务就会断开连接。如果在建立连接时有问题,OpenSSL将会给你一个错误消息,但是你不得不向上翻输出LOG,来找到它。

注意有2个不同的APNS服务器:沙盒服务器是用来做测试,而live服务器是为产品模式使用的。由于以上原因,我们用沙盒服务器,因为我们的证书是用来做开发的,不是产品使用的。

制作Provisioning Profile

在Provisioning Porta还没有完成,点击侧边栏Provisioning,点击New Profile。

在上面的区域里我是这么填写的:

Profile Name:PushChat Development

Certificates:勾选你的证书

App ID:PushChat

Devices:勾选你的设备

这和你以前做的任何provisioning profile都不同。我们需要做一个新的profile,因为每个推送app必须有它自己的profile来连接正确的App ID。

点击提交然后profile就会产生。新的profile将会是Pending状态。刷新Development Provisioning Profiles页面,状态就会变成Active然后你就可以下载文件了(名字是PushChat_Development.mobileprovision)。

通过双击它或者拖到Xcode的图标添加provisioning profile到Xcode里。如果发布你的app,你将不得不重复这个过程来产生一个AdHoc或者App Store distribution profile。

 

在教程的中,你已经能让你的iPhone应用接收推送消息,并且知道了怎样使用一个PHP的脚本来推送一个测试消息。在这个第二部分和最后一个部分中,你将会学到如何用APNS来做一个简单的应用,以及一个简单的PHP Web服务来增强你的应用功能。

注意:这篇教程相对比较长,你需要做好一个较长的时间准备去看完它。但它是很值的,一旦你看完它,你就拥有了一个更强大的应用和使用推送消息的Web服务。

 

介绍PushChat

在这个教程中,我们将要做一个叫做PushChat的简单的发消息应用,它采用推送消息来发信息。以下是它的样子:

用户看到的第一个屏幕是登陆界面。在这里用户输入他们的昵称和一个secret code。用户与他们的朋友们分享这个secret code。

每一个使用相同的secret code的人可以看到相互的消息。那么实际上,这个secret code相当于一个聊天室的名字。当然,如果你能猜到别人的code,那么你也能看到别人消息,所以它叫做secret code。

当用户按了Start!按钮,应用就给我们的服务器发送一条注册聊天室的消息。然后登陆界面就跳转到聊天界面:

Secret code 显示在navigation bar上。本地用户发送的消息显示在屏幕的右边,收到的消息则显示在左边。那么在这样例子中,这个用户和一个叫做SteveJ的人同时登陆到了这个叫做“TopSecretRoom123”的聊天室中。

第三个也是最后一个界面是输入界面。

 

这里没有什么特别的地方。它只是一个带有键盘的text view。消息大小被限制在190个字节,屏幕的上方显示了剩余字节数。

我添加这个限制的目的是因为一条推送消息的最大长度是256个字节,它包括了开头的JSON内容。

当用户按下保存按钮的时候,消息将会被发送到我们的服务器,然后推送到其他登陆相同聊天室的用户。

The Server API 服务器API

在以上阐述PushChat如何工作的过程中,我提到了几次“我们的服务器”。我们需要使用一台服务器是因为我们要使不同的设备相互协作。当只有你一个人在说的时候,这也算不上什么聊天。

我用PHP和MySQL写了一个简单的Web服务。这个iPhone应用将会给我们的服务器发送以下命令:

加入当用户在登陆界面登陆的时候,我们会给服务器发送他的昵称、它的secret code和它的device token。服务器在活跃用户列表中添加一条关于他的记录。这以后,同一个聊天室成员发送的任何消息将会被发送到这个新用户上。

LEAVE这与加入相反。用户在聊天界面中通过按下Exit按钮退出聊天。我们给服务器发送一条LEVEL的命令让它从活跃用户列表中删除这个用户。

MESSAGE.当一个用户在输入界面按下Sava按钮的时候,我们会给我们的服务器发送一条新的文本消息。然后服务器将为每一个聊天室成员将这些消息转换成推送消息,然后传给APNS。过一会儿,ANPS就会将这些消息推送给那些用户的设备。

UPDATE. 这是让服务器知道,这个用户拥有了新的device token。 Token也许会经常更新,如果发现更新了,就必须让服务器知道.关于这个之后会详细讨论

它的工作流程如图所示

 

要频繁的向服务器发送数据,要让服务器知道用户加入或离开一个聊天室或者她正在发送一条新的消息。服务器轮流创建推送消息然后发给APNS.苹果服务器负责将这些消息发送给app。当app收到一条新的推送消息,它会在message.app的Chat View上显示一条含有文字内容的聊天气泡

启用服务器

Setting up the server

如果你是WEB新手,你可以先看一下Ray’s tutorial on PHP and MySQL,我们将使用MAMP在mac上安装服务器和数据库,如果你的APP正在开发阶段,这是个不错的方案,MAMP容易安装并且你也不必为一个分离的服务器付钱。

唯一的要求是你的MAC和你的iPhone共享一个本地网络,否则APP将没法和SERVER交互,很多人家里有WIFI,所以这不是个大问题。

当然,一旦你的APP上线了,你要安装真正的和INTERNET连接的服务器。

你可以免费下载MAMP(也有付费版,但是免费版够用了)

PDO,PDO_MYSQL,MBSTRING,OPENSSL.

MAMP 包含APACHE服务器,PHP脚本,MySQL数据库,我们将使用这所有的三样,如果你想在安装了PHP设备上而不是MAMP上使用这个教程的服务器代码,你要确保你额外安装并启动了以下扩展:

安装MAMP很容易,将下载的文件解压,打开DMG文件,接受License许可,将MAMP文件夹拖拽到你的应用的目录,DONE!

启动MAMP,打开APPLICATION/MAMP点击MAMP图标(有大象的那个),打开了MAMP窗口。

点击OPENSTARTPAGE 按键,打开了默认浏览器,进入welcome界面。

Great!现在下载PUSHCHART SERVER代码,解压,确保在你的桌面上解压了这玩意儿,我之所以说这话,因为我们需要为APACHE web服务器 配置将这些文件的路径。

打开APPLICATIONS/MAMP/CONF/APACHE/HTTPD.CONF 添加如下代码行:

复制代码
Listen 44447
 
<VirtualHost *:44447>
    DocumentRoot "/Users/matthijs/Desktop/PushChatServer/api"
    ServerName 192.168.2.244:44447
    ServerAlias pushchat.local
    CustomLog "/Users/matthijs/Desktop/PushChatServer/log/apache_access.log" combined
    ErrorLog "/Users/matthijs/Desktop/PushChatServer/log/apache_error.log"
 
    SetEnv APPLICATION_ENV development
    php_flag magic_quotes_gpc off
 
    <Directory "/Users/matthijs/Desktop/PushChatServer/api">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>
复制代码

有些需要修改的,我的PUSH CHAT SERVER 的安装目录是“/Users/matthijs/Desktop”.把它改成

你解压的文件的位置。

在SERVERNAME 行里修改IP,改成你的MAC的IP,如果你不知道如何获取IP,打开系统偏好设置,进入网络面板.

我使用我的MACBOOK的WIFI的IP地址,不过 以太网的IP也可以. 注意你在SERVERNAME处将 端口号改为44447.

ServerName <your IP address>:44447

这里的WEB服务使用44447端口,这是 随便 设定的值, 网页服务 使用了80端口,默认的MAMP网页服务在8888端口,这里只是选择一个不会与其它服务冲突的端口.

“pushchat.local”作为服务器的别名,这就像一个域名,但它仅仅工作在本地网络上,我们要把名字改成IP地址,完成这个的捷径就是,编辑文件 “/ETC/HOSTS”在文件末尾添加如下代码行:

127.0.0.1 pushchat.local

在MAMP窗口,点击STOP SERVERS,等灯变红,点击START SERVERS,如果你 对HTTPD.CONF的修改没有错的话,两个服务器的灯都应该再次变绿。

打开默认浏览器,到 http://pushchat.local:44447.能看到信息:

If you can see this, it works!

太棒了!这意味着服务器API的APACHE和PHP代码成功安装,现在要配置服务器。

启动数据库

设置数据库

返回MAMP起始页(你可以通过点击MAMP窗口的OPEN THE START PAGE).点击顶部的PHPMYADMIN按键,屏幕如下:

在CREATE DATABASE 区域键入 pushchat,选择 “utf8_general_ci”核对,然后点击 CREATE创建数据库,可以看到数据创建成功的提示。

点击 屏幕顶端的Privileges按键,然后添加一个新用户。

如下填写:

· User name: pushchat

· Host: localhost

· Password: d]682\#%yI1nb3

· Privileges: Check “Grant all privileges on database “pushchat””

点击GO,添加用户,你可以修改密码,但你同时要记住在不同的PHP脚本中更改密码。

这是 数据库和用户,现在我们需要向数据库中添加表,点击屏幕顶部的SQL按键,将如下代码贴进TEXT BOX.

复制代码
USE pushchat;
 
SET NAMES utf8;
 
DROP TABLE IF EXISTS active_users;
 
CREATE TABLE active_users
(
    udid varchar(40) NOT NULL PRIMARY KEY,
    device_token varchar(64) NOT NULL,
    nickname varchar(255) NOT NULL,
    secret_code varchar(255) NOT NULL,
    ip_address varchar(32) NOT NULL
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

你也可以在 PushChatServer/database/api.sql中找到这些语句,

点击GO,执行脚本,创建一个新表——active_users

当服务器API收到JOIN命令,我们将为用户在表中添加一个新的记录,相反的,当LEAVE命令发送,将从表中移除用户的纪录。

这是另一个要添加的表,重复以上动作添加:

复制代码
USE pushchat;
 
SET NAMES utf8;
 
DROP TABLE IF EXISTS push_queue;
 
CREATE TABLE push_queue
(
    message_id integer NOT NULL AUTO_INCREMENT,
    device_token varchar(64) NOT NULL,
    payload varchar(256) NOT NULL,
    time_queued datetime NOT NULL,
    time_sent datetime,
    PRIMARY KEY (message_id)
)
ENGINE=InnoDB DEFAULT CHARSET=latin1;
复制代码

可以在PushChatServer/database/push.sql找到这些语句。

去论何时 服务器接收 到MESSAGE命令,他将 推送消息添加进这个表。

配置服务器API

 

 

在这里我就不大篇幅描叙服务器的API是如何工作的,但是我已经说了很多关于API.PHP,即使你不知道PHP你应该也能跟上。

与我们息息相关的是 api_config.php 文件,这个文件包含了服务器API的配置选项。

这里有两组 配置选项,一个是development模式,一个是production模式,做两组设置 你可以很容易的在这两者之间切换。

但是怎样告诉API他运行DEVELPMENT模式还是PRODUCTION模式?答案在 APACHE VITRUAL HOST 配置,之前我们添加了 一个<VirtualHost>模块在HTTP.CONF文件里:

复制代码
&lt;VirtualHost *:44447&gt;
    …
 
    SetEnv APPLICATION_ENV development
 
    ...
&lt;/VirtualHost&gt;
复制代码

“SetEnv APPLICATION_ENV development”指令创建了一个 环境变量,它决定了脚本在那种模式下运行。如果你想切换到PRODUCTION CONFIGURATION 模式,把这行去掉,或者将DEVELOPMENT 换成 PRODUCTION。

如果 你用了和我相同的名字做 数据库名字,在API.CONFIG.PHP里你就不用修改任何值了,但是 如果你用了其他的值,你要在这里 输入它们,否则 API就连不上数据库。

浏览 http://pushchat.local:44447/test/database.php,你得到如下消息:

Database connection successful!

建议:如果你得到了 不理解的错误。在PushChatServer/log/apache_error.log检查PHP错误纪录和在PushChatServer/log/apache_error.log.检查Apache 的错误记录,这些log文件可能告诉你 哪里错了。

到这里完成 SERVER API的安装。

让我们开始编程吧!

 

下载PushChatStarter代码并解压,这是没有网络和推送代码的PUSHCHAT应用,我会快速解释这个应用是怎么工作的,然后我会向它里面添加代码,让他能够和服务器对话,并能接受推送通知。

总之,这是你想了解的东西!

你可以在模拟器运行 PUSHCHAT,因为现在他没有任何推送功能,首先在XCODE中打开工程,到目标设置(Target Setting)。你必须将绑定ID(bundle ID——我这项目是“com.hollance.PushChat” )改换成你自己的bundle ID,这样你就可以在iOS Provisioning Portal(APPLE开发者中心)中选择各种你需要的消息方式……这很重要因为APP需要用配置文件(provisioning profile)签名。

 

编译运行,好好把玩一下,因为新的APP使用的BUNDLE ID 和最开始是的TEST APP相同,

所以在编译运行之前最好先 卸载那个 旧的APP,以确保你的IPHONE不会报错。

打开 MAINWINDOW.XIB,看一下 APP是如何构成的:

MAIN WINDOW包含一个 Navigation Controller, ChatViewController是它的根控制器,那个视图控制器展示收到的和发送的消息,你可以在NIB里看到,是一个普通的UITableView 控制器。

speech bubbles 由 MessageTableViewCell绘制,MESSAGETABLEVIEW是UITABLEVIEWCELL SpeechBubbleView是UIView的子类,完成普通绘制功能,这些是简单的TableView编程,你肯定早就知道了,我就不花时间解释了。

两个SCREEN, Login screen and 和 Compose screen,

都是在 CHAT VIEW CONTORLLER顶部展示的view controller模型,但是 Long screen在LoginViewController里实现,Compose Screen在ComposeViewController里实现,它们都有各自的NIB。这些是很简单的SCREEN,查看源码详细看一下。

还剩下两个数据模型类(data model class),DataModel和Message,message描叙了一条单独消息内容,它可能是由当前用户发送的,或者从另一个用户收到的,每一个消息有一个发送者的姓名(sender name),一个日期,还有消息实体。

DataModel 类负责这些消息对象,当一个新的消息被发送或者收到,DataModel将其加到表中,然后将表保存到 APP的文档目录的一个文件里、

当APP启动时,DataModel从文件中加载信息,这个是对应用的一个简单描叙,我建议在继续下一步之前看看代码,代码有大量注解解释每一部分如何工作的。

通过服务器聊天

我们讲解完了app的每一个view并开始向其中添加代码,使得它能和服务器通讯。我们的服务器需要app发送HTTP Post请求,所以我打算用ASIFormDataRequest来发送数据给服务器。所有的请求类已经被加入PushChat的初始代码中,所以这里不需要再次安装。

如果你第一次在iOS apps上使用web服务,我建议你先去看一下Ray’s previous tutorial 中有关这些的教程

添加如下代码到defs.h中

#define ServerApiURL @”http://192.168.2.244:44447/api.php”

我们将把HTTP POST请求发送到这个URL。你需要修改IP地址来指向你的服务器。同时要确认MAMP正在运行,也就是说服务器是可访问的,并且你的iPhone和服务器在同一局域网下。

我们最好先从登陆界面开始。当用户按下Start!按钮,我们需要发送一条JOIN命令到服务器。这让服务器知道这里有一个新的用户。服务器将会把他添加到活跃用户列表中

添加如下代码到LoginViewController的顶部:

#import "ASIFormDataRequest.h"
#import "MBProgressHUD.h"

把下面一大段代码添加到userDidJoin和loginAction方法之间:

复制代码
- (void)postJoinRequest
{
    MBProgressHUD* hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.labelText = NSLocalizedString(@"Connecting", nil);
 
    NSURL* url = [NSURL URLWithString:ServerApiURL];
    __block ASIFormDataRequest* request = [ASIFormDataRequest requestWithURL:url];
    [request setDelegate:self];
 
    [request setPostValue:@"join" forKey:@"cmd"];
    [request setPostValue:[dataModel udid] forKey:@"udid"];
    [request setPostValue:[dataModel deviceToken] forKey:@"token"];
    [request setPostValue:[dataModel nickname] forKey:@"name"];
    [request setPostValue:[dataModel secretCode] forKey:@"code"];
 
    [request setCompletionBlock:^
    {
        if ([self isViewLoaded])
        {
            [MBProgressHUD hideHUDForView:self.view animated:YES];
 
            if ([request responseStatusCode] != 200)
            {
                ShowErrorAlert(NSLocalizedString(@"There was an error communicating with the server", nil));
            }
            else
            {
                [self userDidJoin];
            }
        }
    }];
 
    [request setFailedBlock:^
    {
        if ([self isViewLoaded])
        {
            [MBProgressHUD hideHUDForView:self.view animated:YES];
            ShowErrorAlert([[request error] localizedDescription]);
        }
    }];
 
    [request startAsynchronous];
}
复制代码

呀,介都是嘛啊,让我们一行行的来解释下。

MBProgressHUD* hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.labelText = NSLocalizedString(@"Connecting", nil);

这将显示一个转菊花并阻止整个屏幕的活动,你可以在Ray’s article中了解关于MBProgressHUD更多的内容

NSURL* url = [NSURL URLWithString:ServerApiURL];
    __block ASIFormDataRequest* request = [ASIFormDataRequest requestWithURL:url];
    [request setDelegate:self];

这是为我们的服务器URL创建一个ASIFormDataRequest对象。ASIFormDataRequest在向服务器发送POST请求时做得很出色。你告诉他URL,给他POST的内容 然后他就发出去了,我将要花一分钟来解释下__block的意思

[request setPostValue:@"join" forKey:@"cmd"];
    [request setPostValue:[dataModel udid] forKey:@"udid"];
    [request setPostValue:[dataModel deviceToken] forKey:@"token"];
    [request setPostValue:[dataModel nickname] forKey:@"name"];
    [request setPostValue:[dataModel secretCode] forKey:@"code"];

在这里,我们向request中添加了POST的内容。我设计的服务器API将会在POST的数据中查找cmd的字符串。这个值决定API将会执行什么命令。这段代码是执行Join命令。

Join有四个参数。用户的昵称、secret code是显而易见的,这些事用户在login 页面所填入的信息。但是“udid” 和 “token”呢?也许你猜到了,token是用于推送的device token,这个是让服务器知道他要给设备推送信息时的地址。

UDID是设备的唯一ID,在数据库中标记用户我更青睐于用设备ID。我可以用nickname代替,但是这样就不能存在两个相同昵称的用户,我们就需要在app中告诉用户他的昵称已经被使用了。

我也可以用device token来当做用户的唯一id,但是device token时常更新。但是udid从来不会改变。我没有说这种方法适用所有web服务,但是对于我们来说已经足够了。

好的,现在开始变得有趣了

[request setCompletionBlock:^
    {
        ...
    }];

我们用了块函数(blocks)!这里有两种方法作为ASIFormDataRequest的通知结果。老方式是实现requestFinished 和 requestedFailed delegate。这是Ray在他的web服务文章中使用的方式。

这种方式可以很好的工作,但是必须是iOS4.0以上。Blocks被介绍为一种代替delegate的方式。但只是为了欢乐,我决定在这篇教程中使用块函数。这里我们设置了完成时的块函数。^{}之间的代码,现在是不会被执行的,直到请求成功的完成。

还记得在我创建ASIFormDataRequest对象时的__block标记吗?那是用来阻止block中保持对request引用的。因为request已经在block中保持了引用,这会导致retain循环,并导致内存泄露。

如果你接受不了,那就不要再纠结他了。只要记得当你用块函数替代delegate方法之时,把__block放到ASIFormDataRequest之前。

在块函数中,我们这样:

if ([self isViewLoaded])
        {
            [MBProgressHUD hideHUDForView:self.view animated:YES];
}

首先,我们判断是否还在加载。因为请求是异步在后台的,他会过一段时间才会完成。理论上说,在此期间我们的view是有可能被移除的(unload),例如当收到低内存警告。在那时候,我们仅仅无视请求曾经开始过的事实。

这种情况可能有点极端,但是这只是我程序的一些防护措施。会考虑这种奇怪的情况的下意识是非常好的,特别是你在后台做事情。

剩下的块函数是这样的:

复制代码
if ([request responseStatusCode] != 200)
{
    howErrorAlert(NSLocalizedString(@"There was an error communicating with the server", nil));
}
else
{
    [self userDidJoin];
}
复制代码

我们检查了相应的状态码(status code)。这是服务器API返回的HTTP状态码。如果一切正常,状态码是“200 OK”然而,计算机和internet是变化无常的,有时会出错。例如如果MySQL数据库掉线了,server API会返回 “500 Server Error” 出现那种情况时,我们会显示一个alert view。

在创造UIAlertView并且显示出来,使用ShowErrorAlert函数是很方便的。并且注意我在创建用于显示的字符串的时候使用了NSLocalizedString。这是一个让你本地化变得更容易的好习惯。请参看 Sean Berry’s tutorial on localization.

如果这里没有任何错误,我们调用userDidJoin方法。这个方法告诉DataModel,用户成功加入聊天室,并且关闭当前view,回到主屏幕。

因为没人知道internet会发生什么,我们同样为请求因为某些原因失败,设置一个块函数。经常是因为服务器不可访问,或者是请求时间过长(time out)导致失败。

复制代码
[request setFailedBlock:^
    {
        if ([self isViewLoaded])
        {
            [MBProgressHUD hideHUDForView:self.view animated:YES];
            ShowErrorAlert([[request error] localizedDescription]);
        }
    }];
复制代码

这里隐藏了转菊花,并用alert view显示错误。

复制代码
- (IBAction)loginAction
{
    ...
 
    // REPLACE THIS LINE:
    [self userDidJoin];
 
    // WITH:
    [self postJoinRequest];
}
复制代码

最后,我们告诉请求去做他该做的事情。你将要一直使用异步请求来和服务器通讯。请求会需要若干秒来完成,如果使用同步请求,你的程序会在请求的全过程中完全失去响应。并且如果无响应的时间过长,OS会终止掉你的程序。

还是在LoginViewController.m中,在loginAction中进行如下改变: – (NSString*)udid; – (NSString*)deviceToken; – (void)setDeviceToken:(NSString*)token;

事先,我们在用户点击Start!之后直接调用userDidJoin。现在不能这样了,我们只能在服务器请求完成之后才可调用。

如果你足够仔细,你会发现我们调用了两个DataModel并没定义的方法(udid 和 deviceToken)。让我们添加他们并摆脱编译器的警告。

在DataModel.h中添加:

- (NSString*)udid;
- (NSString*)deviceToken;
- (void)setDeviceToken:(NSString*)token;

在DataModel.m @implementation中添加如下代码:

- (NSString*)udid
{
    UIDevice* device = [UIDevice currentDevice];
    return [device.uniqueIdentifier stringByReplacingOccurrencesOfString:@"-" withString:@""];
}

这是获取设备的UDID。但是默认的UDID中间有破折号,但是我们只想让它的长度固定在40个字符。所以我们剥去破折号(仅供参考:我们的模拟器的device ID 只有32个字符)。

还在DataModel.m中@implementation 之上 添加如下代码:

static NSString* const DeviceTokenKey = @"DeviceToken";

添加下面的代码到@implementation 中

复制代码
- (NSString*)deviceToken
{
    return [[NSUserDefaults standardUserDefaults] stringForKey:DeviceTokenKey];
}
 
- (void)setDeviceToken:(NSString*)token
{
    [[NSUserDefaults standardUserDefaults] setObject:token forKey:DeviceTokenKey];
}
复制代码

这里是initialize方法的小改动。

复制代码
+ (void)initialize
{
    if (self == [DataModel class])
    {
        [[NSUserDefaults standardUserDefaults] registerDefaults:
            [NSDictionary dictionaryWithObjectsAndKeys:
                @"", NicknameKey,
                @"", SecretCodeKey,
                [NSNumber numberWithInt:0], JoinedChatKey,
 
                // 添加了这一行
                @"0", DeviceTokenKey,
 
                nil]];
    }
}
复制代码

我们这里做了什么?在setDeviceToken,我们将token存储在NSUserDefaults字典中。在deviceToken中我们从NSUserDefaults读取出来。如果你对NSUserDefautls不熟悉,这是一个很简单但很有用的类,能让你存储你app的设置。

initialize方法是用来Objective-C第一次运行DataModel类时调用的方法。我们将初始化的默认值存入NSUserDefaults

我们向默认值中添加了一行,device token 为字符串 0,我一会儿会解释这为什么是必须的。目前,它代表我们发送JOIN命令到我们的服务器,它使用0为device token代替真实的token

哎,我们花了这么久来讲述这些。下一篇教程让我们编译运行它,并看它是否正确工作。

posted @ 2014-09-06 10:24  幼儿园大班班长  阅读(967)  评论(0编辑  收藏  举报