讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 11.9 事件泥潭 >

11.9 事件泥潭

你的代碼之所以能運行是因為Cocoa發送了事件,而你已經創建好了方法來接收這個事件。Cocoa會發送大量事件,告訴你用戶做了什麼事情,通知你應用進入到了生命週期中的哪個階段及其目標是什麼,等待你的輸入以便繼續。要想接收到監聽的事件,你需要通過叫作入口點的方法來達成所願,所謂入口點指的是這樣一些方法:它們擁有正確的名字,位於正確的類中,這樣就可以通過事件被Cocoa所調用。事實上,很容易就會想到,在很多情況下,一個類中的代碼幾乎都是入口點。

作為一名iOS程序員來說,合理安排這些入口點是面臨的主要挑戰之一。你知道要做什麼,但卻不能「想做就做」。你需要劃分好應用的功能,使之與Cocoa調用你的代碼的時間與方式保持一致。在編寫自己的代碼前,類的框架結構其實已經大致勾畫出來了,這是根據要接收的Cocoa事件而實現的。

假設一個iPhone應用要顯示出一個包含了表視圖的界面(這種情況其實很常見)。你可能要有一個相應的UITableViewController子類;UITableViewController是個內建的UIViewController子類,你所定義的UITableViewController子類的實例將會擁有並控制表視圖,同時還可能會將這個類作為表視圖的數據源與委託。在這個類中,你至少需要實現如下方法:

initWithCoder:或initWithNibName:bundle:

UIViewController生命週期方法,在這裡進行實例初始化。

viewDidLoad

UIViewController生命週期方法,在這裡進行視圖相關的初始化。

viewDidAppear:

UIViewController生命週期方法,在這裡設置一些界面顯示後需要使用的狀態。比如,如果要註冊通知或創建定時器,那麼這就是一個很適合的地方。

viewDidDisappear:

UIViewController生命週期方法,這裡所做的事情與viewDidAppear:正好相反。比如,可以在這裡取消通知註冊,或禁用在viewDidAppear:中所創建的定時器。

supportedInterfaceOrientations

UIViewController查詢方法,在這裡指定該視圖控制器的主視圖可以使用哪些設備方向。

numberOfSectionsInTableView:

tableView:numberOfRowsInSection:

tableView:cellForRowAtIndexPath:

UITableView數據源查詢方法,在這裡指定表的內容。

tableView:didSelectRowAtIndexPath:

UITableView委託用戶動作方法,在這裡對用戶輕拍表的一行這一動作進行響應。

deinit

Swift類實例生命週期方法,在這裡執行生命結束的清理工作。

假設你使用viewDidAppear:註冊通知並創建了一個定時器。該通知有一個選擇器(如果沒有使用塊),定時器也有一個選擇器;因此,你還需要實現這兩個選擇器所指定的方法。

我們已經有很多方法了,其存在的目的只是作為樣板代碼而已。它們並不是你定義的方法;你也永遠不會調用它們。它們是Cocoa的方法,放在這裡就是為了能在應用生命週期的某個恰當時刻對其進行調用。

按照這種方式,程序的邏輯將變得很難理解!我這裡並不是要批評Cocoa,事實上,我們很難想像其他的應用框架是如何工作的;不過,客觀上來講,Cocoa程序,甚至是你自己編寫的程序,在開發時都是難以閱讀的,因為包含了大量分離的入口點,每個入口點都有自己存在的意義,並且會在某個時刻被調用,然後這一切從程序的角度來看是非常晦澀的。要想理解我們假設的這個類到底在做什麼,你需要知道viewDidAppear:何時會被調用,它是如何使用的,諸如此類;否則,你就完全無法理解程序的邏輯與行為,更不必說程序的代碼含義了。在閱讀其他人的代碼時,這種痛苦還會加劇(這也是我在第8章曾說過示例代碼對於初學者來說幫助並不大的原因所在)。

查看一個iOS程序的代碼(甚至是你自己的代碼),當看到那麼多在各種情況下會被Cocoa自動調用的方法時,我相信你一定會驚呆。然而,經驗會告訴你諸如重寫的UIViewController方法、表視圖委託以及數據源方法等。另外,即便經驗再多,你也不可能知道某個方法會作為按鈕的動作或通過通知被調用。註釋是很有用的,我強烈建議你在開發任何iOS應用時都要對每個方法進行註釋,如果需要,註釋還要很詳盡,寫清楚方法要做的事情,以及在什麼情況下會被調用:特別是如果方法是一個入口點,那麼誰會調用它。

也許在編寫Cocoa應用時,最常犯的錯誤並不是代碼本身有Bug,而是將代碼放到了錯誤的地方。代碼沒有運行、在錯誤的時間運行,或運行的順序不對。我發現在各種在線用戶論壇中,這類問題一直都有人在問(下面就是用戶常問的一些問題):

·在視圖出現與按鈕呈現其文本之間存在延遲。

這是因為你將設置按鈕文本的代碼放到了viewDidAppear:中,這太遲了;代碼應該更早一些運行,放在viewWillAppear:中比較合理。

·我的子視圖是通過代碼定位的,不過其位置全都錯亂了。

這是因為你將定位子視圖的代碼放到了viewDidLoad中。這太早了;代碼應該晚一些在視圖的大小確定後再運行。

·雖然視圖控制器的supportedInterfaceOrientations不允許,但視圖還是可以旋轉。

這是因為你在錯誤的類中實現了supportedInterfaceOrientations;應該在包含了視圖控制器的UINavigationController中實現(或如本章前面所述,使用委託的navigationControllerSupportedInterfaceOrientations)。

·我為文本框中的Value Changed創建了動作連接,但當用戶編輯時,代碼並未得到調用。

這是因為你連接了錯誤的控件事件;文本框會發出Editing Changed而非Value Changed事件。

另外的挑戰在於你不可能精確知道入口點何時會被調用。文檔會給出概要性的介紹,不過在大多數情況下,對於事件何時會發生,以什麼順序發生並沒有保證。你可能覺得某個事件會發生,而且文檔也使你相信這個事件會發生,但可能並不會發生。你自己的代碼可能會觸發一些意料之外的事件。文檔可能並沒有清楚說明何時會發出通知。Cocoa中可能還會存在Bug,導致事件的調用方式與文檔不符。你沒法看到Cocoa源代碼,因此也搞不清底層實現細節。因此,我建議在開發應用時,使用原始調試(println與NSLog,如第9章所示)來分析代碼。在測試代碼時,請密切關注控制台輸出,尋找有意義的消息。你可能會對自己的發現感到驚訝。