本文轉(zhuǎn)自:http://www.cocoachina.com/swift/20150803/12881.html
原作者:Hector Matos
原發(fā)表日期:2015-07-13
<b>本文的代碼示例改用swift3.0,原文使用的是swift2.0</b>
<h2>Swift的核心</h2>
我們可以通過等式的傳遞性來理解swift:
Swift 的核心是面向協(xié)議的編程。
面向協(xié)議的編程的核心是抽象和簡化。
所有swift的核心就是抽象和簡化
<small>
你可能對我的標(biāo)題感到詫異。我并不是說子類沒有價值,尤其在使用單一繼承的情況下,類和子類當(dāng)然是強有力的工具。然而我想說的是,<b>iOS日常開發(fā)的問題是對類和繼承的過度使用</b>。作為面向?qū)ο蟮木幊陶?object-oriented programmer,后面統(tǒng)一換為OOP編程者)我們總是會自然的去傾向于引用類型和類去解決問題,但是我個人還是認(rèn)為應(yīng)該反過來,傾向于<b>用值類型來代替引用類型</b>。我們還是要去寫模塊化的,可伸縮的并且可重用的代碼,這一點不會變。<b>swift 中有強大的值類型就可以幫我們實現(xiàn)模塊化這一目的,且不會對引用類型有過度依賴。</b>我認(rèn)為不僅面向協(xié)議的編程 (protocol oriented programming,后面統(tǒng)一為POP) 可以幫我們實現(xiàn)這點,另外兩種類型也可以,且都具有抽象和簡化的核心思想,這兩種分別是:面向值的編程和 函數(shù)式編程
先說清楚,我絕不是這種編程類型 (POP,面向值的編程 和 函數(shù)式編程) 的專家。和你一樣,從MMM時代(收到內(nèi)存管理)開始我就是一個OOP編程者。通過自學(xué),從開始就很重視值的抽象和簡化思想。我都沒有意識到自己是一個傾向于函數(shù)式編程的OOP編程者,而且很多時候都是在用面向值的編程和POP的思路。這可能是我為什么在第一天就興高采烈的加入了swift的浪潮之中的原因。在WWDC的一整周里,swift的核心理念與我認(rèn)為的該怎樣去編程是如此之契合,這個感受一直充斥在我腦海中。通過這篇文章,我希望能幫助你(OOP的編程者)打開思路,去考慮該如何用更加Non-OOP(非OOP)的方式去解決問題。
</small>
<h2>OOP的問題(和我不得不學(xué)它的原因)</h2>
我會是第一個跳出來說的:不用OOP的話做出iOS應(yīng)用很難。Cocoa的核心就是OOP。沒有OOP的話你根本寫不出來一個iOS應(yīng)用。有時候我會幻想這不是真的。如果你有不同觀點,趕快證明我是錯的吧。我真的需要這樣,求你了,證明我是錯的吧!
不管怎么樣,你總會遇到必須用對象、用引用類型解決問題的時候,然后由于Cocoa的規(guī)定而被迫使用類(classes)。這種情況下你碰到的問題都是我們大家熟知并熱愛的
<small>
- 傳遞class的實例這個做法好像總是有種不可思議的能力:你想用一個實例的時候讓這個實例的狀態(tài)和你所期望的不一樣。<b>這是由于可變狀態(tài)導(dǎo)致,你這個對象的另一個享有者在它覺得合理的地方改變此對象的屬性。</b>
- 如果不用多繼承的話,從一個很棒的class派生出子類從而獲得它的擴展功能,妨礙了你使用另外一些class的更多功能,而且還增加了復(fù)雜性。(舉個例子來說,把兩個UITextField的子類結(jié)合起來生成一個擁有這兩者功能的UITextField子類,難)
- 上面一條的另外一個問題是會引出意外行為。如果你遇到了類似上面一條所描述的情況,你就陷入到了一個依賴問題中:你連接了兩個superclass各自特性,對其中一個superclass的移除改動可能會給另外一個superclass代理不良影響。這就是class之間緊耦合所帶來的問題
- 單元測試中的mocking。有些class在系統(tǒng)中的耦合過于緊密,想完全測試這些class就需要你創(chuàng)建每一個class的假象表。我都不用告訴你本質(zhì)上你并沒有真正的測試了這個class,你不過是在假裝測試它。這里就不提很多Mocking的庫是用運行時的小把戲來造一個假的class了。
- 并發(fā)問題。這和上面提到的可變狀態(tài)是伴隨出現(xiàn)的。你從多個線程中同時改變一個引用就會引起這個問題,運行時使對象之間的同步發(fā)生異常。
-
很容易導(dǎo)致出現(xiàn)像上帝類(God classes - 承擔(dān)著很多subclasses需要的重要高層級代碼的所有責(zé)任),Blobs(有過多職權(quán)的classes),Lava Flow(因為含有太多的非法代碼導(dǎo)致任何人都不敢碰的classes)等等這些種反面模式
</small>
ggg
<h2>POP 面向協(xié)議的編程</h2>
陷入OOP的反面模式特別容易。多半時間我們(包括我)就是太懶而不愿意去點File>New File。結(jié)果是在現(xiàn)有class的基礎(chǔ)上添加一個函數(shù)是如此輕松,我們就不愿意從零開始建一個新的class了。如果你一直這么干,而且一直非常懶的從一個"很重要"的class派生subclass的話,你就把上帝類/死星類給弄出來了。實際上我之前就這么干過:我給一個app里的每個view Controller都加了能呈現(xiàn)一個指向navigationController的navigationBar的error view的功能。唉,我可真蠢。直到要改動那個Error上帝類行為的時候,我不得不把整個app都改一遍。這不是聰明的做法,你真應(yīng)該看看那些bug。
如果使用了POP,這個Error上帝類很大程度上就能很容易的抽象出來,以后改進(jìn)它也方便。
這是一個能展示(之前的方式)有多殘暴的例子:
class PresentErrorViewController: UIViewController {
var errorViewIsShowing: Bool = false
func presentError(message: String = "Error!", withArrow shouldShowArrow: Bool = false, backgroundColor: UIColor = UIColor.red, withSize size: CGSize = CGSize.zero, canDismissByTappingAnyWhere canDismiss: Bool = true) {
// 寫下了復(fù)雜的,脆弱的代碼
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
隨著項目的進(jìn)行,事情馬上變的明了:并不是每一個UIViewController需要這個error邏輯,或者真的需要這個class 所提供的每一個功能。我們團隊里任何一個人都可以請用的在這個superclass里改點什么,從而影響整個app。這就讓代碼變得很脆弱。還是代碼呈現(xiàn)出多態(tài)。本應(yīng)該有子類覺得它自己的行為,這里的superclass卻給幫著決定了。下面是在swift3.0中的我們?nèi)绾斡肞OP來更好的構(gòu)建這段代碼:
protocol ErrorPopoverRenderer {
func presentError(message: String, withArrow shouldShowArrow: Bool, backgroundColor: UIColor, withSize size: CGSize, canDismissByTappingAnywhere canDismiss: Bool)
}
extension UIViewController: ErrorPopoverRenderer {
//使所有遵從于ErrorPopoverRenderer協(xié)議的UIViewController具有一個presentError的默認(rèn)實現(xiàn)
func presentError(message: String, withArrow shouldShowArrow: Bool, backgroundColor: UIColor, withSize size: CGSize, canDismissByTappingAnywhere canDismiss: Bool) {
// 加上呈現(xiàn)error視圖的默認(rèn)實現(xiàn)
}
}
class KrakenViewController:UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func methodThatHasAnError() {
// ...
// 拋出error,原因是Kraken海妖今天吃人會感到不適。
presentError(message: "", withArrow: true, backgroundColor: UIColor.red, withSize: CGSize.zero, canDismissByTappingAnywhere: true)
}
}
看,這里發(fā)生了很炫酷的事情。我們不僅消除了上帝類存在,還讓代碼更加的模塊化并增強了它的擴展性。通過創(chuàng)建一個 ErrorPopoverRenderer協(xié)議,就會讓任何則遵循了該協(xié)議的class具有呈現(xiàn)出一個ErrorView的能力。還不止這些,我們的KrakenViewController class 不用必須實現(xiàn)presentError這個函數(shù),因為我們擴展了UIViewController,讓它提供了一個默認(rèn)實現(xiàn)。
唉不過等下!這有個問題!我們每次想要呈現(xiàn)一個ErrorView的時候都不想要去實現(xiàn)每一個參數(shù)。這就有點兒讓人不爽了,因為我們不能再protocol協(xié)議函數(shù)聲明中為參數(shù)提供默認(rèn)值。
我還挺喜歡這些參數(shù)的!更槽糕的是在讓買賣根據(jù)模塊化特征的過程中我們引入了復(fù)雜度。還是繼續(xù)吧,用swift3.0中新加的一個小妙招來多少的補償一下:
protocol ErrorPopoverRenderer {
func presentError()
}
extension ErrorPopoverRenderer where Self: UIViewController {
func presentError() {
// 在這里加默認(rèn)實現(xiàn),并提供ErrorView的默認(rèn)參數(shù)。
}
}
class KrakenViewController: UIViewController, ErrorPopoverRenderer {
func methodThatHasAnError() {
//...
// 拋出error,原因是Kraken海妖今天吃人會感到不適
presentError()
}
}
好了,現(xiàn)在看起來已經(jīng)很不錯了。我們不僅消除了這些煩人的參數(shù),還用swift3.0的新特性在protocol的層級上用Self給了presentError一個默認(rèn)實現(xiàn)。用Self意味著當(dāng)且晉檔協(xié)議的遵循者是繼承自UIViewController的情況下,這個擴展才會有效。這就讓我們能夠把ErrorPopoverRenderer真的當(dāng)做是一個UIViewController,而不需要對后者做擴展!更棒的是,從現(xiàn)在開始,Swift的運行時是以靜態(tài)調(diào)度而非動態(tài)調(diào)度去調(diào)用presentError()方法。大致的意思就是我們在函數(shù)調(diào)用點給presentError()方法增強了一點性能。
唉,不過還是有個問題。到這里我們POP的旅途暫時告一段落,但對于它的完善依舊不會停止。我們的問題是如果只想對一部分參數(shù)使用默認(rèn)值,對生效的不用默認(rèn)值該怎么做?在這方面POP的話基本幫不上什么忙,但是我們可以尋求另外一種方法?,F(xiàn)在,我們使用面向值的編程(VOP)吧。
<h2>面向值的編程(Value-oriented-programming)</h2>
看到了吧,POP和VOP總是伴隨出現(xiàn)。在WWDC視頻中,Crusty提出了一下大膽的論斷:我們用struct 和 enum 類型就可以做到一切class能做到的事。我很大程度上同意這點,但是沒這么極端。依我看,protocol本質(zhì)上是吧VOP粘合在一起的膠水,這點我和Crusty吃相同太大。實際上既然我們說的了Swift的核心理念以及VOP,我想給你們看看從Andy Matuschak的精彩訪談中關(guān)于Swift中的VOP
的話題里面摘出來的一張極好的圖:

