鍍金池/ 教程/ iOS/ 結(jié)構(gòu)體和值類型
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

結(jié)構(gòu)體和值類型

如果你曾經(jīng)使用過 Objective-C 或者像 Ruby,Python,JavaScript 這樣的語言,可能會覺得 Swift 里的結(jié)構(gòu)體就像外星人一樣奇異。類是面向?qū)ο缶幊陶Z言中傳統(tǒng)的結(jié)構(gòu)單元。的確,和結(jié)構(gòu)體相比,Swift 的類支持實現(xiàn)繼承,(受限的)反射,析構(gòu)函數(shù)和多所有者。

既然類比結(jié)構(gòu)體強大這么多,為什么還要使用結(jié)構(gòu)體?正是因為它的使用范圍受限,使得結(jié)構(gòu)體在構(gòu)建代碼塊 (blocks) 的時候非常靈活。在本文中,你將會學(xué)習(xí)到結(jié)構(gòu)體和其他的值類型是如何大幅提高代碼的清晰度、靈活性和可讀性的。

值類型和引用類型

結(jié)構(gòu)體是值類型的,而類是引用類型的,這一行為上的細(xì)微區(qū)別造就了架構(gòu)上的無限可能。

值類型的實例,不管是在賦值或是作為函數(shù)參數(shù)的時候,都是被復(fù)制的。數(shù)字,字符串,數(shù)組,字典,枚舉,元組和結(jié)構(gòu)體都是值類型。比如:

var a = "Hello"
var b = a
b.extend(", world")
println("a: \(a); b: \(b)") // a: Hello; b: Hello, world

引用類型的實例 (主要是類) 可以有多個所有者。將一個引用賦值給一個新的變量或者傳遞給一個函數(shù)的時候,它們都指向同一個實例。這是你熟悉的對象的行為。比如:

var a = UIView()
var b = a
b.alpha = 0.5
println("a: \(a.alpha); b: \(b.alpha)") // a: 0.5; b: 0.5

這兩種類型的區(qū)別看起來似乎不大,但是選擇值類型還是選擇引用類型會給你的系統(tǒng)架構(gòu)帶來很大的差異。

培養(yǎng)我們的直覺

既然我們已經(jīng)知道了值類型和引用類型行為上的區(qū)別,現(xiàn)在讓我們討論一下使用上的區(qū)別。

Swift 將來除了對象可能還會有其他的引用類型,但是就這次討論,我們只將對象作為引用類型的范例。

我們在代碼中引用對象和我們在現(xiàn)實生活中引用對象是一樣的。編程書籍經(jīng)常使用一個現(xiàn)實世界的隱喻來教授人們面向?qū)ο缶幊蹋耗憧梢詣?chuàng)建一個 Dog 類,然后將它實例化來定義 fido (譯注:狗的名字)。如果你將 fido 在系統(tǒng)的不同部分之間傳遞,它們談?wù)摰娜匀皇峭粋€ fido。這是有意義的,因為如果你的確有一只叫 Fido 的狗,無論何時你談到它時,你將會使用它的名字進行信息傳輸 —— 而不是傳輸狗本身。你可能依賴于其他人知道 Fido 是誰。當(dāng)你使用對象的時候,你是在系統(tǒng)內(nèi)傳遞著實例的名字

值就像數(shù)據(jù)一樣。如果你向別人發(fā)出了一張費用開銷表,你發(fā)出的不是一個代表那個信息的標(biāo)簽 —— 你是在傳遞信息本身。消息接收者可以在不和任何人交流的情況下,計算總和,或者把費用寫下來供日后查閱。如果消息接收者打印了費用表并且修改了它們,這也沒有修改你自己的那張表。

一個值可以是一個數(shù)字,也許代表一個價格,或是一個類似字符串的描述。它可以是枚舉中的一個選項:這次的花費是因為一頓晚餐,還是旅行,還是材料?在指定的位置中還能包括一些其他的值,比如一個代表經(jīng)度和緯度的 CLLocationCoordinate2D 結(jié)構(gòu)體?;蛘咚梢允且恍┢渌档牧斜淼鹊取?/p>

