天天看點

Apple推送通知服務教程 PART-2

生成APP ID和SSL證書

登入​​iOS Provisioning Portal​​頁面

首先,我們将要建立一個App ID. 每一個推送APP都需要一個唯一的對應的App ID,推送的消息将被送達到這個ID對應的APP應用中(這裡不能使用通配ID)。

在​​iOS Provisioning Portal​​頁面左側選擇 App IDs,然後點選 New App ID 的按鈕。

Apple推送通知服務教程 PART-2

在例子中,對應的表單項填的值如下:

· 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推送通知服務教程 PART-2
在“Apple Push Notification service”這一欄中,有兩個橙燈,分别對應配置APP推送功能的“開發版”和“産品版”,這也就是說,這個App ID能接收推送消息,但是我們仍然需要設定好才可以。點選Configure 連結打開對應的配置頁面。
Apple推送通知服務教程 PART-2
勾選“Enable for Apple Push Notification service ”的複選框,接下來點選Development Push SSL Certificate右邊的Configure 按鈕,會彈出“Apple推送通知服務SSL證書助手”的頁面。
Apple推送通知服務教程 PART-2
第一件事讓你生成一個證書簽名的請求,這一步我們已經做過了,是以點選Continue。下一步是上傳CSR檔案。選擇在前面步驟中我們已生成好的CSR檔案,然後點選Generate按鈕。
Apple推送通知服務教程 PART-2
等待數秒鐘後便會生成SSL證書。當證書生成完成後,點選Continue按鈕。
Apple推送通知服務教程 PART-2
現在點選Download 按鈕下載下傳這個生成好的檔案名為“aps_developer_identity.cer”的證書。點選Done 關閉證書助手,回到配置App ID的界面。
Apple推送通知服務教程 PART-2

現在可以看到,我們已經生成了一個可用的證書,現在推送通知功能已經為接下來的開發準備好了(開發版)。你可以在需要的時候重複下載下傳這個證書檔案。需要注意的是,這個開發證書隻有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。

Apple推送通知服務教程 PART-2

在上面的區域裡我是這麼填寫的:

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服務來增強你的應用功能。

Apple推送通知服務教程 PART-2

注意:這篇教程相對比較長,你需要做好一個較長的時間準備去看完它。但它是很值的,一旦你看完它,你就擁有了一個更強大的應用和使用推送消息的Web服務。

介紹PushChat

在這個教程中,我們将要做一個叫做PushChat的簡單的發消息應用,它采用推送消息來發資訊。以下是它的樣子:

Apple推送通知服務教程 PART-2

使用者看到的第一個螢幕是登陸界面。在這裡使用者輸入他們的昵稱和一個secret code。使用者與他們的朋友們分享這個secret code。

每一個使用相同的secret code的人可以看到互相的消息。那麼實際上,這個secret code相當于一個聊天室的名字。當然,如果你能猜到别人的code,那麼你也能看到别人消息,是以它叫做secret code。

當使用者按了Start!按鈕,應用就給我們的伺服器發送一條注冊聊天室的消息。然後登陸界面就跳轉到聊天界面:

Apple推送通知服務教程 PART-2

Secret code 顯示在navigation bar上。本地使用者發送的消息顯示在螢幕的右邊,收到的消息則顯示在左邊。那麼在這樣例子中,這個使用者和一個叫做SteveJ的人同時登陸到了這個叫做“TopSecretRoom123”的聊天室中。

第三個也是最後一個界面是輸入界面。

Apple推送通知服務教程 PART-2

這裡沒有什麼特别的地方。它隻是一個帶有鍵盤的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也許會經常更新,如果發現更新了,就必須讓伺服器知道.關于這個之後會詳細讨論

它的工作流程如圖所示

Apple推送通知服務教程 PART-2

要頻繁的向伺服器發送資料,要讓伺服器知道使用者加入或離開一個聊天室或者她正在發送一條新的消息。伺服器輪流建立推送消息然後發給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視窗。

Apple推送通知服務教程 PART-2
點選OPENSTARTPAGE 按鍵,打開了預設浏覽器,進入welcome界面。
Apple推送通知服務教程 PART-2

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,打開系統偏好設定,進入網絡面闆.

Apple推送通知服務教程 PART-2

我使用我的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按鍵,螢幕如下:

Apple推送通知服務教程 PART-2

在CREATE DATABASE 區域鍵入 pushchat,選擇 “utf8_general_ci”核對,然後點選 CREATE建立資料庫,可以看到資料建立成功的提示。

點選 螢幕頂端的Privileges按鍵,然後添加一個新使用者。

Apple推送通知服務教程 PART-2

如下填寫:

· 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檔案裡:

<VirtualHost *:44447>
    …

    SetEnv APPLICATION_ENV development

    ...
</VirtualHost>      

“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)簽名。

Apple推送通知服務教程 PART-2

編譯運作,好好把玩一下,因為新的APP使用的BUNDLE ID 和最開始是的TEST APP相同,

是以在編譯運作之前最好先 解除安裝那個 舊的APP,以確定你的IPHONE不會報錯。

打開 MAINWINDOW.XIB,看一下 APP是如何構成的:

Apple推送通知服務教程 PART-2

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