Xcode 7:Storyboard Reference、Strong IBOutlet以及Scene Dock

本文由CocoaChina譯者小袋子(部落格)翻譯
原文:Storyboard Reference, Strong IBOutlet, Scene Dock in iOS 9
在這個教程中,我想要聊一些有關於Xcode 7中Interface Builder的新特性,我相信這將會改變你對Storyboards的看法。
Strong 引用的 IBOutlet
Apple已經對Xib和Storyboard檔案做了很多優化。並且由於這些優化,你現在可以將IBOutlet定義為strong,而不是weak。Apple曾在上一屆的WWDC上指出這一點,因此讓我們來看一下其中的更多細節。你可以從 這個文件 中找到管理Nib檔案中物件生命週期的章節:
Outlet一般來說應該為weak,除了在nib檔案 ( 或者iOS中,storyboard scene) 中的File’s Owner的頂級物件,這個物件可以是strong。你建立的Outlets應該為weak,原因如下:
你建立的一個 view controller 檢視的子檢視或者 window controller 窗體檢視的 Outlets,是物件之間的弱引用,不應該有依賴關係。
strong的outlet通常是特殊的framework類(如:UIViewController 檢視的 outlet,或者 NSWindowController 視窗的 outlet)。
正如這個段落所解釋的一樣,view controller檢視的子檢視 outlet應該為 weak,因為這個檢視已經被nib檔案的頂級物件所擁有了。然而,當一個Outlet被定義為weak指標時,ARC會在編譯期間呼叫以下函式:
id objc_storeWeak(id *object, id value);這個函式把物件的值作為key,並把它新增到table中。這個table被稱為weak table。ARC使用這個table去儲存應用中的所有的weak指標。現在,當物件被deallocated時,ARC將會指向weak table並且將weak引用置為nil。同時,ARC將會呼叫:
緊接著,登出這個物件並再次呼叫objc_destroyWeak:
這種weak引用關聯的生命週期是strong引用的2-3倍。所以,通過避免簡單地定義outlets為strong,使用弱引用是一種執行期間的通用做法。
我想這個決策與已廢棄的viewDidUnload方法有關。直到iOS 5,這個方法被用於清空在低記憶體環境下的檢視。正如文件中解釋的那樣:
在iOS 5之前,當發生低記憶體警告或者當前view controller的檢視不被需要時,在檢視被釋放之後,系統會選擇性地呼叫這個方法。這個方法讓你可以進行最後的清理工作。如果你的檢視儲存了檢視或者其子檢視的單獨引用,你應該使用這個方法去釋放這些引用。
在那時,定義一個屬性為weak是有意義的,因為這就不用在viewDidUnload額外地釋放物件。但是在iOS 9中,我相信我們已經有足夠的時間去避免使用這個方法。因此,在IBOutlets定義weak是沒意義的。
現在 Storyboard 的幾個限制
Apple是在iOS 5中開始引入storyboards的。在此之前,使用Interface Builder的nib檔案是建立UI的唯一途徑。在iOS開發中,單個檔案中操縱多個nib檔案是很普遍的。然而,為了理解應用流以及view controller如何連線在一起,開發者需要去每一個view controller類內去找出跳轉到下一個介面的橋接點。這是一個非常耗費時間的工序,尤其當你不是應用的原始開發者時。
Apple提出Storyboards用以簡化這個過程,並幫助開發者能夠對整個應用程式流有完全的控制。除此之外,storyboards允許你在一個檔案中擁有一個view controller檢視(通過新增 .storyboard檔案)。用這種方式,你可以看到整個程式的流狀態,並且能夠方便地理解view controller的連線關係。然而,storyboards也引出了一些問題。把所有的nib檔案都放在一個檔案中顯然是非常便利並且能完美工作,但是這只是在你為單人開發的前提下。只要你的團隊擴大了,你會使用版本控制,例如git或者subversion,這時你就會討厭storyboards。因為,當把修改合併到一個通用的git branch時,就會產生衝突,而解決此類衝突是很頭疼的。在編譯期間,nib會被編譯成XML檔案。所以,為了解決合併衝突,你需要比較兩個巨大的XML檔案,並且要嘗試理清哪部分是你修改的,哪部分是你同事修改的。此外,Apple經常修改這個檔案格式。所以,試圖去理解並且反轉storyboard格式是非常浪費時間的。
例如,在iNVASIVECODE(這是作者所在的公司),我們傾向於使只用storyboards去構建app原型。我們的設計師能夠在幾個小時內設計出一個能夠在iOS裝置上執行的原型,有時候只需要幾分鐘。這樣可以在不寫一行程式碼的情況下使用storyboards。所以,storyboards對於構建原型來說是非常方便的,但是不建議在開發期間使用。
另一個storyboard的重要侷限是不能新增不屬於一個場景體系的檢視。我個人認為跟前面所說的合併問題相比,這是一個更為致命的限制。只要能夠使用,我必定會使用IB。我喜歡這個,因為這可以避免寫程式碼。但是使用storyboards,不能新增場景體系以外的檢視。因此,當我需要額外的檢視時,我就強迫自己去使用nib。
Storyboards還有一個額外的侷限就是轉場動畫問題。在iOS 7及之後的iOS 8,Apple提出了在兩個view controller之間定製一個轉場動畫的新方法。當你執行一個segue時,這個新方法需要建立不能使用storyboard的特殊物件。所以,如果你想要新增定製轉場動畫的方法到你的view controllers,你要避免使用storyboards。
但是猜猜看!Xcode 7和iOS 9為我們解決了所有的這些問題。
Storyboard Reference
在Xcode 7中,我們有一個在多個storyboards中組織scenes的新方法,並且能對它們進行引用。讓我們來看一個實踐的例子。下載 這個我已經準備好的例子。開啟它,並且選擇Main.storyboard檔案。我已經為了準備好了一系列組織在一個tab bar controller下view controller。每一個tab包含一個navigation controller。下面的圖片強調了示例專案的storyboard部分。