Fido 可能在自己的地盤里來回跑叫。它也許會有特殊的行為使它區(qū)別于其他的狗。他可能會同其他的狗建立關(guān)系。你不能把 Fido 換成其他的狗 —— 你的孩子們會發(fā)現(xiàn)的!但是一張費用開銷表是獨立的。那些字符串和數(shù)字不會做任何事情。它們不會背著你私下改變,不管你用多少種不同的方式在第一列寫入了一個6,它永遠(yuǎn)只會是一個6。

這就是值類型的偉大之處。

值類型的優(yōu)勢

Objective-C 和 C 具有值類型,但是 Swift 允許你在以前不能使用的場景下使用它們。比如,泛型系統(tǒng)的抽象特性可以讓泛型類型在值和引用類型間互換。數(shù)組既可以存儲 Int 也能存儲 UIView。Swift 中的枚舉的表現(xiàn)更是大放異彩,因為它們現(xiàn)在可以攜帶某些值和方法了。結(jié)構(gòu)體可以遵守協(xié)議和指定的方法。

Swift 增強了對值類型的支持,這提供了一個巨大的機會:值類型成為了使代碼簡單的一個非常靈活的工具。你可以使用它們將孤立的、可預(yù)見組件從臃腫的類中抽離出來。默認(rèn)情況下,值類型被強制使用或者至少說被鼓勵使用在屬性上,來使得工作更清晰。

在這部分,我會描述一些鼓勵使用值類型特性的情形。值得注意的是,你也可以讓對象包含這些特性,但是語言本身決定了你沒必要去那么做。如果你在代碼里看到了一個對象,你不會期待它出現(xiàn)這些特性;然而,如果你看到了一個值類型,那么對這些特性的期望就是合理的。誠然,不是所有的值類型都有這些屬性 —— 我們稍后會討論這個 —— 但是這是合理的概括。

值類型是穩(wěn)定的

總的來說,值類型不具有行為。它是非常穩(wěn)定的。它保存數(shù)據(jù)并暴露使用這些數(shù)據(jù)進行計算的方法。其中的一些方法可能會使值類型本身發(fā)生改變,但是控制流卻還是嚴(yán)格地受控于該實例的唯一所有者。

這太好了!這下更容易思考被唯一所有者直接調(diào)用才會執(zhí)行的代碼了。

相比之下,一個對象可能將它自己注冊為一個定時器的 target。它可能會接收到來自系統(tǒng)的事件。這樣的交互意味著引用類型需要有多個擁有者。因為值類型只能有一個所有者并且沒有析構(gòu)函數(shù),所以我們也不容易寫出會對自己產(chǎn)生副作用影響的值類型。

值類型是孤立的

一個典型的值類型對任何外部組件的行為都沒有隱式的依賴。一眼看上去,與引用類型和其未知個數(shù)的所有者之間的交互相比,值類型和它的唯一所有者之間的交互要簡單多了。它是孤立的。

如果你正在獲取一個可變實例的引用,那么你對該實例的所有其他所有者都產(chǎn)生了隱式依賴:它們可能在任何時刻背著你偷偷改變它。

值類型是可交換的

因為每次將值類型賦給一個新變量的時候,該值類型都是被復(fù)制的,所以,所有的這些副本都是可交換的。

你可以安全地存儲傳遞給你的值,然后在將來就像使用值一樣使用它們。人們區(qū)分該實例和其他實例的唯一依據(jù)就是實例所包含的數(shù)據(jù)。可交換還意味著不管一個給定的值是如何進行構(gòu)造的,我們通過 == 進行比較的話,同樣的值在任何情形下都是相等的。

所以如果你使用值類型同系統(tǒng)里的組件進行通信,你可以很容易地改變你的組件圖。你有沒有一個視圖用來描繪觸摸采樣的序列?你不用觸及視圖代碼,只通過一個觸摸采樣序列的組件,就可以補償觸摸延遲,依據(jù)前一次的采樣,追加用戶手指將要移動位置的預(yù)測,然后返回一個新的序列。你可以自信地將另一個新的組件的輸出傳給視圖 —— 因為視圖分辨不出區(qū)別。

