國外一些網路媒體如 Tech Crunch、還有 John Gruber 的網站等,前兩天報導,Joe Hewitt (Facebook 的 iPhone 應用程式的作者)說,他個人打算中止 Facebook 的 iPhone 應用程式開發,轉往進行其他的 Facebook 的計畫,原因是他對於蘋果的 App Store 上架審核機制非常不滿。
剛看到這則新聞的時候,還搞不清楚是怎麼一回事,第一個想法是-別人對上架審核不滿也就罷了,Facebook 有什麼好不滿的?其他人將軟體送進去之後快則到七天之後才能夠等到審核結果,就 App Store 開張以來,唯一看到能夠一個星期推出兩個新版本的軟體,也就只有一個,而這個軟體還不是別的,就是 Facebook。
昨天上班一打開電腦,才終於搞清楚發生在 Joe Hewitt 身上是什麼狀況。打開收信程式,看到蘋果送來的退件信件,說,我們寫的程式不能夠在 App Store 上架,原因是程式裡頭呼叫了 iPhone 的 private API;而我從來就不記得我什麼時候用到過這些東西,查了一下,呼叫 private API 的,不是自己的程式,而是因為程式用到了一個 external library,這個 library 呼叫了,而這個 library 就是 Joe Hewitt 所撰寫的 Three20。
Three20 大概是目前最有名的第三方 iPhone 的 UI library,因為 iPhone 的螢幕大小是 320×480 pixel,所以取了這麼一個名字。Three20 計畫是 Facebook 的 iPhone 應用程式的延伸產物,他們把 Facebook 的 iPhone 程式在架構上拆成了兩塊,一塊處理如何登入、讀取 server 資料的網路 API ,另外一部分就是如何在 iPhone 上做 user interface 的呈現,而這部份 Facebook 將程式碼開放出來讓其他人都能使用,之前是放在 Joe Hewitt 的個人名下(以及 github 的個人帳號下),前兩天轉到了 Facebook 的帳號下。
Three20 這個 library 裡頭,個人覺得厲害的有幾個地方:
一、自己做了一套在 iPhone 上的 Rich Text 的文字排版引擎:
在 iPhone SDK 原本所提供的 UI 元件中,想要在畫面上放一段文字的話,每個 UILabel 元件一次只能夠使用一種字體、一種顏色、一種大小,如果想要比較複雜的排版,就必須使用瀏覽器元件 UIWebView。
這樣要做一個介面老實說頂痛苦的-當你在做一個輸入資料的介面,在一堆文字輸入框以及按鈕當中,想要有一段排版比較複雜的說明文字時,你不太可能就這樣在畫面中挖一塊,放個瀏覽器元件進去;而且 UIWebView 本身也不太能夠作為 iPhone 應用程式的主要介面元件,在 Mac OS X 上你還可以透過比較多的 delegate method 以及 Javascript-Objective C bridging,讓 Web view 裡頭發生的事件傳回你的 controller class 裡頭,在 iPhone 上也沒有這方面的 API。
Three20 自己做了一套排版引擎,語法是 HTML,只要你把想要填入的內容加上 HTML tag,就可以直接在 UILabel 上呈現排版效果;同時你也可以使用像是 CSS 的樣式指令,雖然每個樣式都還是一個 Objective C selector,但是可以很方便的就加上了字體、顏色、背景、縮排…等原本很難做的排版。這部份放在 TTStyle 部分的程式中。
二、提供大量現成而且簡化過的 UI 元件
如果你想要做一個像 iPhone 內建的相簿程式那樣的介面,想要有可以全螢幕瀏覽圖片,用手指滑一下就跳到下一張,然後可以回到縮圖瀏覽模式…這些功能,Three20 已經有現成的元件,你的圖片資料可以放在本機、也可以放在網路上,只要提供一個內容是 URL 的 Array 就好了,Three20 會自己幫你處理圖片下載與 cache 等工作。
另外像是 Table View,原本你寫一個 UITableViewController subclass 可能要實作五六個 data source 與 delegate method,他也幫你處理掉很多工作,有時候就只需要一個 method、準備一個 Array 就夠了,也幫你處理什麼想在表格裡頭放置在網路上的圖片這類的工作。
裡頭有一些 UI 元件我會拿來用(正所謂人活著好好的幹嘛重新發明輪子),不過我自己是比較不贊同把抓取網路資料的行為、跟 iPhone 的 UI code 混在一起,因為這樣會將網路資料交換的程式散亂在很多地方,很容易搞亂架構,而且會變得不容易測試。
三、URL Based Navigation
Three20 是一個在想法上非常 Web 導向的 library,像是前一點自己弄一個 HTML 引擎,或是覺得網路上的圖片應該直接在 UI code 部分處理,不過,這種 Web 思考的導向,還是在 URL Based Navigation 最明顯。
iPhone 的 UI 設計的一大特色,就是相較其他平台,應用程式有很清楚的導覽路徑-如果你眼前的畫面的左上方有一個向左的箭頭,就代表可以回到目前瀏覽路徑的上一層,然後點到表格中的某個項目或某個其他元件,就會從右方跑出一個新的畫面,透過動畫效果把原本的畫面擠到左方,告訴你進入了下一層。在 Palm Pre 系統沒有視覺元件告訴你現在你在哪一層,唯一的一個實體按鈕則有時是回到上一層、有時拿來當做切換應用程式,Android 有實體的 Back 與 Menu 按鈕,但往往讓人看不出來什麼時候可以回上一層、什麼時候有 Menu 可以用。
iPhone SDK 提供 UINavigationController 這個 class,處理這種一層一層瀏覽行為,要進入下一層,就是產生一個新的 UIViewController subclass,呼叫 UINavigationController 的 -pushViewController:animated:。而這個過程有一個麻煩的地方-這些物件在跳出應用程式後就消失了,那,我在跳出應用程式,再重新進入應用程式後,我要怎麼記得上次瀏覽到什麼地方?我要怎麼可以恢復最後一次使用的瀏覽狀態?
Three20 就在 UINavigationController 的基礎上,架了一層用 URL 處理事情的想法:雖然我們現在在做一套 iPhone 應用程式,但是卻用 Web App 的想法設計-每一個 iPhone 的畫面,都想像成是一個 Web App 的 URL,每個 UIController 在初始化時需要的參數,都像是我們對一個 Web App 的 URL 傳入的 Get 參數一樣。
如果你有一個通訊錄程式,首頁是通訊錄群組列表,點下去是群組中的人名列表,人名點上去可以看通訊資料內容,再點下去可以編輯,可能就會像這樣:
通訊群組列表(主畫面) addressbook://root
顯示某一個群組 addressbook://group/1
顯示某一個人的資料 addressbook://person/1
編輯某一個人的資料 addressbook://person/edit/1
而當 UINavigationController 在推入這些 view controller 的時候,就同時把對應到這些 view controller 的 URL,像是瀏覽器歷史紀錄一樣的記下來,下一次進入程式時,就可以按照最後一次記下的路徑,逐一重新產生 view controller,呈現最後一次使用的狀態。
目前在 github 上,Three20 是 Objective-C 語言類中,在使用者追蹤開發進度排行榜、以及分支數量的排行榜上,都是累計第一名。github 的 clone 數報表目前故障,沒辦法知道有多少人 checkout Three20 拿來用的紀錄,但是有兩百多個分支,至少代表有兩百多個人還需要客製化 Three20,在這邊先不講求什麼精確的數字,總之在使用 Three20 的開發者有一大堆,而 Three20 的用途就是放在 iPhone 應用程式裡頭,所以,有用到 Three20 的應用程式,一大堆。
於是 Apple 開始 reject 用到 Three20 的程式之後,這兩天你又是可以看到、有人在 Stack Overflow 上發問、有人把問題丟到 issue tracker,當然在 Three20 的討論區上更是免不了,一串討論就是一百多篇,因為整個討論串很長,所以又必須有人做出一份摘要,其中對你來說比較重要的資訊是-目前如果想要一份沒有用到 private API 的 Three20,請先服用 Prime 31 的 fork。
蘋果所稱 Three20 所使用的 private API,是 Three20 直接存取了 UITouch 這個 class 裡頭的幾個成員變數,像是 _locationInWindow、_view、_window 等。UITouch 就是用來記錄、處理使用者用手指摸過 iPhone 螢幕時所產生的事件的 class,裡頭儲存的資料包括-什麼時候發生、手指在螢幕上的座標位置、點到的是哪個畫面等。
一般來說,好像不太需要自己產生 UITouch 物件,而是在使用者按了螢幕之後,系統就會把 UITouch 物件傳遞給你的程式。而 Three20 用到 UITouch 成員變數的地方,在於自己產生 UITouch 物件,初始化時自然會將各種資料儲存在這些成員變數裡,需要自己產生 UITouch 物件的主要用途,則是用來作 UI 的自動化測試。
根據 commit log,這段程式在今年二月十八日就有了,在這段時間內 Facebook 出了多少版本,多少用了 Three20 library、用了這段程式的軟體在 App Store 上架,到了十一月,突然統統 reject。
而這些 UITouch 的成員變數有多「private」呢?你也不需要用什麼逆向工程工具就可以知道,因為都直接寫在 UITouch 的 header 裡頭,如果你裝了 iPhone SDK,請打開 Xcode,隨便開一個文字檔,打上「UITouch」幾個字,按著鍵盤的 command key,在剛剛打的字上面點兩下,Xcode 就幫你打開了 UITouch.h,另外一種更快的方法是直接用 spotlight 搜尋「UITouch.h」。雖然這些變數是 class 裡頭的內部狀態,但是就是寫在這麼容易就可以看到的檔案裡頭,然後說這是 private API…。
如果真的不希望別人使用,Xcode 也可以設計成,在編譯的時候,同時檢查哪些是你不希望別人使用的東西,提出警告。Xcode 現在連哪裡有 potential leak 哪裡有 over release 都可以找到,這種事情也不是做不到。
不過,這個時候突然針對取用 UITouch 成員變數有大動作,總覺得是想要排除很多現在的應用程式日後不相容的問題,UITouch 大概會有大改寫。而 UITouch 還可以改寫什麼呢?新的觸控介面的資料?在雙螢幕上同時出現 touch 事件?(隨便亂猜)
這邊加一段我同事的意見:
『Objective-C 確實沒有像 C++ 那個嚴格的成員變數保護,這些底線開頭的變數也確實被 Apple 視為 private ivar,但是在沒有任何溝通的情況下,直接以「這是使用private API」的行為來 reject 掉一些拿來測試用的 code ??』
總之,狀況簡述如下-
你製作、維護一套花了很大力氣做出來的 library,說服公司將資產透過網路分享給別人,自己寫這套 library 只領公司薪水,其他人的軟體卻因此在 UI 精緻度上提昇並因此獲利。有一天,蘋果 reject 了(想來絕對不下)上千套軟體,原因是一段就算處在灰色地帶,但是長久以來(以 iPhone 的發展速度,九個月算很久了)沒什麼問題,但突然被認為有問題的十行程式。其他開發者的詢問如雪片般飛來,一片哀鴻遍野,一群人為了各自利益的軟體產品圍著你一個人看你寫程式,做這件事情對自己與公司看來也都沒有什麼好處,而問題追根究柢是蘋果的政策。你會怎麼做?
於是看到某些討論就實在讓人覺得很歡樂,比方說看到這樣的留言-