正如你所看到的那樣,tab bar controller包含了三個navigation controller。每一個navigation controller控制著不同的檢視控制器。現在,想象一下在這個專案裡和其他開發者一起工作。正如我前面描述的那樣,使用同一個storyboard檔案是非常令人頭疼的,因為你們每個人都會修改它。你可以把著三個navigation分支分割成三個storyboard檔案。然而,當你準備在執行期從一個storyboard跳轉到另外一個時,你必須載入相應的storyboard檔案。這需要增加額外的程式碼。
xcode 7允許你建立多個storyboards,並且可以方便地操縱它們。選擇頂部的navigation controller 以及兩個view controller,如下圖所示:

選擇好之後,開啟選單欄的Editor,然後選擇Refactor to Storyboard(如圖)

為新的storyboard取一個名字(如圖)。我將它命名為First.storyboard。

點選儲存。正如你所見到的那樣,一個新的storyboard已經被新增到你的專案中了。讓我們回到Main.storyboard,你將會看到如下的物件。

這個稱之為Storyboard Reference,它確實為新建的First.storyboard的引用,並且替換了先前選擇的三個view controller。最棒的是如果你雙擊storyboard引用,Xcode 7會開啟所引用的storyboard。因此,當你想要控制應用流時,你可以方便地導向不同的storyboard。在執行期間,當segue指向的一個Storyboard Reference被執行時,這個被引用的storyboard中的初始化view controller會被載入。此外,Storyboard References還能夠引用相同的storyboard。
另外,你也可以手工建立一個新的storyboard,然後新增一個Storyboard Reference到起始的storyboard中。讓我們來試一下。
建立一個新的storyboard並命名為Third.storyboard。在Main.storyboard檔案中,從Object Library中新增新的Storyboard Reference。選擇Storyboard Reference並且開啟相應的Attributes Inspector。如下圖所示:

在這個欄位中,選擇你想要引用的storyboard(在我們的例子中是Third)。如果這個欄位為空白,則被引用的storyboard是定義的Storyboard Reference。Reference ID指向在目的storyboard中的一個特定scene。如果你置空的話,初始化view controller會載入。

最後,Bundle欄位需要被置為包含目的storyboard的bundle。如果你留空的話,就會使用源storyboard的bundle。
在Third.storyboard檔案中,你需要新增一個新的view controller並將其作為初始化的view controller。之後,只要view controller是Main storyboard的一部分,你可以都可以執行app並且導航到那裡。
所有,現在你可以在多個檔案裡組織你的storyboard,並且可以保持這些storyboard的引用。此外,每一個storyboard能夠被分配給一個不同的開發者,而你不需要去考慮view controller間的連線組合。這真是非常方便。
Scene Dock and Extra Views
這是我最喜歡的特性。現在,我能夠在storyboard中新增在scene體系外的檢視。為了讓你明白它使如何工作的,我們先建立一個新的專案。將其命名為ExtraView,開啟Main storyboard,在頂部的First Responder和Exit之間新增一個新的view。如下圖所示(這個叫做Scene Dock):

把這個view的大小調整為 1500×120 畫素。然後在這個view的頂層新增一個大小為 240×112 的小view。把這個檢視放到大檢視的中心,然後增加頂部和底部的約束 (constants = 8),寬度約束(constant = 240) 以及水平居中的約束。然後新增一個scrollview到view controller中,將其居中,並新增trailing和leading space約束 (constant = 0),高度約束(constant=128),最後增加垂直居中約束。在ViewController.swift中,新增下列兩個outlet:
將它們連線到scrollview以及外面的view。最後,新增viewDidAppear:方法:
然後執行專案,可以看到的是,你現在可以新增額外的檢視(可以任意新增),並且可以在執行期間很方便地加載出來。你可以 下載 這個示例來加深理解。
定製轉場動畫
這是Xcode 7中storyboard另外一個很酷的新特性。具體的細節我將會留到之後釋出的文章,這裡我只想先給你一些你能做什麼的想法。如果你在多storyboard專案中選擇任意的action segue,並且開啟Attributes Inspector,你將會看到一個新的欄位Segue Class,正如下圖所示:

你可以建立一個UIStoryboardSegue的子類,然後遵從UIViewControllerTransitioningDelegate 協議。然後,在類中實現animationControllerForPresentedController: presentingController: sourceController: 以及 animationControllerForDismissedController:。此外,你還需要建立兩個NSObject的子類,遵從UIViewControllerAnimatedTransitioning delegate。在這些類中,你必須實現兩個方法:transitionDuration: 和 animateTransition:。
我將會在接下來的文章中介紹其中的細節。
總結
Xcode 7的Storyboards增加了很多便利的新特性。我們現在可以建立storyboard references,在scene體系外增加檢視,並且可以使用新的定製檢視轉場動畫。我還討論了為什麼你應該將outlet定義為strong引用而不是weak。
作者介紹
Geppy
Geppy Parziale (@geppyp) is cofounder of InvasiveCode (@invasivecode). He has developed iOS applications and taught iOS development since 2008. He worked at Apple as iOS and OS X Engineer in the Core Recognition team. He has developed several iOS and OS X apps and frameworks for Apple, and many of his development projects are top-grossing iOS apps that are featured in the App Store.