為值類型編寫單元測試不需要花哨的模擬 (mocking) 框架。你可以直接從應(yīng)用程序中的活的實例中構(gòu)造出無分別的值。上面提到的觸摸預(yù)測組件很容易進行單元測試:可預(yù)測的值類型輸入,可預(yù)測的值類型輸出等,它們都不會產(chǎn)生副作用。

這是巨大的優(yōu)勢。在以對象行為主導(dǎo)的傳統(tǒng)架構(gòu)中,你必須要測試與正被測試的對象的交互以及與系統(tǒng)的其他部分之間的交互。那通常意味著笨拙的模擬,或者為了建立那樣的關(guān)系而添加了大量的設(shè)置代碼。值類型是孤立的,穩(wěn)定的和可交換的,所以你可以直接地構(gòu)建一個值,調(diào)用一個方法,然后檢查輸出。更簡單的測試,更大的覆蓋范圍意味著代碼更容易修改。

不是所有的值類型都有這些特性

雖然值類型的結(jié)構(gòu)鼓勵這些特性,但是你也可以使值類型違反這些特性。

包含不是由所有者調(diào)用而執(zhí)行的代碼的值類型,通常是不可預(yù)測的,并且通常情況下應(yīng)該是要避免使用的。比如:一個結(jié)構(gòu)體的構(gòu)造函數(shù)可能調(diào)用 dispatch_after 來安排一些工作。但是將該結(jié)構(gòu)體的一個實例傳遞給函數(shù)的時候,因為進行了一次復(fù)制,就會不經(jīng)意地重復(fù)做這件事情。值類型應(yīng)該是穩(wěn)定的。

包含引用的值類型通常都不是孤立的,并且應(yīng)該避免使用它們:它們攜帶了對那個對象的所有其他所有者的依賴。這些值類型也不是易交換的,因為外部引用可能以復(fù)雜的方式與系統(tǒng)的其他部分相聯(lián)系。

對象們的對象

我當(dāng)然不是建議使用穩(wěn)定的值類型來構(gòu)建所有的事情。

更精確地講,對象也是有用的,因為它們不包含我上面所說的屬性。一個對象在系統(tǒng)中扮演著實體的角色。它有身份,具有行為,通常也是獨立的。

那種行為通常復(fù)雜并且不容易思考,但是其中一些細(xì)節(jié)通??梢杂珊唵蔚闹岛凸铝⒌睾瘮?shù)調(diào)用表現(xiàn)出來。那些細(xì)節(jié)不會和對象的復(fù)雜的行為交織在一起。通過將它們分離,對象的行為就會變得清晰。

可以將對象看成是一個薄的、命令式的層,它位于可預(yù)測的、純值類型的層之上。

對象維護通過值來定義的狀態(tài),但是那些值其實是獨立于對象被設(shè)定和操作的。值層 (value layer) 實際上沒有狀態(tài);它僅僅用來表示和變換數(shù)據(jù)。那些數(shù)據(jù)作為狀態(tài)可能有 (也可能沒有) 高層的意味,這取決于使用值的上下文。

對象就像 I/O 和網(wǎng)絡(luò)一樣會有副作用,但是數(shù)據(jù)、計算和重要的決策最后都驅(qū)使這些副作用存在于值類型層。對象就像薄膜,通過這一層薄膜,將那些純凈的、可預(yù)測的結(jié)果引入副作用的不純凈的領(lǐng)域。

對象可以和其他對象通信,但是通常它們發(fā)送的是值,而不是引用,除非它們確實想要和外部不可或缺的層創(chuàng)建一個持久的連接。

值類型的總結(jié)

值類型能夠使你構(gòu)建非常清晰,簡單,更容易測試的典型架構(gòu)。

值類型與外部狀態(tài)通常沒有依賴或者只有很少的依賴,所以當(dāng)你思考它們的時候,你只需要考慮很少的一部分。

值類型是內(nèi)在可組合的和可重用的,因為它們是可交換的。

最后,一個值類型層允許你從應(yīng)用程序穩(wěn)定的業(yè)務(wù)邏輯中獨立出活躍的行為元素。代碼越穩(wěn)定,你的系統(tǒng)會變得越容易測試和修改。

參考文獻

上一篇:Swift 方法的多面性下一篇:照片框架