能看出來Swift的標(biāo)準(zhǔn)庫中,僅有的4個class,和余下的95個struct和enum的實例共同構(gòu)建了Swift功能的核心。
Andy如此闡述道:用Swift編程的時候我們要考慮用一層很薄的對象層,和一個很厚的值類型層。Class是有它們的地方,但是我想盡最大程度的去認(rèn)為它們的位置只應(yīng)該處于對象層中的一個很高的級別上,在這里通過操縱值類型層中的邏輯來管理各種行為。
"把邏輯和行為分開"——Andy Matuschak
和你所了解的一樣,值類型被賦給一個變量或者常量,抑或是傳給函數(shù)做參數(shù)時是它的值被拷貝的。這就讓值類型在任何時候只有一個享有者,從而降低復(fù)雜度。和引用類型相反,在賦值過程中引用類型會有很多享有者,其中一部分你甚至都沒意識到。在任何時間點使用引用的話會帶來一些副作用:引用的享有者會搗蛋,在背后偷偷改變這個引用。Class = 高復(fù)雜度,值 = 低復(fù)雜度。
通過利用值類型的簡約特性,咱們實現(xiàn)一下之前提過的默認(rèn)參數(shù)的設(shè)計吧。我們用的是 Brian Gesiak的value options paradigm方法:
struct ErrorOptions {
let message: String
let showArrow: Bool
let size: CGSize
let candismissByTap: Bool
let backgroundColor: UIColor
init(message: String = "Error", shouldShowArrow: Bool = true, backgroundColor: UIColor = UIColor.red, size: CGSize = CGSize.zero, canDismissByTappingAnywhere canDismiss: Bool = true) {
self.message = message
self.backgroundColor = backgroundColor
self.size = size
self.candismissByTap = canDismiss
self.showArrow = shouldShowArrow
}
}
使用上面的選項型struct(是值類型!)就使我們的POP帶上了一些VOP的色彩,如下:
protocol ErrorPopoverRenderer {
func presentError(_ errorOptions: ErrorOptions)
}
extension ErrorPopoverRenderer where Self: UIViewController {
func presentError(_ errorOptions: ErrorOptions) {
// 在這里加默認(rèn)實現(xiàn),并提供ErrorView的默認(rèn)參數(shù)。
}
}
class KrakenViewController: UIViewController, ErrorPopoverRenderer {
func methodThatHasAnError() {
//...
// 拋出error,原因是Kraken海妖今天吃人會感到不適
presentError(ErrorOptions(message: "Oh noes! I didn't get to eat the Human!", size: CGSize(width: 1000.0, height: 20)))
}
}
