iOS混合應用開發入門
介紹
上周(譯者:原文成于2012.07.06),紐約時報透露說Facebook一直在致力于對其iOS應用進行重大升級。這個事實本身沒有什么新聞價值。Facebook當然一直在致力于對其iOS應用進行重大升級。但是,這次的升級相當有新聞價值。就如何構建和維護越來越多的移動應用套件而言,Facebook正在計劃一個意義重大的航線修正(譯者:技術轉型)。
到目前為止,Facebook公開的移動策略是為了避免「重復寫四次相同代碼」(之后會更多),而發布混合應用 (hybrid apps)到各主流 平臺。理論上講,這個想法很完美:當你可以為相同的功能只維護一個代碼庫的時候,誰愿意為此維護多個呢?但是,實際的情況是,Facebook的應用在任 何平臺上都沒有「原生」的感覺,同時也被嚴重的性能問題所折磨,并普遍的被其用戶所辱罵。
這篇文章將會對混合應用的主題進行更深入的探討。我們將會以講解混合應用的構成做為開始。然后,我們將會看下混合應用能帶來的優勢,并且也會提供更 多消極方面的細節。我們將會檢驗一些可以使混合應用在感覺上更加像一個原生應用的選項,然后給你提供一些用來優化性能,外觀以及體驗的建議。最后,我們會把注意力轉回到Facebook上,為了理解他們是如何成為今天這個樣子的,我們將會更細致的查看他們iOS應用的歷史。
從個人的角度來說,我不會為iOS構建一個混合應用,除非我必須這么做不可。我不認為混合應用在iOS上表現良好: 它們比較慢,也比較笨拙,并且從 來沒有原生應用那樣的外觀和體驗。但是,在合適的場景下,它們所能提供的優勢能抵掉它們的劣勢。為了學習關于混合應用的所有內容,什么是能做的,什么是不 能做的,并且它們是否滿足你的需求,請繼續往下讀。
什么是混合應用?
通常來說,一個iPhone應用是利用Cocoa Touch框架以純Objective C來構建的。你可能會有一個UITabBarController,上面安放了一些視圖控制器(view controllers)。這些視圖控制器可能是UITableViewController的子類,或者也有可能是UIViewController, 其UI是使用XIB所定義或者在代碼中定義。有時候,一個視圖控制器上可能會安放一個UIWebView控件,用來在一個應用的內部展示web內容或長文本內容。
混合應用用HTML,CSS和Javascript而不是Objective-C來實現部分或者所有的客戶端代碼。一個特定應用的屏幕實際上可能包含一個UIWebView,用它來渲染服務端返回的標記語言(譯者:即HTML)并且 解釋服務端返回的代碼(譯者:JavaScript)而不是Objective C。
一些應用,比如像Instapaper和Pocket,是高度依賴web視圖來實現其關鍵的應用功能的。 Instapaper和Pocket對 web視圖的使用采取了一個務實的方案:兩個應用的主函數都是展示重新格式化的web內容,所以只用web視圖來展示HTML是有道理的。
就像在下面的類型列表中即將看到的那樣,Facebook盡可能的在它們的iOS應用中使用HTML,其本質上是把http://touch.facebook.com的一個版本填充進了一個Objective C的殼里邊。它們在一些限定的應用特性中使用Objective C,比如登錄窗口和它們曾經獨一無二的滑出菜單界面。
最后,還存在可以添加到iPhone主屏的移動站點。這些站點是100%用HTML構建的,并且特定的運行在移動版Safari中。
應用的類型應用類型
類型
|
例子
|
完全原生
|
Path 2.0和其他完全利用Cocoa Touch框架用Objective C所實現的應用。
|
一些web視圖
|
Instapaper和Pocket都是使用web視圖來實現關鍵的應用功能,但是其他的功能是用Objective C來實現的。
|
最小化Objective C
|
比如Facebook。盡可能的使用HTML/JS/CSS來實現的應用,并且只有很少的組件是原生的。
|
純HTML
|
就像它描述的那樣
|
混合應用的優勢
為什么你想要創建一個混合應用?這兒有一些原因,比如產品開發和更新的速度,輕松的進行A/B測試,以及通過讓前端web開發人員加入到iOS開發中,可以增加可用的開發人員數量。
開發速度
盡管開發一個外觀完美,行為原生的移動web體驗是不太可能一夜完成 的,但是在較短的時間內開發一個合宜的體驗相對來說是比較簡單的。開發一個得體的web體驗,并且把它加載到一個UIWebView中肯定比開發一個 JSON API,并用Objective C來寫一些前端代碼要容易。由于這個原因,一些開發者可能比較喜歡用HTML來交付那些用來檢驗想法的功能,然后接下來當功能的價值被證實了的時候再把功 能的代碼替換成原生代碼。
投入市場的時間是公司之間的關鍵區分點,發布日期延遲幾周或者甚至幾個月對于資源受限(limited runway)的創業公司來說這可能意味著生與死的區別。
發布更新的速度
通過HTML來提供產品功能的另一個優勢是你可以在不向蘋果發起 應用更新請求的情況下去更新你應用的功能集合,并且不需要等待蘋果幾周的審批過程。 快速的進行改變,修復問題以及能擴展你應用的功能,這可以為你提供一個用來超越對手的重要競爭優勢,你的競爭對手由于每個小的改動都需要經過蘋果的審批過 程,所以它們的靈活性就比較低。
早上想要的新功能,這天結束的時候就可以發布,并且晚上就可以觀測用戶的使用情況,以便第二天早上就可以對其進行優化,調整或者刪除,這是對體驗進行打磨非常強大的方法,而體驗恰恰是你的應用可以在市場上取得成功的必需條件。
A/B測試
與開發的速度和發布更新的速度都相關的是快速的對用戶進行A/B測試或者分組測試的 能力。比如,你可能想測試一下你應用上的新feed功能,在其自身包含一個Like按鈕的情況下和只在feed內容詳情頁面包含這個按鈕的情況,用戶使用 率是否有所提高。用HTML來實現feed功能,你可以很容易的對這個更改進行區分測試,為了從統計數據上來看這個重要的用戶行為是否發生,可以只在一組 用戶中展示這個按鈕。
在原生代碼中執行A/B測試不是不可能的,但是準備測試用例會更加具有挑戰性,并且對比一個混合應用來說,從測試用例中返回對應的新數據,這個過程將會顯著地耗費更多的時間。如果你對如何在Objective C中實現A/B測試好奇,Little Big Thinkers上有一篇好文章展示了它們如何在iPhone上進行A/B測試(譯者:翻墻)。
App開發的民主方式
如果你曾經嘗試過雇傭一個iOS開發者(或者如果你是一個 iOS承包商),你知道對靠譜iOS開發者的需求與可雇傭的人來說早就已經是供不應求。混 合應用可以大大的增加你所需要的靠譜開發者數量,因為你可以讓你任何的前端開發人員轉做你iOS應用中的功能開發,假設他們已經熟悉 Javascript,HTML,CSS和你服務器上使用的任何后端語言。
混合應用的劣勢
當然,如果混合應用沒有缺點,沒人想開發原生應用。現實中顯然不是這種情況,并且這里有幾個原因解釋了為什么會這樣。概括來說,混合應用較慢,較臃腫,外觀看起來沒有原生的感覺,并且,最重要的是,沒有原生的體驗。
Nitro的缺失
Nitro是移動Safari的Javascript引擎,速度太TMD快了。不幸的是,由于安全的關系,UIWebViews無法享受到這個速度提升帶來的優勢:
Nitro 比WebKit之前的JavaScript引擎性能有所提升的最大原因是其采用了JIT-”Just-In-Time” 編譯… JIT需要可以把RAM中的內存頁標記為可執行狀態的能力,但是,iOS,出于安全的考量,不允許內存頁被標記為可執行狀態。這是一個重要并且嚴格的安全 策略。大部分現代操作系統允許內存頁被標記為可執行狀態-包括Mac OS X,Windows,和(我相信)Android。iOS 4.3對這個策略有個例外,但是這個例外只限于移動Safari。
實際上來說,這意味著如果你的混合應用使用了Javascript,那么你將會感覺到同樣的UI要比在移動 Safari中要慢,也比以 Objective C實現的同樣的UI要慢。不幸的是,這兒沒有簡單的方法來解決這個問題,除了減少或者全部移除UIWebViews中你所使用的Javascript之 外,盡可能的以CSS3動畫(animations)和過渡(transitions)來做為替代(這可以利用GPU加速的優勢)。
為了弄清楚,你可以只使用CSS3來完成一些真實的不可思議的效果,就像剛才Hakim El Hattab在他的stroll.js項目所演示的那樣。但是,總的來說,Nitro的缺失是任何混合iOS應用成功的嚴重障礙,特別是如果想獲得與原生iOS應用同樣的外觀和體驗。
模仿原生UI的挑戰
除了上面描述的性能問題之外,模仿原生Cocoa Touch用戶界面的挑戰是實現一個混合應用所面對的另一個困難。就像之前被指出過無數次,iPhone應用擁有非常不同尋常的外觀和體驗。比如,Table cell擁有標準的字體大小,內邊距和空白要求,gradient selection高亮,disclosure箭頭,以及許多其他很難被復制的特性。
一個替代的方式是放棄對iOS系統控件的復制,相反為你的應用構建一個獨一無二的UI,就像Facebook的 News Feed和Timeline功能那樣。但是,即使你選擇了這個方法,為了使你的混合應用在感覺上不像一個web頁面,這兒還有一些WebKit特性需要被 解決。
有時候事情很容易變糟
預計遲早有一天,你混合視圖中的CSS或者 Javascript文件將會加載失敗,這不是沒有理由的。可能你的用戶是紐約或者舊金山的 AT&T的客戶,眾所周知他們使用的是不穩定的GSM網絡。可能上帝今天僅僅是沒對你笑而已。不管怎么樣,終有一天你將會給用戶展示一個像這樣的 UI:
…并且最糟糕的是你對于這種情況幾乎做不了任何事情,除了把你的JS和CSS都混入HTML頁面中。這種情況對于你和你的應用都是極為糟糕的,并且它是完全無法控制的。至少對一個完整的原生應用來說,服務端錯誤可以通過一個彈出的警告來更加優雅的進行處理。 (截屏來自@timanrebel,社會化滑雪和滑雪板應用,Snowciety的創始人。)
如何使你的混合應用擁有原生的體驗UIWebView的陰影和背景顏色
默認情況下,UIWebViews會顯示一個陰影,以及背景顏色或者一個位于渲染頁面下面的圖案(取決于iOS版本和硬件)。這個外觀和感覺明顯是非原生 的,所以你將會需要移除它們兩個。幸運的是,這么做很簡單:為了使視圖變得扁平(flat),所有你需要做的事情就是一個UIWebView子類里邊的幾 行代碼。check out我的那個MIT協議的項目,FlatWebView,看看例子里邊是如何做的。
鏈接的高亮顯示
為了幫助用戶知道他們究竟觸碰過哪里,當鏈接被觸碰的時候WebKit會在其周圍高亮的顯示一個半透明的矩形。
盡管這對于web來說很便捷,但是它在感覺上明顯不是原生的。為了清除這個行為,你可以在你的web應用中包含這個CSS片段:
- /*http://stackoverflow.com/questions/9157080/wrong-webkit-tap-highlight-color-behavior-when-page-as-web-standalone-app */
- html {
- -webkit-tap-highlight-color: rgba(0,0,0,0);
- -webkit-user-select: none;
- }
觸摸延遲
默認情況下,當 WebKit對web頁面上的被觸摸的鏈接進行響應的時候,它會增加一個大約300毫秒的延遲。這實際上是一個特性,不是一個 bug,因為意外觸碰一個web頁面上的錯誤鏈接是特別常見的,所以擁有一個細微的延遲可以給用戶提供一個修正他們錯誤的機會,對于什么都不做來說這可以 創造更好的體驗。無論如何,當你正在創建一個混合應用的時候,你應該只使用觸碰目標(hit target)尺寸大得可以避免被意外觸碰的UI組件。(你正在使用最小尺寸為40×40px的大尺寸觸碰目標,對嗎?)
為了確保你應用的UI在感覺上和Cocoa Touch的UI擁有一樣的響應體驗,當你在創建一個混合應用的時候你必須禁用WebKit的觸摸延遲的特性。Matteo Spinelli提供了一些方便的Javascript代碼(不是jQuery!)用來解決這個「問題」:Remove onclick delay on Webkit for iPhone.
滾動
過去創建一個混合應用最大的挑戰之一就是在一個UIWebView中復制原 生的滾動行為。特別是想要在UIWebView中的UIScrollView子類中去復制滾動速度,慣性以及「橡皮筋」(rubberbanding)視 覺效果,這幾乎是不可能。幸運的是,蘋果在iOS 5中為UIWebView增加了一個scrollView屬性,這使得對上面這些功能的支持是小菜一碟。
你所需要做的就是把你web視圖上的滾動視圖的decelerationRate屬性設置為UIScrollViewDecelerationRateNormal:
- UIWebView *hybridView = [self somethingThatGetsOurWebView];
- webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
Objective C到Javascript
為了在原生代碼發生改變的時候去更新一個web視圖,你擁有三個選擇:
- 重新加載UIWebView的內容。
- 使用NSURLRequest加載一個新頁面。
- 在你的UIWebView中使用-stringByEvaluatingJavaScriptFromString:這個API來執行Javascript代碼。
重新加載@UIWebView@中的內容-或者加載一個全新的頁面-這糟透了,因為這將會在一個無法忽視的時間內展示給你一個空白,無法操作的頁面。你可以一直顯示一個loading HUD視圖(比如SVProgressHUD),但是這仍然不是一個非常好的體驗。
在UIWebView中執行Javascript代碼來更新視圖是一個較好的選擇:比如,這給你提供了執行部分更新的機會。不過,它也有它自己的挑戰,比如極其糟糕的調試體驗,當一些功能不能正常工作的時候你就會感受到了。
Javascript到Objective C
為了在當你的web視圖發生改變 的時候去更新原生代碼,與iOS打賭(譯者:交互)的最佳方式是實現UIWebView的委托方法 -webView:shouldStartLoadWithRequest:navigationType:。為了可以從UIWebView中調用原生平 臺的特性,你可以定義自己的scheme(比如用myappname://代替http://)和自己的URIs(比如用 showImageCapture代替apple.com)。不幸的是,這個方案即使對于復雜程度不高的應用來說也會退化為一個龐大的if區塊。
比如說Facebook可能像這樣來實現他們的委托方法:
- if ([request.url.scheme isEqual:@"showImagePicker"]) {
- // show a UIImagePickerController
- } else if ([request.url.scheme isEqual:@"sendMessage"]) {
- // show the send message view controller
- } else if ([request.url.scheme isEqual:@"checkIn"]) {
- // show the places checkin view controller
- } else if ([request.url.scheme isEqual:@"postStatus"]) {
- // show the post status view controller
- }
幸運的是,這里有一些方法可以簡化它。比如,你可以在某種點對點的調度系統中使用一個NSDictionary來對scheme到action進行路由。
- - (void)viewDidLoad
- {
- self.dispatch = [NSMutableDictionary dictionary];
- [dispatch setObject:[NSValue valueWithPointer:@selector(showImagePickerForURL:)] forKey:@"showImagePicker"];
- }
- - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
- {
- NSValue *selectorPointer = [self.dispatch objectForKey:request.url.scheme];
- if (selectorPointer)
- {
- [self performSelector:[selectorPointer pointerValue] withObject:request.url];
- return NO;
- }
- else
- {
- return YES;
- }
- }
當Facebook的iPhone應用首次亮相的時候,它幾乎是個完整的原生應用,是由Joe Hewitt單槍匹馬寫出來的(據我所知),他把Three20 iOS框架從應用的核心中提取了出來。如果你曾經用Three20開發過應用,你知道這個框架有一點笨拙。可能,在Joe退出iOS開發團隊之后的一段時間,Facebook的領導們判斷出當前應用的實現簡直是太難維護了,是時候重新考慮方案了。
Dave Fetterman,Facebook平臺的工程經理,他在去年F8大會一個演講中的部分篇幅描述了這個突變:
由于一些根本原因你需要為四個不同的平臺開發應用。什么?你想為所有的這些平臺各自開發?好吧,那你將會像SB一樣開發四次。然后每個平臺上都有了所有的特性-群組,交易,新的profile。但是這么做實在是太遭了。這么說來,我們不得不開發四次,這意味著開發的速度變慢了。代碼也變得陳 舊了。這里會存在不能一起工作的不同版本的等價功能,這對于像Facebook這樣快速發展的公司來說太糟糕了。
所以,當Facebook4.0的iOS版本發布的時候, 它體現出了”Write Once, Run Anywhere”的思想。就個人來講,我必須說,我認為在理論上這是一個偉大的想法。沒人想開發和維護相同的功能集合四次,特別是像Facebook這 樣的公司,它擁有驚人的高「用戶-工程師」比例。很不幸,真實的情況是UIWebView并沒有足夠的能力來處理像Facebook這樣的富應用的需求, 用戶的眼睛是雪亮的:
總結
綜上所述,混合應用在iOS上的體驗不太好:它們較慢,也較笨拙,可能引起一些無法修復的錯 誤,并且它們在感覺上并不能和原生應用相比。但是,它們 所提供一些優勢在特殊的境況之下,可能是一個有價值的折中方案。個人來講,我建議你應該一直用原生代碼來開發你整個iOS應用的用戶界面,然后根據需求所 要求的那樣有選擇的把應用中的部分功能改造成混合方式,不管需求是純開發速度,即刻更新的能力,或者在很短的時間內在用戶身上去測試功能的多個變化。
最后,無論怎樣,對你的用戶和你的業務來說,只有你知道什么才是真正正確的。確保你是因為正確的原因而做的選擇,而不是純粹的圖方便。
對于混合應用你是怎么看的?什么時候你覺得利大于弊?你還有其他可以提高混合應用體驗的竅門和技巧嗎?還需要更多如何從混合開發轉向完全原生開發的建議?請留言,我們期望聽到你對這個問題的聲音。