企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)(實(shí)戰(zhàn)篇)_第1頁
企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)(實(shí)戰(zhàn)篇)_第2頁
企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)(實(shí)戰(zhàn)篇)_第3頁
企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)(實(shí)戰(zhàn)篇)_第4頁
企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)(實(shí)戰(zhàn)篇)_第5頁
已閱讀5頁,還剩44頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

企業(yè)級(jí)iOS應(yīng)用開發(fā)實(shí)戰(zhàn)\h\h實(shí)戰(zhàn)篇目錄\h實(shí)戰(zhàn)篇\h第18章企業(yè)APN\h18.1企業(yè)APN的建設(shè)\h18.2iPhone與APN\h18.3配置描述文件\h18.4在iPhone上實(shí)現(xiàn)一個(gè)HTTP服務(wù)器\h18.5后臺(tái)任務(wù)與無限后臺(tái)任務(wù)\h18.6實(shí)現(xiàn)APN切換\h18.7檢測(cè)網(wǎng)絡(luò)狀況\h18.8Safari阻塞\h18.9本章小結(jié)\h第19章iOS企業(yè)應(yīng)用實(shí)戰(zhàn)\h19.1應(yīng)用場(chǎng)景與功能概述\h19.2應(yīng)用程序架構(gòu)\h19.3服務(wù)器端\h19.4iPhone客戶端\h19.5本章小結(jié)\h光盤內(nèi)容實(shí)戰(zhàn)篇第18章企業(yè)APNAPN(AccessPointName,接入點(diǎn)名稱)是手機(jī)通過運(yùn)營(yíng)商接入互聯(lián)網(wǎng)時(shí)必須設(shè)置的一個(gè)名稱。這個(gè)名稱根據(jù)運(yùn)營(yíng)商和網(wǎng)絡(luò)類型的不同而不同,比如中國(guó)移動(dòng)的cmnet/cmwap,中國(guó)聯(lián)通的uninet/uniwap/3gnet/3gwap,中國(guó)電信的ctnet/ctwap。在日常生活中,我們通過在手機(jī)上設(shè)置這個(gè)接入點(diǎn)名稱,就能使手機(jī)瀏覽網(wǎng)頁并訪問互聯(lián)網(wǎng)。在企業(yè)移動(dòng)應(yīng)用中,手機(jī)客戶端當(dāng)然也可以通過互聯(lián)網(wǎng)來使用移動(dòng)應(yīng)用。但是,通過互聯(lián)網(wǎng)來訪問位于企業(yè)內(nèi)部的數(shù)據(jù)和服務(wù),相當(dāng)于把整個(gè)企業(yè)內(nèi)部網(wǎng)絡(luò)暴露在公網(wǎng)中,這種做法并不安全。那么,我們可以選擇“企業(yè)APN”。企業(yè)APN也叫“專線APN”,是APN中的一種,不同的是,通過企業(yè)APN,我們的手機(jī)接入的是企業(yè)內(nèi)網(wǎng),而不是互聯(lián)網(wǎng)。這樣,通過使用企業(yè)APN,把企業(yè)私網(wǎng)與互聯(lián)網(wǎng)隔離開來,滿足了企業(yè)在網(wǎng)絡(luò)使用上的安全要求。本章將介紹企業(yè)APN在企業(yè)應(yīng)用中的應(yīng)用概況,以及使用企業(yè)APN網(wǎng)絡(luò)后對(duì)iOS客戶端的一些特殊要求。18.1企業(yè)APN的建設(shè)企業(yè)APN的建設(shè),需要建設(shè)方(企業(yè))和運(yùn)營(yíng)商之間的密切合作。大體來講,需要經(jīng)過以下幾個(gè)步驟:1.商務(wù)談判階段首先企業(yè)向營(yíng)運(yùn)商提出申請(qǐng),雙方業(yè)務(wù)部門進(jìn)行初步的磋商,達(dá)成一致意見后以簽訂正式合同的方式確定合作關(guān)系。商務(wù)談判階段需要確定的內(nèi)容包括:建設(shè)費(fèi)用、帶寬要求、企業(yè)專線的資費(fèi)標(biāo)準(zhǔn)、APN用戶資費(fèi)標(biāo)準(zhǔn)、工期等。2.方案準(zhǔn)備階段此階段需要和運(yùn)營(yíng)商共同確定具體施工方案,以及雙方權(quán)責(zé)的劃分。包括光纜走線布線、設(shè)備上架和調(diào)試、光纜通過路徑、最近接入的光交箱或基站位置、室內(nèi)光纖布線等。3.施工階段此階段按照施工方案進(jìn)行施工,由雙方各自負(fù)責(zé)相應(yīng)的工作量,分頭進(jìn)行。一般而言,企業(yè)負(fù)責(zé)光纖進(jìn)入企業(yè)后的項(xiàng)目,包括:機(jī)房裝修,室內(nèi)光纖布線,防火墻IP地址和端口過濾,以及配套設(shè)備采購及安裝(GRE隧道路由器、Radius服務(wù)器、DHCP服務(wù)器、光電路由器及配套的機(jī)箱和電源)。而運(yùn)營(yíng)商則負(fù)責(zé)室外光纜的布放、尾纖的制作及跳線。4.調(diào)試階段施工完成,由移動(dòng)運(yùn)營(yíng)商負(fù)責(zé)建立業(yè)務(wù)數(shù)據(jù)、分配網(wǎng)關(guān)和企業(yè)的私有IP地址。雙方通過私有IP進(jìn)行聯(lián)網(wǎng),雙方互Ping連通。5.完工階段調(diào)試成功后,企業(yè)(或運(yùn)營(yíng)商)需要設(shè)置Radius服務(wù)器和DHCP服務(wù)器。Radius服務(wù)器負(fù)責(zé)對(duì)企業(yè)APN用戶的主叫號(hào)碼及賬號(hào)、密碼進(jìn)行認(rèn)證,而DHCP服務(wù)器負(fù)責(zé)為認(rèn)證通過的用戶分配IP地址。因此需要將準(zhǔn)備使用企業(yè)APN的用戶的主叫號(hào)碼向Radius服務(wù)器進(jìn)行注冊(cè)。這樣就可以限制了只有某些主叫號(hào)碼能夠訪問企業(yè)APN,其他號(hào)碼則不能訪問。也就是說,企業(yè)APN和用戶的SIM卡號(hào)碼是綁定的,進(jìn)一步保證了企業(yè)網(wǎng)絡(luò)的安全運(yùn)行。18.2iPhone與APNiOS升級(jí)至4.1之后,市面上許多版本的iPhone已經(jīng)無法設(shè)置APN選項(xiàng)。比如使用聯(lián)通卡的用戶,比如美版iPhone,有的港版iPhone和移動(dòng)卡用戶也無法設(shè)置APN。當(dāng)然,解決方法也不是沒有。網(wǎng)上流傳的方法很多,比如安裝APNEditing,或者TetherMe-個(gè)人熱點(diǎn),前提當(dāng)然是要越獄。對(duì)企業(yè)APN而言,這絕對(duì)算不上是什么好消息。想解決這個(gè)問題,不一定非要越獄。蘋果AppStore中,也有不少的APN切換工具可以選擇。但是對(duì)于用戶來說,每次打開企業(yè)應(yīng)用前都需要先打開APN切換工具,肯定會(huì)有一些不好的體驗(yàn)。我對(duì)此的建議是,在企業(yè)應(yīng)用中嵌入一個(gè)APN切換工具的所有功能。首先需要聲明的是,iOSSDK中絕對(duì)沒有關(guān)于APN設(shè)置的API,你可以查找所有的蘋果官方文檔。不要說APN設(shè)置,關(guān)于系統(tǒng)“設(shè)置”程序中的所有功能選項(xiàng),蘋果都沒有提供API。提示:在iOS5.0時(shí),蘋果曾開放了一種新的URLScheme方案,用于支持對(duì)設(shè)置程序的訪問。你可以看這里/kmyhy/article/details/7940660。但不幸的是,在iOS5.1之后,蘋果又迅速將這個(gè)權(quán)限收回了。那么,蘋果應(yīng)用商店中的APN切換工具又是如何做的呢?通過蘋果的文檔企業(yè)部署指南(EnterpriseDeploymentGuide),我們可以知道,要修改iPhone的APN設(shè)置,可以通過配置描述文件(.mobileconfig后綴文件)進(jìn)行。企業(yè)通過iTunes、iPhone配置工具或OTA(通過Email或者HTTP)部署配置描述文件,iPhone手機(jī)就可以訪問配置描述文件中指定的APN。而運(yùn)營(yíng)商們(比如聯(lián)通),正是通過這種方式,將每一臺(tái)iPhone簽約機(jī)設(shè)置為自己的APN。同時(shí),在企業(yè)部署指南中提到,通過“iPhone配置工具”,我們可以編輯、創(chuàng)建自己想要的配置描述文件。配置描述文件的安裝則是通過Safari瀏覽器自動(dòng)進(jìn)行的。Safari下載到配置描述文件之后,會(huì)自動(dòng)調(diào)用系統(tǒng)的“設(shè)置”程序進(jìn)行安裝。當(dāng)然,還有一個(gè)問題。對(duì)于一個(gè)APN切換工具來說,它很難以O(shè)TA的方式部署配置描述文件。因?yàn)镺TA部署需要通過網(wǎng)絡(luò)進(jìn)行,而用戶本來就無法訪問網(wǎng)絡(luò)——APN都無法設(shè)置,手機(jī)當(dāng)然無法上網(wǎng)。沒有網(wǎng)絡(luò),APN切換工具無法使用OTA部署配置描述文件,只能另辟蹊徑。經(jīng)過多次觀察市面上的APN切換工具,你會(huì)發(fā)現(xiàn)多數(shù)APN切換工具都會(huì)提供自己的HTTP服務(wù)。也就是說,APN切換工具本身就帶有一個(gè)HTTP服務(wù)器。不要奇怪,iPhone是可以作為一臺(tái)功能正常的服務(wù)器使用的。很早以前就有人這樣干過。如果把配置描述文件放到了本地服務(wù)器(即iPhone)上,那么Safari可以通過本機(jī)的HTTP服務(wù)來訪問配置描述文件,從而避開網(wǎng)絡(luò)的訪問。本章接下來的內(nèi)容,就將圍繞如何實(shí)現(xiàn)一個(gè)APN切換工具進(jìn)行。相信經(jīng)過本章的介紹,你不難在自己的企業(yè)應(yīng)用中集成一個(gè)APN切換器的功能。18.3配置描述文件iOS支持兩種描述文件,預(yù)置描述文件和配置描述文件。預(yù)置描述文件以.mobileprovision為后綴名,配置描述文件以.mobileconfig為后綴名。提示:預(yù)置描述文件(.mobileprovision)即本書早些時(shí)候介紹過的用于在真機(jī)上調(diào)試和部署應(yīng)用程序的“設(shè)備激活文檔”。安裝到iPhone上的配置描述文件,可以在iPhone的系統(tǒng)設(shè)置程序中進(jìn)行查看。打開系統(tǒng)設(shè)置程序,轉(zhuǎn)到“通用→描述文件”頁面,在“配置描述文件”文件欄,你可以看到所有已安裝的配置描述文件,如圖18-1所示。圖18-1在設(shè)置程序中查看配置描述文件配置描述文件其實(shí)是一個(gè)plist文件,其形式類似于如下代碼所示:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEplistPUBLIC"-//Apple/DTDPLIST1.0//EN""/DTDs/PropertyList-1.0.dtd"><plistversion="1.0"><dict><key>PayloadContent</key><array><dict><key>PayloadDisplayName</key><string>LDAPSettings</string><key>PayloadType</key><string>com.apple.ldap.account</string><key>PayloadVersion</key><integer>1</integer><key>PayloadUUID</key><string>6df7a612-ce0a-4b4b-bce2-7b844e3c9df0</string><key>PayloadIdentifier</key><string>com.example.iPhone.settings.ldap</string><key>LDAPAccountDescription</key><string>CompanyContacts</string><key>LDAPAccountHostName</key><string></string><key>LDAPAccountUseSSL</key><false/></dict></plist>.mobileconfig文件是一個(gè)標(biāo)準(zhǔn)的XML文件,包含對(duì)iPhone進(jìn)行的所有設(shè)置(包括APN設(shè)置)。你可能不明白.mobileconfig文件語法,不用擔(dān)心,在蘋果公司官方文檔“企業(yè)部署指南”的附錄B中,列出了.mobileconfig文件格式規(guī)范。但是,你沒有必要手工編寫.mobileconfig文件。蘋果提供了專門的“iPhone配置工具”用于創(chuàng)建配置描述文件,不需要你記憶繁瑣的.mobileconfig文件語法。首先,你需要下載“iPhone配置工具”:/kb/DL1465?viewlocale=zh_CN并將它安裝到你的Mac上。打開“應(yīng)用程序→實(shí)用工具→iPhone配置實(shí)用工具”。在“資料庫”一欄中選擇“配置描述文件”,然后點(diǎn)擊工具欄第一個(gè)按鈕“新建”,創(chuàng)建一個(gè)新的配置描述文件,如圖18-2所示。圖18-2創(chuàng)建一個(gè)配置描述文件接下來,我們嘗試配置一個(gè)中國(guó)移動(dòng)cmnet的APN網(wǎng)絡(luò)設(shè)置。其實(shí),對(duì)于APN配置來說,不管是中國(guó)移動(dòng)的cmnet還是中國(guó)連通的3gnet,都是非常簡(jiǎn)單的:只需要在配置文件中指定APN名稱一項(xiàng)即可。在圖18-2中的“通用”欄,我們按照如下設(shè)置:?“名稱”一項(xiàng)為文檔指定一個(gè)描述性的名稱,比如設(shè)置為“移動(dòng)cmnet”,這個(gè)名稱會(huì)作為配置文件的文件名。?“標(biāo)識(shí)符”一項(xiàng)為文檔指定一個(gè)唯一標(biāo)識(shí),比如“pany.××××”。pany使用反域名規(guī)則,××××標(biāo)識(shí)文檔的內(nèi)容。iOS通過標(biāo)志符來識(shí)別不同的配置描述文件,安裝描述文件時(shí),iOS會(huì)對(duì)已安裝的標(biāo)志符相同的描述文件進(jìn)行覆蓋。?“機(jī)構(gòu)”一項(xiàng)可填寫你公司的名字。?“描述”一項(xiàng)隨便填寫,用于描述該配置文件的內(nèi)容。注意,“通用”項(xiàng)是必須填寫的,不管你實(shí)際要配置什么內(nèi)容。接下來,我們切換到“APN”一項(xiàng),將“訪問點(diǎn)名稱”指定為中國(guó)移動(dòng)的上網(wǎng)配置:cmnet,其他不用填寫。點(diǎn)擊工具欄中的“導(dǎo)出”按鈕,彈出“導(dǎo)出配置文件向?qū)А保鐖D18-3所示。圖18-3導(dǎo)出配置文件向?qū)г凇鞍踩浴敝羞x擇“給配置描述文件簽名”,然后點(diǎn)擊“導(dǎo)出……”按鈕。在接下來的窗口中選擇保存地址及文件名稱,然后點(diǎn)擊“存儲(chǔ)”。18.4在iPhone上實(shí)現(xiàn)一個(gè)HTTP服務(wù)器為什么要在iPhoneapp中自己實(shí)現(xiàn)一個(gè)HTTP服務(wù)器?因?yàn)锳PN切換需要在iPhone上安裝一個(gè)配置描述文件。但當(dāng)我們獲得了配置描述文件之后,問題就來了。我們把這個(gè)配置描述文件放在什么地方才是用戶可以獲得的?根據(jù)蘋果文檔“OTA文檔的下發(fā)及配置”,可以有4種方法部署配置文檔:?通過與設(shè)備的物理連接。?通過Email。?通過Web網(wǎng)頁。?通過Over-the-air設(shè)置。首先第一項(xiàng)和第四項(xiàng)被排除。因?yàn)橥ㄟ^物理連接方式,幾臺(tái)或少量的設(shè)備還好辦,如果大范圍部署(成百上千臺(tái))是不可想象的。如果采用OTA部署,則需要使用蘋果的SCEP協(xié)議實(shí)現(xiàn)設(shè)備的注冊(cè)和認(rèn)證,而一個(gè)完整的SCEP驗(yàn)證過程相當(dāng)復(fù)雜,對(duì)于我們這樣的簡(jiǎn)單需求完全沒有必要。使用Email或Web頁的方式是不錯(cuò)的選擇。相比較而言,后者(使用Web頁)更加方便,Safari通過URL來訪問配置描述文件并進(jìn)行安裝。問題是當(dāng)用戶的APN設(shè)置不正確的時(shí)候,用戶無法上網(wǎng),又如何訪問該Web頁?所以我們要自己實(shí)現(xiàn)一個(gè)HTTP服務(wù)器,并將配置描述文件放到服務(wù)器可以訪問的地方,比如應(yīng)用程序自身的資源束中。這樣,無論網(wǎng)絡(luò)是否可用,本地回環(huán)地址()總是可以訪問的。要實(shí)現(xiàn)一個(gè)HTTP服務(wù)器,這涉及UnixSocket編程,不管你是用BSDSockets還是CocoaCFSocketAPI。無論如何,你不得不跟這些“丑陋”的C語言的API打交道——這不是一件容易的事情。幸運(yùn)的是,有人實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的,可擴(kuò)展的HTTP服務(wù)器:/2009/07/simple-extensible-http-server-in-cocoa.html在這個(gè)簡(jiǎn)單的HTTP服務(wù)器的實(shí)現(xiàn)中,只包含了兩個(gè)類:HTTPServer和HTTPResponseHandler。在后面的Demo里,我引入了這兩個(gè)類(有一些細(xì)微的修改)。提示:HTTPServer使用了CFNetwork框架,別忘了在項(xiàng)目中引入CFNetwork.framework。接下來是一個(gè)Demo,我們演示了如何用HTTPServer和HTTPResponseHandler實(shí)現(xiàn)定制的HTTP服務(wù)器。示例程序位于光盤“source/第18章/ApnDemo”目錄下。新建SingleViewApplication,將HTTPServer和HTTPResponseHandler拷貝到項(xiàng)目文件夾。由于HTTPServer使用了SynthesizeSingleton.h,別忘了把SynthesizeSingleton.h也拷貝到項(xiàng)目文件夾。注意:在HTTPServer.h中,LocalPort常量定義了本地監(jiān)聽端口為7531,如果你想監(jiān)聽不同的端口,請(qǐng)修改為其他值。新建類MyResponseHandler,繼承自HTTPResponseHandler。我們首先需要重載NSObject的類方法load,在這個(gè)方法中,我們必須向父類進(jìn)行注冊(cè):+(void)load{[HTTPResponseHandlerregisterHandler:self];}通過這種方法,HTTPResponseHandler能準(zhǔn)確地知道自己所擁有的子類。這是為了便于HTTPRequestHandler能根據(jù)不通請(qǐng)求在已注冊(cè)的HTTPResponseHandler子類中查找適當(dāng)?shù)奶幚沓绦颉=酉聛韺?shí)現(xiàn)canHandleRequest:method:url:headerFields:方法。在這個(gè)方法中,我們應(yīng)當(dāng)表明MyResponseHandler類所能處理的請(qǐng)求。這里,返回YES即用MyResponseHandler來處理所有的請(qǐng)求:+(BOOL)canHandleRequest:(CFHTTPMessageRef)aRequestmethod:(NSString*)requestMethodurl:(NSURL*)requestURLheaderFields:(NSDictionary*)requestHeaderFields{returnYES;}然后實(shí)現(xiàn)startResponse方法,在這個(gè)方法中,我們從請(qǐng)求中獲取URL路徑的最后一部分,然后在資源束中查找相應(yīng)的文件,如果找到,返回文件內(nèi)容:-(void)startResponse{NSString*filename=url.lastPathComponent;NSString*path=[[NSBundlemainBundle]pathForResource:filenameofType:nil];BOOLexists=[[NSFileManagerdefaultManager]fileExistsAtPath:path];if(!exists){return;}NSData*fileData=[NSDatadataWithContentsOfFile:path];CFHTTPMessageRefresponse=CFHTTPMessageCreateResponse(kCFAllocatorDefault,200,NULL,kCFHTTPVersion1_1);CFHTTPMessageSetHeaderFieldValue(response,(CFStringRef)@"Content-Type",(CFStringRef)@"text/plain");CFDataRefheaderData=CFHTTPMessageCopySerializedMessage(response);@try{[fileHandlewriteData:(NSData*)headerData];[fileHandlewriteData:fileData];}@catch(NSException*exception){}@finally{CFRelease(headerData);[servercloseHandler:self];}}現(xiàn)在我們可以在應(yīng)用程序一啟動(dòng)的時(shí)候啟動(dòng)服務(wù)器了。編輯AppDelegate.m,引入HTTPServer.h頭文件,在application:didFinishLaunchingWithOptions:方法中加入此句:[[HTTPServersharedHTTPServer]start];同時(shí)在applicationWillTerminate:方法中記得關(guān)閉HTTPServer:[[HTTPServersharedHTTPServer]stop];隨便將任意文件(文本文件、圖片文件)復(fù)制到項(xiàng)目文件夾中,例如我們把一個(gè)名為“textfile”的文本文件(文件內(nèi)容是:Justsoso.)復(fù)制到示例項(xiàng)目中,運(yùn)行程序,然后打開Safari瀏覽器,在地址欄輸入::7531/textfile,瀏覽器將輸出文本文件textfile的內(nèi)容“Justsoso.”。如圖18-4所示。提示:如果是圖片文件,則瀏覽器會(huì)輸出圖片的內(nèi)容。當(dāng)然,因?yàn)槲覀冊(cè)趕tartResponse方法中,沒有正確地設(shè)置響應(yīng)頭(如Content-Type),瀏覽器中輸出的只能是亂碼,而不是正確的圖形。18.5后臺(tái)任務(wù)與無限后臺(tái)任務(wù)剛才我們是在Mac的Safari瀏覽器中進(jìn)行測(cè)試的。ApnDemo程序在iOSSafari(不管是模擬器還是iPhone)上測(cè)試無法得到正確的結(jié)果。為什么呢?因?yàn)閕OS本質(zhì)上仍然是一個(gè)單任務(wù)系統(tǒng),當(dāng)你在iPhone上,將前臺(tái)程序由ApnDemo切換到Safari時(shí),ApnDemo將退出前臺(tái)。而當(dāng)程序一旦轉(zhuǎn)入后臺(tái),它的代碼就不會(huì)再被執(zhí)行,HTTP服務(wù)器也就無法正常工作。要讓應(yīng)用程序能夠在后臺(tái)執(zhí)行,必須使用第14章14.3節(jié)講到的iOS4“后臺(tái)任務(wù)”。我們需要在applicationDidEnterBackground:方法中聲明一個(gè)后臺(tái)任務(wù):-(void)applicationDidEnterBackground:(UIApplication*)application{backgroundTask=[applicationbeginBackgroundTaskWithExpirationHandler:^{dispatch_async(dispatch_get_main_queue(),^{if(backgroundTask!=UIBackgroundTaskInvalid){if([[HTTPServersharedHTTPServer]state]!=0){[[HTTPServersharedHTTPServer]stop];}[applicationendBackgroundTask:backgroundTask];backgroundTask=UIBackgroundTaskInvalid;}});}];//開始運(yùn)行后臺(tái)任務(wù)dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{[NSThreadsleepForTimeInterval:15];NSLog(@"Timeremaining:%f",[applicationbackgroundTimeRemaining]);dispatch_async(dispatch_get_main_queue(),^{if(backgroundTask!=UIBackgroundTaskInvalid){if([[HTTPServersharedHTTPServer]state]!=0){[[HTTPServersharedHTTPServer]stop];}[applicationendBackgroundTask:backgroundTask];backgroundTask=UIBackgroundTaskInvalid;}});});}注意,[NSThreadsleepForTimeInterval:15];一句向iOS申請(qǐng)了15秒鐘的后臺(tái)運(yùn)行時(shí)間。iOS不會(huì)管你把這15秒用來干什么,甚至是什么也不干。它只是把應(yīng)用程序“掛起”的期限往后延了15秒而已。因此在這15秒內(nèi)雖然我們什么也沒做,但HTTPServer服務(wù)器仍然是工作的。你可以再次運(yùn)行ApnDemo程序,然后按“Home”鍵把它退到后臺(tái),打開Safari,在地址欄輸入:7531/textfile,就可以查看到textfile文件的內(nèi)容,如圖18-4所示。當(dāng)然,這一切必須在15秒之內(nèi)完成。圖18-4在iOSSafari中進(jìn)行測(cè)試如果你覺得15秒鐘的后臺(tái)執(zhí)行時(shí)間不夠,你可以增加這個(gè)數(shù)字。但這個(gè)數(shù)字不是無限大的。根據(jù)蘋果文檔中關(guān)于后臺(tái)執(zhí)行的描述,任何App都有10分鐘左右的后臺(tái)任務(wù)執(zhí)行時(shí)間。10分鐘后,App會(huì)被iOS強(qiáng)行掛起。但是,有5類App允許有“無限的”后臺(tái)運(yùn)行時(shí)間:?Audio?Location/GPS?VoIP?Newsstand?ExernalAccessory你可以將任何應(yīng)用程序聲明為上述5種類型以獲得無限的后臺(tái)運(yùn)行時(shí)間,但當(dāng)你提交App到AppStore時(shí),蘋果會(huì)審查你的App,一旦發(fā)現(xiàn)你“濫用”了后臺(tái)API,你的App將被拒絕。當(dāng)然,對(duì)于企業(yè)開發(fā)而言,不存在“濫用”的問題——企業(yè)App可以通過OTA部署,不經(jīng)過蘋果商店審查。你可以將ApnDemo聲明為VoIP,雖然ApnDemo和VoIP沒有絲毫關(guān)系,我們的目的只是為了讓iOS給我們無限后臺(tái)執(zhí)行的權(quán)限。聲明過程是在App的ApnDemo-Info.plist文件中加入一個(gè)名為UIBackgroundModes的key:<key>UIBackgroundModes</key><array><string>voip</string></array>然后在AppDelegate.m中定義一個(gè)backgroundHandler方法:-(void)backgroundHandler{NSLog(@"###-->backgroundinghandler");UIApplication*app=[UIApplicationsharedApplication];backgroundTask=[appbeginBackgroundTaskWithExpirationHandler:^{[appendBackgroundTask:backgroundTask];backgroundTask=UIBackgroundTaskInvalid;}];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{});}在backgroundHandler中,我們啟動(dòng)了一個(gè)后臺(tái)任務(wù),當(dāng)然在后臺(tái)任務(wù)塊中沒有代碼——我們沒有額外的工作要做,我們要求的只是應(yīng)用程序不被掛起而已。然后修改applicationDidEnterBackground:方法中的代碼如下:-(void)applicationDidEnterBackground:(UIApplication*)application{BOOLbackgroundAccepted=[[UIApplicationsharedApplication]setKeepAliveTimeout:600handler:^{[selfbackgroundHandler];}];if(backgroundAccepted){NSLog(@"backgroundingaccepted");}[selfbackgroundHandler];}這里,我們使用了新的setKeepAliveTimeout:handler:API方法。注意,魔法來自于這里。我們?cè)趆andler塊以及iOS接受后臺(tái)任務(wù)之后都調(diào)用了backgroundHandler方法!也就是說,當(dāng)?shù)谝淮紊暾?qǐng)的600秒后臺(tái)執(zhí)行時(shí)間用完之后,我們會(huì)再次申請(qǐng)新的后臺(tái)執(zhí)行時(shí)間?,F(xiàn)在,我們的ApnDemo擁有了無限的后臺(tái)執(zhí)行時(shí)間,你可以在ApnDemo程序轉(zhuǎn)入后臺(tái)后的任何時(shí)間請(qǐng)求HTTPServer服務(wù)。18.6實(shí)現(xiàn)APN切換現(xiàn)在,我們來實(shí)現(xiàn)不同APN之間如何切換。比如,我們中國(guó)移動(dòng)的兩個(gè)APN,其接入點(diǎn)名稱分別為cmnet和cmwap。首先,參照前面介紹的步驟,我們用“iPhone配置使用工具”分別制作兩個(gè)APN配置文件:cment.mobileconfig和cmwap.mobileconfig。提示:在創(chuàng)建這兩個(gè)描述文件時(shí),兩者的標(biāo)識(shí)符最好相同,比如都叫做“pany.apnconfig”。將這兩個(gè).mobileconfig文件加入(復(fù)制)到ApnDemo項(xiàng)目中。打開MyResponseHandler.m:CFHTTPMessageSetHeaderFieldValue(response,(CFStringRef)@"Content-Type",(CFStringRef)@"text/plain");將“text/plain”修改為“application/x-apple-aspen-config”。打開ViewController.xib,在界面上拖入兩個(gè)按鈕,如圖18-5所示。圖18-5ViewController.xib的界面設(shè)計(jì)打開AssistantEditor,將兩個(gè)按鈕連接到Action方法switchAPN:-(IBAction)switchAPN:(id)sender{UIButton*button=(UIButton*)sender;NSString*filename=[button.titleLabel.textstringByAppendingString:@".mobileconfig"];NSURL*url=[NSURLURLWithString:[@":7531/"stringByAppendingString:filename]];[[UIApplicationsharedApplication]openURL:url];}在設(shè)備上運(yùn)行程序,當(dāng)你點(diǎn)擊cmnet按鈕,Safari會(huì)彈出,緊接著iOS會(huì)調(diào)用系統(tǒng)設(shè)置程序的描述文件安裝界面如圖18-6所示。圖18-6設(shè)置程序的描述文件安裝界面點(diǎn)擊“安裝”按鈕,將開始安裝描述文件。提示:如果你的iPhone上已經(jīng)安裝了其他安裝描述文件,則iOS彈出“一次只能安裝一個(gè)APN配置”的提示。這時(shí)你只有先將已安裝的描述文件刪除才能繼續(xù)安裝。安裝后的描述文件,可以通過“設(shè)置→通用→描述文件”來查看。如果你想將APN網(wǎng)絡(luò)切換到cmwap,可以點(diǎn)擊cmwap按鈕。設(shè)置程序?qū)⒆詣?dòng)安裝cmwap.mobileconfig文件。注意:雖然你的iPhone已安裝過一個(gè)cmnet.mobileconfig描述文件,但此時(shí)并不會(huì)提示“一次只能安裝一個(gè)APN配置”的提示,而是提示“安裝此描述文件將改變iPhone上的設(shè)置”。這是因?yàn)閏mwap和cmnet的描述文件使用的是同一個(gè)“標(biāo)識(shí)符”,iOS將cmwap的安裝行為視同為對(duì)同一個(gè)描述文件的“更改”而不是新安裝。18.7檢測(cè)網(wǎng)絡(luò)狀況APN切換經(jīng)常會(huì)出現(xiàn)意外。比如,用戶重復(fù)安裝描述文件——由于不能很直觀地看到iPhone當(dāng)前的網(wǎng)絡(luò)狀況,用戶往往會(huì)在網(wǎng)絡(luò)已經(jīng)可用的情況下,仍然會(huì)執(zhí)行多余的安裝步驟。此外,iPhone的網(wǎng)絡(luò)狀況也會(huì)因移動(dòng)信號(hào)的強(qiáng)弱而發(fā)生變化。為此,我們需要為應(yīng)用程序提供網(wǎng)絡(luò)檢測(cè)的功能,讓用戶能夠?qū)Phone的網(wǎng)絡(luò)通斷情況有一個(gè)更直觀的了解。比如獲知iPhone的某個(gè)網(wǎng)卡(WiFi網(wǎng)卡或蜂窩數(shù)據(jù)網(wǎng)卡)是否已經(jīng)打開。比如讀取用戶當(dāng)前的IP地址。甚至于需要提供一個(gè)ping工具,以便用戶可以在iPhone上對(duì)某個(gè)服務(wù)器進(jìn)行ping測(cè)試。首先,我們來看如何獲取iPhone的IP地址。我們?cè)贏pnDemo的ViewController.xib中增加兩個(gè)控件:一個(gè)按鈕和一個(gè)TextView,如圖18-7所示。圖18-7ViewController.xib的界面設(shè)計(jì)用AssistantEditor創(chuàng)建兩個(gè)連接,一個(gè)IBAction連接由“檢測(cè)網(wǎng)絡(luò)”按鈕連接至方法detectNetwork,一個(gè)IBOutlet由TextView連接至屬性textView。在方法detectNetwork中,我們可以使用getifaddrs()函數(shù)(在頭文件ifaddrs.h中聲明)獲得網(wǎng)卡地址:-(IBAction)detectNetwork:(id)sender{NSMutableString*tvString=[[NSMutableStringalloc]init];NSString*wIFIStatus=nil,*cellularStatus=nil;UInt32address=0;structifaddrs*interfaces;if(getifaddrs(&interfaces)==0){structifaddrs*interface;for(interface=interfaces;interface;interface=interface->ifa_next){if((interface->ifa_flags&IFF_UP)&&?。╥nterface->ifa_flags&IFF_LOOPBACK)&&(strcmp(interface->ifa_name,"en0")==0||strcmp(interface->ifa_name,"pdp_ip0")==0)){conststructsockaddr_in*addr=(conststructsockaddr_in*)interface->ifa_addr;if(addr&&addr->sin_family==AF_INET){address=addr->sin_addr.s_addr;if(strcmp(interface->ifa_name,"en0")==0){wIFIStatus=[selfipv4Address:address];}if(strcmp(interface->ifa_name,"pdp_ip0")==0){cellularStatus=[selfipv4Address:address];}}}}freeifaddrs(interfaces);}if(wIFIStatus==nil){wIFIStatus=@"關(guān)閉";}if(cellularStatus==nil){cellularStatus=@"關(guān)閉";}[tvStringappendFormat:@"WiFi網(wǎng)絡(luò):%@\n蜂窩數(shù)據(jù)網(wǎng)絡(luò):%@",wIFIStatus,cellularStatus];textView.text=tvString;}getifaddrs()函數(shù)可以獲取設(shè)備(iPhone)的網(wǎng)絡(luò)接口信息,注意這些信息保存為一個(gè)ifaddrs指針。這是一個(gè)鏈表式的結(jié)構(gòu),每個(gè)ifaddrs都有一個(gè)ifa_next成員,指向下一個(gè)ifaddrs結(jié)構(gòu)體(如果為NULL,表明鏈表結(jié)束)。需要注意的是ifaddrs的ifa_name成員,它指向了接口名,比如我們關(guān)心的兩個(gè)網(wǎng)卡——WiFi網(wǎng)卡和蜂窩數(shù)據(jù)網(wǎng)卡,它們的接口名分別是“en0”和“pdp_ip0”。ifaddrs的sin_addr存放的是接口的地址信息,包括IP地址。這個(gè)IP地址是一個(gè)UInt32值,要轉(zhuǎn)換成我們習(xí)慣的IPv4格式的地址,需要進(jìn)行一些格式轉(zhuǎn)換,這個(gè)過程由ipv4Address:方法完成:-(NSString*)ipv4Address:(UInt32)ipv4{NSString*IPString;if(ipv4!=0){constUInt8*b=(constUInt8*)&ipv4;IPString=[NSStringstringWithFormat:@"%u.%u.%u.%u",(unsigned)b[0],(unsigned)b[1],(unsigned)b[2],(unsigned)b[3]];}returnIPString;}現(xiàn)在,運(yùn)行程序。點(diǎn)擊“檢測(cè)網(wǎng)絡(luò)”按鈕,文本框中將列出iPhone的WiFi網(wǎng)卡和蜂窩數(shù)據(jù)網(wǎng)卡的IP地址。如果有一個(gè)網(wǎng)絡(luò)不可用,則對(duì)應(yīng)網(wǎng)卡將顯示“關(guān)閉”字樣,如圖18-8所示。有時(shí)候在網(wǎng)絡(luò)可用的情況下,我們?cè)L問服務(wù)器仍然會(huì)出現(xiàn)異常,比如服務(wù)器不可用。因此我們也需要對(duì)服務(wù)器連接性進(jìn)行測(cè)試。測(cè)試的方法有許多種。其中一種是HTTP連接測(cè)試,我們可以嘗試連接目標(biāo)服務(wù)器,等待HTTP響應(yīng),查看HTTP是否會(huì)拋出異常,以此來檢查服務(wù)器是否工作正常。但是這樣需要付出一些額外的代價(jià)——這個(gè)測(cè)試的時(shí)間可能很短,也可能很長(zhǎng),因?yàn)槿绻?wù)器工作正常,HTTP會(huì)很快返回服務(wù)器的響應(yīng),但網(wǎng)絡(luò)經(jīng)常會(huì)有延遲,如果服務(wù)器不能正常響應(yīng),則從一個(gè)請(qǐng)求開始到HTTP拋出異常的時(shí)間可能會(huì)很長(zhǎng),如果服務(wù)器遲遲不返回響應(yīng),客戶端必須等待,一直到HTTP響應(yīng)超時(shí),這個(gè)時(shí)間是你在請(qǐng)求時(shí)設(shè)置的超時(shí)時(shí)間。圖18-8檢測(cè)網(wǎng)絡(luò)另一種方法是采用Ping測(cè)試。因?yàn)镻ing一個(gè)服務(wù)器很快,而且Ping采用的是ICMP協(xié)議,可能幾個(gè)毫秒就能得到結(jié)果,相比較HTTP連接測(cè)試而言,這個(gè)消耗可以忽略不計(jì)。因此,我們決定用Ping測(cè)試來檢測(cè)服務(wù)器的連接狀態(tài)。首先將SimplePing和SimplePingHelper類引入到ApnDemo項(xiàng)目。SimplePing是蘋果提供的用于進(jìn)行Ping測(cè)試的一個(gè)實(shí)用工具類,SimplePingeHelper類是它的助手類。當(dāng)然,為了更符合我們的目的,我對(duì)這兩個(gè)類進(jìn)行了一些細(xì)微的修改。主要是在Ping測(cè)試過程中提供了一個(gè)Timeout超時(shí)選項(xiàng)。SimplePingHelper類的使用非常簡(jiǎn)單,在它的接口文件中,只提供了一個(gè)類方法:+ping:target:sel:timeout:,用于對(duì)某個(gè)地址進(jìn)行Ping測(cè)試。注意Ping是異步進(jìn)行的,所以我們需要提供一個(gè)target和selecotr參數(shù),以便在Ping返回結(jié)果時(shí)指定接收的方法。+(void)ping:(NSString*)addresstarget:(id)targetsel:(SEL)seltimeout:(float)second;其中,方法參數(shù)的說明如下:?address:Ping測(cè)試的目標(biāo)地址。address可以是IP地址,也可以是域名。?target和sel:指定Ping返回時(shí),要返回的目標(biāo)對(duì)象和方法。?second:指定Ping測(cè)試的超時(shí)時(shí)間,超過這個(gè)時(shí)間仍未返回則認(rèn)為是Ping不通。提供這個(gè)選項(xiàng)的原因是,由于服務(wù)器性能和網(wǎng)絡(luò)的原因,Ping所需要的時(shí)間往往是不一定的。有的服務(wù)器Ping很短的時(shí)間(幾個(gè)毫秒)就能得到結(jié)果,有的服務(wù)器卻需要更長(zhǎng)的時(shí)間。有時(shí)候second參數(shù)直接影響了Ping測(cè)試的結(jié)果,有時(shí)候?yàn)榱说玫綔?zhǔn)確的結(jié)果,我們需要把second的值設(shè)置得更大。首先我們?cè)赿etectNetwork方法中加入Ping測(cè)試的代碼:[SimplePingHelperping:@""target:selfsel:@selector(pingReturn:)timeout:1];然后實(shí)現(xiàn)Ping測(cè)試的回調(diào)方法pingReturn:方法:-(void)pingReturn:(NSNumber*)success{NSString*ret=success.boolValue?@"成功":@"失敗";textView.text=[NSStringstringWithFormat:@"%@\n====ping:\n\t%@!",textView.text,ret];}注意,pingReturn:方法應(yīng)該帶有一個(gè)NSNumber類型的參數(shù),這個(gè)參數(shù)用數(shù)值1或0表示Ping測(cè)試的結(jié)果:成功或失敗。這樣,當(dāng)我們點(diǎn)擊“檢測(cè)網(wǎng)絡(luò)”按鈕后約1秒后,文本框中將輸出我們對(duì)進(jìn)行Ping測(cè)試后的結(jié)果如圖18-9所示。圖18-9Ping測(cè)試結(jié)果注意:Ping不是測(cè)試服務(wù)器連通性的唯一手段。由于防火墻的存在,某些服務(wù)器往往是禁止被Ping的。這時(shí),你可以考慮使用HTTP連接測(cè)試。18.8Safari阻塞在iPhone上進(jìn)行網(wǎng)絡(luò)檢測(cè),哪怕是網(wǎng)絡(luò)正常的情況下,也會(huì)出現(xiàn)Ping失敗的現(xiàn)象。這其中的一個(gè)原因就是“Safari阻塞”。要想明白什么是“Safari阻塞”,請(qǐng)進(jìn)行下面這個(gè)測(cè)試。測(cè)試只能在真機(jī)上進(jìn)行,你的手機(jī)必須能夠打開蜂窩數(shù)據(jù)網(wǎng)絡(luò),同時(shí)還需要能夠打開WiFi網(wǎng)絡(luò)。如果不具備這個(gè)前提條件,你就無法進(jìn)行下面的測(cè)試。首先,在iPhone上運(yùn)行ApnDemo,點(diǎn)擊“檢測(cè)網(wǎng)絡(luò)”。如果你的WiFi和蜂窩數(shù)據(jù)網(wǎng)絡(luò)都處于打開狀態(tài),那么你將在文本框中得到輸出文字,如圖18-10所示。注意我們的Ping測(cè)試結(jié)果,Ping是成功的。然后切換到Safari,用Safari訪問任何一個(gè)你可以訪問的網(wǎng)址,比如位于辦公網(wǎng)絡(luò)上的某個(gè)服務(wù)器頁面,或者蘋果的首頁。無論是什么,只要這個(gè)頁面是WiFi網(wǎng)絡(luò)可以訪問的。如圖18-11所示。圖18-10第一次網(wǎng)絡(luò)測(cè)試結(jié)果圖18-11通過Safari使用WiFi網(wǎng)絡(luò)注意:如果這個(gè)地址能夠同時(shí)通過WiFi網(wǎng)絡(luò)和蜂窩數(shù)據(jù)網(wǎng)絡(luò)訪問到,則iOS優(yōu)先使用WiFi網(wǎng)絡(luò),以節(jié)省你的蜂窩數(shù)據(jù)流量費(fèi)用。因此,在WiFi打開的情況下,Safari肯定是通過WiFi網(wǎng)絡(luò)對(duì)進(jìn)行訪問的。打開設(shè)置程序,關(guān)閉WiFi網(wǎng)絡(luò)。然后切換至ApnDemo程序,點(diǎn)擊“檢測(cè)網(wǎng)絡(luò)”。出人意料的結(jié)果發(fā)生了,此時(shí)我們的Ping測(cè)試失敗了!如圖18-12所示。圖18-12再次進(jìn)行Ping測(cè)試,結(jié)果失敗了我不知道蘋果怎么解釋這件事情,我把這個(gè)詭異的現(xiàn)象稱為“Safari阻塞”,因?yàn)槲疫€沒有發(fā)現(xiàn)其他人報(bào)告過這個(gè)Bug。如果你還不明白什么是“Safari阻塞”,那么你可以再次按照以下步驟進(jìn)行測(cè)試。?打開WiFi網(wǎng)絡(luò),用Safari訪問WiFi網(wǎng)絡(luò)。?關(guān)閉/不關(guān)閉WiFi網(wǎng)絡(luò)。?再次Ping測(cè)試。注意,第2步“關(guān)閉/不關(guān)閉WiFi網(wǎng)絡(luò)”直接影響了Ping測(cè)試的結(jié)果。如果在第2步中“關(guān)閉”WiFi網(wǎng)絡(luò)進(jìn)行Ping測(cè)試,結(jié)果是失??;而“不關(guān)閉”WiFi進(jìn)行Ping測(cè)試,結(jié)果是成功。此外,如果在Ping測(cè)試之前,將Safari進(jìn)程退出,測(cè)試總是成功的。我個(gè)人的解釋,是Safari的存在會(huì)干擾iOS對(duì)于“WiFi優(yōu)先”策略的執(zhí)行。我不知道如何關(guān)閉Safari,iOS不會(huì)允許你直接關(guān)閉一個(gè)后臺(tái)掛起的進(jìn)程。但是在測(cè)試中我發(fā)現(xiàn)有一點(diǎn),當(dāng)?shù)?次Ping失敗后,后續(xù)幾次Ping又是成功的,可能是第2次,也可能是第3次。我們可以從這一點(diǎn)入手,來解決這個(gè)問題。比如,當(dāng)用戶點(diǎn)擊“監(jiān)測(cè)網(wǎng)絡(luò)”時(shí),我們可以進(jìn)行多次Ping測(cè)試,只要有一次Ping成功,我們就返回。由于為了防止在Ping測(cè)試過程中阻塞主線程,我們采用Dispatch方式進(jìn)行Ping測(cè)試。還是用detectNetwork方法。我們將detecNetwork方法中的代碼修改為:pingSuccess=false;_blockinttimesOfTry=0;//?source=dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD,0,0,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0));//?dispatch_source_set_event_handler(source,^{//?if(pingSuccess==YES||timesOfTry==5){//?dispatch_source_cancel(source);dispatch_release(source);dispatch_source_cancel(timer);dispatch_release(timer);printf("Dispatchstopped.\n");}else{//?timesOfTry++;NSLog(@"%dtimesfortry.\n",timesOfTry);[selfperformSelectorOnMainThread:@selector(ping:)withObject:@""waitUntilDone:NO];}});dispatch_resume(source);//?timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0));//?dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC,0);//?dispatch_source_set_event_handler(timer,^{dispatch_source_merge_data(source,1);});//?dispatch_resume(timer);//⑩提示:在方法中使用了3個(gè)靜態(tài)變量,請(qǐng)?jiān)谶m當(dāng)?shù)牡胤郊尤胱兞康穆暶髡Z句:staticdispatch_source_tsource,timer;staticboolpingSuccess;代碼說明:?聲明了一個(gè)int變量,用于統(tǒng)計(jì)Ping測(cè)試的次數(shù)。由于在塊中會(huì)修改這個(gè)變量,我們使用了_block關(guān)鍵字修飾(請(qǐng)參考第3章3.5節(jié)“塊編程”部分)。?用dispatch_source_create創(chuàng)建一個(gè)dispatch源(請(qǐng)參考第14章14.2節(jié)“并行編程”部分)。?設(shè)置dispatch源的handler。?在handler塊中,我們判斷Ping測(cè)試是否成功,以及Ping的次數(shù)達(dá)到了最大限制(5次)。如果是,我們將取消dispatch源,于是Ping測(cè)試全部結(jié)束。?如果不是,則繼續(xù)調(diào)用ping:方法進(jìn)行Ping測(cè)試。?在設(shè)置好dispatch源source的handler之后,我們用dispatch_resume提交dispatch源。這不會(huì)導(dǎo)致source的handler塊被執(zhí)行。因?yàn)閟ource的類型是DISPATCH_SOURCE_TYPE_DATA_ADD類型,這種類型的dispatch源的handler塊只能通過dispatch_source_merge_data函數(shù)來調(diào)用。?我們?cè)俅斡胐ispatch_source_create創(chuàng)建第2個(gè)dispatch源。與第1個(gè)源不同,這次創(chuàng)建了一個(gè)定時(shí)器源timer。?定時(shí)器源比較特殊的是必須設(shè)置它的定時(shí)器,我們用dispatch_source_set_timer將timer的定時(shí)器運(yùn)行周期設(shè)置為1秒鐘。?接下來設(shè)置定時(shí)器源的handler。handler中僅有代碼“dispatch_source_merge_data(source,1);”一句,它會(huì)根據(jù)source的類型提交handler塊。由于source是DISPATCH_SOURCE_TYPE_DATA_ADD類型,所以這句代碼將以ADD的方式合并提交source的handler塊。⑩提交dispatch源timer。這會(huì)導(dǎo)致定時(shí)器立即運(yùn)行。于是每間隔1秒,source源的handler塊被調(diào)用一次(進(jìn)行一次Ping測(cè)試),直到Ping返回成功或達(dá)到5次。ping:方法用于進(jìn)行Ping測(cè)試,方法定義如下:-(void)ping:(NSString*)address{[SimplePingHelperping:addresstarget:selfsel:@selector(pingReturn:)timeout:1];}當(dāng)Ping有結(jié)果(成功或失?。┓祷貢r(shí),pingReturn:方法被調(diào)用。pingReturn方法簡(jiǎn)單判斷Ping成功與否,然后調(diào)用tvLog:方法進(jìn)行輸出:-(void)pingReturn:(NSNumber*)success{pingSuccess=success.boolValue;NSString*ret=pingSuccess?@"成功":@"失敗";[selftvLog:@"====ping:%@!",ret];}tvLog:方法僅僅是輸出文本到UITextView控件,但它的參數(shù)有一點(diǎn)點(diǎn)特殊,它有一個(gè)可變參數(shù),可變參數(shù)是個(gè)數(shù)不定的參數(shù)列表(請(qǐng)參考第3章3.6節(jié)“可變參數(shù)”部分)。-(void)tvLog:(NSString*)fmt,……{va_listargs;va_start(args,fmt);NSString*string=[[[NSStringalloc]initWithFormat:fmtarguments:args]autorelease];va_end(args);if(textView.text&&textView.text.length>0){string=[NSStringstringWithFormat:@"%@\n%@",textView.text,string];}objc_msgSend(textView,@selector(setText:),string);NSRangerange=NSMakeRange(textView.text.length,0);objc_msgSend(textView,@selector(scrollRangeToVisible:),range);}tvLog方法很像NSLog方法。它也具有兩個(gè)參數(shù),第1個(gè)是NSString,是一個(gè)帶百分號(hào)的格式字符串;第2個(gè)是一個(gè)省略號(hào)……,代表一個(gè)可變參數(shù)??勺儏?shù)代表了一個(gè)個(gè)數(shù)未知的參數(shù)列表,此外我們也無法獲知它的參數(shù)類型。因此使用tvLog方法時(shí),我們完全可以用使用NSLog一樣的方法,例如:[selftvLog:@"%d%@",123,@"copies"];從“va_listargs:”一行到“va_end(args);”一行,我們將可變參數(shù)按照格式字符串fmt指定的格式組裝出一個(gè)完整的文本。注意,NSString有一個(gè)初始化方法initWithFormat:arguments:方法,它的第2個(gè)參數(shù)允許指定一個(gè)va_list類型(可變參數(shù)列表),這樣它會(huì)自動(dòng)計(jì)算第1個(gè)格式化字符串參數(shù)中%號(hào)的個(gè)數(shù)來作為可變參數(shù)的個(gè)數(shù),同時(shí)配合第2個(gè)參數(shù)(可變參數(shù))來初始化一個(gè)指定格式的字符串。類似的還有NSLog(NSString*format,va_listargs)函數(shù),也使用了同樣的技術(shù)。然后將文本追加到UITextView文本的最后。然后滾動(dòng)UITextView的文本區(qū)域。注意,我們使用ojbc_msgSend函數(shù)來調(diào)用UITextView的相應(yīng)方法,是因?yàn)闊o論修改UITextView的text屬性還是滾動(dòng)文本視圖,都需要刷新UI。刷新UI應(yīng)該在主線程中進(jìn)行,如果你不使用objc_msgSend方法,那么就得用performSelectorOnMainThread方法。但是performSelectorOnMainThread方法無法直接傳遞NSRange這樣的簡(jiǎn)單參數(shù)(必須是NSObject類型的參數(shù))。運(yùn)行ApnDemo程序,點(diǎn)擊“檢測(cè)網(wǎng)絡(luò)”,如果Ping測(cè)試不成功,則會(huì)進(jìn)行多次Ping測(cè)試,直到Ping成功,如圖18-13所示。圖18-13程序最終運(yùn)行結(jié)果18.9本章小結(jié)為了滿足企業(yè)對(duì)網(wǎng)絡(luò)安全和信息安全的實(shí)際要求,使用企業(yè)APN(APN專線)是一種很常見的做法。對(duì)于企業(yè)移動(dòng)應(yīng)用來說,業(yè)務(wù)系統(tǒng)位于企業(yè)內(nèi)網(wǎng),使用APN將它和互聯(lián)網(wǎng)進(jìn)行隔離是非常必要的,很難有其他可以替代的手段。但是,由于iOS的種種限制,對(duì)于某些不能通過設(shè)置程序修改APN配置的用戶來說,只能通過安裝配置描述文件的方式來進(jìn)行APN的切換。本章介紹了一個(gè)以編程方式實(shí)現(xiàn)的修改iPhoneAPN設(shè)置的解決方案,即一個(gè)簡(jiǎn)單的APN切換工具(同時(shí)它也提供了簡(jiǎn)單的網(wǎng)絡(luò)狀態(tài)檢測(cè))。你可以把它集成在自己的應(yīng)用程序中。雖然它很簡(jiǎn)單,但涉及了廣泛的內(nèi)容,諸如后臺(tái)任務(wù)、配置描述文件、BSDSocket編程、網(wǎng)絡(luò)檢測(cè)、Safari阻塞和并行編程GCD(GrandCentralDisptach)。第19章iOS企業(yè)應(yīng)用實(shí)戰(zhàn)本章中,我們討論一個(gè)綜合網(wǎng)絡(luò)應(yīng)用案例——AnyMail。這是一個(gè)iPhone上的簡(jiǎn)單郵件客戶端,它可以讓你通過iPhone接收服務(wù)器上存放的郵件,也可以讓你用iPhone向其他人發(fā)送郵件。這只是一個(gè)虛擬的項(xiàng)目,其服務(wù)器端代碼(是用Java實(shí)現(xiàn)的)被大大簡(jiǎn)化了,這是有意的,因?yàn)槲覀儾粦?yīng)該沖淡本書主題——iOS應(yīng)用開發(fā)。19.1應(yīng)用場(chǎng)景與功能概述AnyMail完全依靠于iPhone的網(wǎng)絡(luò)通信能力。如果你想在iPhone上測(cè)試這個(gè)程序,那么需要打開iPhone的蜂窩數(shù)據(jù)網(wǎng)絡(luò)或WiFi。因?yàn)锳nyMail隨時(shí)可能請(qǐng)求服務(wù)器的數(shù)據(jù)。AnyMail需要實(shí)現(xiàn)如下功能:郵箱登錄、查看郵件、查看附件、發(fā)送郵件、寫郵件、從服務(wù)器中獲取聯(lián)系人列表。當(dāng)然服務(wù)器端也必須實(shí)現(xiàn)相應(yīng)的接口。注意:在這個(gè)案例中,服務(wù)器端的代碼被盡可能地簡(jiǎn)化了,我們甚至沒有使用到數(shù)據(jù)庫,而實(shí)際開發(fā)中這幾乎是不可能的。在實(shí)際項(xiàng)目中,服務(wù)端的代碼要復(fù)雜得多,但本書只能把重心放到iPhone端。19.2應(yīng)用程序架構(gòu)應(yīng)用程序架構(gòu)為C/S架構(gòu)。客戶端和服務(wù)器通過HTTP協(xié)議進(jìn)行通信。服務(wù)器端采用Tomcat和Java實(shí)現(xiàn),客戶端為iOS。服務(wù)器端代碼位于光盤“source/第19章/test”目錄下,客戶端代碼位于光盤“source/第19章/AnyMail”目錄下。19.3服務(wù)器端服務(wù)器端代碼為Java實(shí)現(xiàn)。下面我們簡(jiǎn)單介紹服務(wù)器端代碼,熟悉Java開發(fā)的讀者可以跳過這部分內(nèi)容不讀,直接使用作者已經(jīng)簡(jiǎn)單實(shí)現(xiàn)的服務(wù)器端代碼就可以了。服務(wù)器端的代碼是一個(gè)完整的EclipseJ2EE項(xiàng)目,你可以直接在Eclipse中打開它,或者直接在Tocmat服務(wù)器上部署它。19.3.1環(huán)境搭建在本章中,服務(wù)器環(huán)境是Tomcat。因此你需要在機(jī)器上安裝JDK和Tomcat。同時(shí)還需要安裝一個(gè)Eclipse作為開發(fā)環(huán)境。MacOSX默認(rèn)已經(jīng)安裝了JDK1.6。因此你只需要安裝一個(gè)Tomcat和一個(gè)EclipseIDE就行,以下是下載地址。Tomcat:/download-70.cgiEclipse:/downloads/19.3.2實(shí)現(xiàn)登錄接口為求簡(jiǎn)便,登錄接口用一個(gè)JSP頁面實(shí)現(xiàn)簡(jiǎn)單驗(yàn)證。只要用戶名和密碼不為空,我們就驗(yàn)證為登錄成功。打開Eclipse,新建Web項(xiàng)目。新建JSP頁:Login.jsp<%Stringuser=request.getParameter("user");Stringpass=request.getParameter("pass");out.println("<?xmlversion=\"1.0\"?>");if(user!=null||pass!=null){out.println("<login><status>true</status><user_id>007</user_id></login>");}else{out.println("<login><status>false</status></login>");}%>運(yùn)行Web程序,在瀏覽器中輸入:http://localhost:8080/AnyMail/login.jsp?user=&pass=回車,服務(wù)器將返回XML:<?xmlversion=“1.0"?><login><status>true</status><user_id>007</user_id></login>提示:請(qǐng)求參數(shù)"?user=&pass="并不會(huì)使user和pass為空,而是表示user和pass為空字符串“”。通過request.getParameter獲取到這兩個(gè)參數(shù)時(shí),會(huì)得到空字符串,但字符串本身不會(huì)為空。19.3.3實(shí)現(xiàn)企業(yè)通訊簿接口企業(yè)通訊簿接口用directory.jsp實(shí)現(xiàn)如下:<%Stringuser=request.getParameter("user");Stringpass=request.getParameter("pass");out.println("<?xmlversion=\"1.0\"encoding=\"utf-8\"?>");if(user!=null||pass!=null){out.println("<list><deptname=\"行政部\"id=\"01\"><linkmanid=\"001\"name=\"郭書全\"></linkman></dept>");out.println("<deptname=\"人力部\"id=\"02\"><linkmanid=\"002\"name=\"王有福\"></linkman></dept></list>");}else{out.println("<login><status>false</status></login>");}%>作為一個(gè)Demo,企業(yè)通訊簿接口直接返回了靜態(tài)HTML數(shù)據(jù)。實(shí)際上,你應(yīng)該從企業(yè)數(shù)據(jù)庫中獲得企業(yè)通訊簿數(shù)據(jù)。19.3.4實(shí)現(xiàn)收件箱接口企業(yè)通訊簿接口用inbox.jsp實(shí)現(xiàn),代碼如下:<%Stringuser=request.getParameter("user");Stringpass=request.getParameter("pass");out.println("<?xmlversion=\"1.0\"encoding=\"utf-8\"?>");if(user!=null||pass!=null){out.println("<list><mail><title>測(cè)試郵件1</title>");out.println("<id>01</id><content>test</content>");out.println("<sender>王有福</sender><attachid>0001</attachid>");out.println("<time>2011-11-11</time></mail></list>");}else{out.println("<login><status>false</status></login>");}%>作為一個(gè)Demo,收件箱接口直接返回了靜態(tài)HTML數(shù)據(jù)。實(shí)際上,你應(yīng)該從郵件服務(wù)器或數(shù)據(jù)庫中獲得收件箱郵件數(shù)據(jù)。19.3.5實(shí)現(xiàn)附件上傳接口附件上傳接口使用第7章7.4.3節(jié)“文件上傳”中介紹的UploadServlet類實(shí)現(xiàn)。注意:在文件系統(tǒng)中要確定文件上傳目錄的存在。即在root下新建文件夾data,并在data目錄下建立temp目錄(Apachefileupload組件的臨時(shí)文件目錄)。19.3.6實(shí)現(xiàn)附件下載接口附件下載由download.jsp頁面實(shí)現(xiàn),代碼如下:<%Stringuser=request.getParameter("user");Stringpass=request.getParameter("pass");Stringattachid=request.getParameter("attachid");out.println("<?xmlversion=\"1.0\"?>");if(user!=null||pass!=null){out.print("<attach><attachid>0001</attachid><file_size>219169</file_size>");out.print("<file_name>0064.jpg</file_name>");out.print("<url>http://localhost:8080/AnyMail/0064.jpg</url>");out.print("</attach>");}else{out.print("<login><status>false</status></login>");}%>注意:在Web目錄中放一個(gè)用于示范的下載文件0064.jpg。作為一個(gè)Demo,附件下載接口直接返回一個(gè)圖片文件的URL地址。實(shí)際上,你應(yīng)該從郵件服務(wù)器或數(shù)據(jù)庫中獲得這個(gè)URL,或者直接返回文件流。19.4iPhone客戶端接下來介紹iPhone客戶端的實(shí)現(xiàn),這是本章重點(diǎn)關(guān)注的內(nèi)容。iPhone客戶端代碼是一個(gè)Xcode項(xiàng)目,你可以直接在Xcode中打開它。19.4.1實(shí)現(xiàn)登錄本書的第7章“網(wǎng)絡(luò)”和第8章“XML和JSON”與本章有密切聯(lián)系。本例需要使用第7章中介紹的ASIHttpRequest框架。新建EmptyApplication,并在項(xiàng)目中引入ASIHttpRequest框架(需要導(dǎo)入相關(guān)依賴庫,請(qǐng)參考第7章相關(guān)內(nèi)容)。本例需要使用第8章中介紹的GDataXML框架解析XML,請(qǐng)將GDataxMLNode.h和GDataXMLNode.m文件添加到項(xiàng)目目錄(需要libxml庫)。此外,我們使用了第7章中自定義網(wǎng)絡(luò)模塊NetworkModule和PostRequest。打開光盤“source/第8章/testGData”目錄,將“NetworkModule”目錄下的所有文件加入項(xiàng)目中,如圖19-1所示。圖19-1NetworkModule目錄中包含的文件注意:根據(jù)本章實(shí)戰(zhàn)項(xiàng)目的實(shí)際情況,我們對(duì)PostRequest類和NetworkModule類做了一些改動(dòng),主要是PostRequest類的postURLWithDelegate方法和NetworkModule類的postURL方法。請(qǐng)自行參考源代碼。首先我們來創(chuàng)建解析器。登錄接口中只有一個(gè)XML文件,使用第8章中介紹的GDataXML和XMLNode類,我們很容易就創(chuàng)建這個(gè)XML文件的解析器loginXML,你可以在xmlObjects文件夾下看到它。它有一個(gè)實(shí)例化方法:initWithXMLDocument:以及兩個(gè)String屬性status和user_id。@property(retain,nonatomic)NSString*status;@property(nonatomic,retain)NSString*user_id;-(id)initWithXMLDocument:(GDataXMLDocument*)doc;由于使用了SynthesizeSingleton的單例模式(在第7章專門介紹過),我們也聲明了一個(gè)單例方法:+(loginXML*)sharedloginXML;提示:這個(gè)單例方法不需要實(shí)現(xiàn)。聲明就行。在實(shí)現(xiàn)中,只有一個(gè)initWithXMLDocument:方法的實(shí)現(xiàn),如下所示:-(id)initWithXMLDocument:(GDataXMLDocument*)doc{self=[superinit];if(self){if(doc&&doc.rootElement!=nil){XMLNode*xmlNode=[[[XMLNodealloc]init]autorelease];xmlNode.element=doc.rootElement;for(GDataXMLNode*eachindoc.rootElement.children){NSString*label=[GDataXMLNodelocalNameForName:];objc_property_tproperty=class_getProperty([selfclass],[labelcStringUsingEncoding:NSUTF8StringEncoding]);if(property){NSString*text=each.stringValue;[selfsetValue:textforKey:label];由于使用了Objective-C的運(yùn)行時(shí)庫,上面的代碼顯得很簡(jiǎn)潔,而且復(fù)用性很高,此后你會(huì)發(fā)現(xiàn),我們會(huì)在后續(xù)實(shí)現(xiàn)中大量重復(fù)使用這段代碼。它會(huì)自動(dòng)把XML元素的標(biāo)簽名和類聲明中的屬性名進(jìn)行匹配,以后對(duì)于不同的XML文件解析類,我們只要根據(jù)XML文件中的標(biāo)簽名來聲明屬性就可以了。接下來我們對(duì)登錄界面loginVC.xib做簡(jiǎn)單設(shè)計(jì)(如圖19-2所示)。}}}}returnself;}圖19-2登錄界面設(shè)計(jì)進(jìn)行必要的連接。即將兩個(gè)TextField連接到兩個(gè)IBOutlet,將Button的touchUpInside事件連接到動(dòng)作-(IBAction)loginAction。在下面的代碼中,我們使用了網(wǎng)絡(luò)模塊NetworkModule類向服務(wù)器進(jìn)行登錄:-(IBAction)loginAction:(id)sender{NSString*url=@"http://localhost:8080/AnyMail/login.jsp?user=%@&pass=%@";url=[NSStringstringWithFormat:url,tfName.text,tfPass.text];[[NetworkModulesharedNetworkModule]postURL:urltag:kBusinessTagUserLoginowner:self];}總共3行代碼,復(fù)雜邏輯都被封裝到了NetworkModule類里。接下來實(shí)現(xiàn)NetworkModuleDelegate協(xié)議:-(void)endPost:(GDataXMLDocument*)resultbusiness:(kBusinessTag)tag{if(tag==kBusinessTagUserLogin){loginXML*login=[loginXMLsharedloginXML];[logininitWithXMLDocument:result];if([@"true"isEqualToString:login.status]){[loginsetPass:tfPass.text];[loginsetUsername:tfName.text];InboxVC*vc=[[[InboxVCalloc]init]autorelease];[self.navigationControllerpushViewController:vcanimated:YES];}else{showMessage(@"",@"登錄失?。?);}}}登錄成功,我們跳轉(zhuǎn)到收件箱頁面。19.4.2查看收件箱收件箱頁面由InboxVC實(shí)現(xiàn)。它顯示了收件箱的郵件列表,由一個(gè)TableView構(gòu)成。TableView上的單元格是我們自定義的InboxCell類(繼承自UITableViewCell)。我們先講InboxCell類的實(shí)現(xiàn)。選擇NewFile,新建類InboxCell,繼承自UITableViewCell。選擇NewFile,“新建iOS→UserInterface→View”,命名為InboxCell。這將生成一個(gè)InboxCell.xib文件。編輯InboxCell.xib,將View對(duì)象的Idenifier修改為InboxCell,Size修改為freedom。提示:Xcode會(huì)提示一個(gè)警告,大意為只有Xcode4.2以上版本支持freedom,默認(rèn)的xib文件是Xcode4.1的。選擇InboxCell.xib文件,打開它的FileInspector面板,找到Development一行,將其修改為Xcode4.2,警告消除。在InboxCell.xib中設(shè)計(jì)單元格的布局,放入幾個(gè)UILabel。最終結(jié)果如圖19-3所示。創(chuàng)建必要的連接。編輯InboxCell.h聲明兩個(gè)方法:圖19-3InboxCell的UI設(shè)計(jì)+(InboxCell*)getCell;-(v

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論