以 delegation model 為基礎的伺服器操作抽象化設計
非常饒舌的標題。其實白話是,如何用 Objective-C 設計一個類似 ObjectiveFlickr 的 framework 來包裝一些 web service 操作。
最近在重寫一些舊的噁心程式碼,也順便看了一下 ObjectiveFlickr ,發現其實大家要做的事情都很類似,都是抽象化伺服器操作。不只目標很像,竟然連程式的寫法也相當類似(其中許多巧合大概得歸咎於 Cocoa 在這方面的缺陷),例如大家都重新用 CFReadStream 之類的 API 重新包裝了一個 HTTPConnection 物件而不用 NSURLConnection 等。
其中我遇到一個比較值得討論的共同問題是這樣的:由於網路程式的特性,synchronous call 顯得不太合適,於是「射後不理、出事了再回頭找人」的 asynchronous behavior 便成為網路程式設計的典範。Cocoa 在實作 asynchronous behavior 採用的是 delegation model,也就是指定一個委託人,等到網路那端有消息了再去呼叫委託人的某些 method,在我看來其實只是一種 callback function 的物件化版本。
然而以 ObjectiveFlickr 的設計來看,使用 delegation 可能會出一些問題:
[invoc setDelegate: A];
[invoc callMethod: @"flickr.get.xxx" ...];
[invoc setDelegate: B];
[invoc callMethod: @"flickr.put.xxx" ...];
假設第二行的 callMethod 的網路回應還沒回來之前就已經執行完第三行,等到第二行的網路回應回來了,卻發現竟然「所託非人」,本來的委託人 A 被換成 B ,就有可能出問題了。
究竟為何導致這樣的問題呢?我發現大家蠻常採用一種做法有可能是原因:用來代表 Server 的物件模型通常都會用各種手段取得一個單一的 HTTPConnection 物件 reference,例如 Server 本身擁有一個 HTTPConnection member。其他人的理由我不知道,我本身會這樣做的原因是記憶體管理!HTTPConnection 本身不能是一個 autoreleased 的物件,因為它必須要夠「長壽」到網路回應來,將後事交代給委託人以後才能安息。所以我們的 Server 需要 init 一個 HTTPConnection。然而等到它交代完後事了以後,根據 Cocoa Memory Management 原則,必須由當初 init 它的人,也就是 Server,來親手了結它的性命!萬般無奈下,只好讓 Server 與 HTTPConnection 建立一個其實不必要的從屬關係,讓 Server 能夠在必要時 release HTTPConnection。
讓 Server 擁有一個單一的 HTTPConnection 物件為何會導致一開始提到的奇怪問題?仔細思考,問題出在「單一」和「非同步」。因為手上一次只能有一個 HTTPConnection ,在「連線開始」和「連線結束」之間又有可能改變物件的行為,導致一種隱含的相依關係:在一個連線結束之前,會改變內部的動作都不允許,甚至包括另外一個邏輯上不相依的連線(例如同時取得 contact list 和 photo list 邏輯上可以並行,但因為需要不同的委託人來處理所以會改變內部的 delegation)。這樣的設計有可能讓使用者在 delegate method 上加上額外的邏輯來呼叫下一個連線(已確保此連線結束)或是不斷去 polling 看看連線結束了沒。
我認為合理的設計是這樣子的,讓每個 HTTPConnection 都記得屬於自己的委託人,並且 Server 和 HTTPConnection 物件之間根本不需要那麼強的關係。在每次要連線時,直接產生一個新的 HTTPConnection,告知其委託人即可。
問題又來了,每次產生一個新的 HTTPConnection ,屆時 Server 將沒有辦法回收記憶體,發生 memory leak。解法很簡單,直接在 HTTPConnection 自己連線結束的 callback 最後解決自己。但是這樣子仍舊違反了記憶體管理原則啊?Server init 卻沒有 release,不太好吧。
於是,做一點小修正:Server 要產生 HTTPConnection 時,必須產生 autoreleased 的物件,而等到 HTTPConnection 被告知要展開連線的時候,[self retain] 。等到 HTTPConnection 連線結束或是錯誤發生,release itself。(什麼,這麼 trivial ?那上面寫這麼多幹嘛?我剛想到的時候也有這種感覺)
話說回來, Objective-C 2.0 有了 garbage collection 以後以上就全都是屁了,讓我們為 Leopard 歡呼吧!
P.S. ObjectiveFlickr 目前 flag 的設計在 single-thread 下是 OK 的,但是到 multi-thread 就不保證了(雖然機率可能很小,但還是有可能會爛),縱使如此,本篇也未討論到 multi-thread 的部份,因為蠻複雜的…

“我想我還是等 Objective-C 2.0 之後再來學 Cocoa 好了 lol”
---ericsk. 4/12, 2007