在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 值對象
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
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)試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
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ù)
導航應(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 容器
學無止境
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 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

值對象

在這篇文章里,我們會看看如何用 Objective-C 寫值對象 (value objects)。在編寫中,我們會接觸到 Objective-C 中的一些重要的接口和方法。所謂值對象,就是指那些能夠被判等的,持有某些數(shù)值的對象 (對它們判等時我們看重值是否相等,而對是否是同一個對象并不是那么關(guān)心)。通常來說,值對象會被用作 model 對象。比如像下面的 Person 對象就是一個簡單的例子:

@interface Person : NSObject

@property (nonatomic,copy) NSString* name;
@property (nonatomic) NSDate* birthDate;
@property (nonatomic) NSUInteger numberOfKids;

@end

創(chuàng)造這樣的對象可以說是我們?nèi)諒鸵蝗盏幕竟ぷ髁?,雖然這些對象表面上看起來相當簡單,但是其中暗藏玄機。

我們中有很多人會教條主義地認為這類對象就應(yīng)該是不可變的 (immutable)。一旦你創(chuàng)建了一個 Person 對象,它就不可能在做任何改變了。我們在稍后會在本話題中涉及到可變性的問題。

屬性

首先我們來看看定義一個 Person 時所用到的屬性。創(chuàng)建屬性是一件機械化的工作:對于一般的屬性,你會將它們聲明為 nonatomic。默認情況下,對象屬性是 strong 的,標量屬性是 assign 的。但是有一個例外,就是對于具有可變副本的屬性,我們傾向于將其聲明為 copy。比如說,name 屬性的類型是 NSString,有可能有人創(chuàng)建了一個 Person 對象,并且給這個屬性賦了一個 NSMutableString 的名字值。然后過了一會兒,這個可變字符串被變更了。如果我們的屬性不是 copy 而是 strong 的話,隨著可變字符串的改變,我們的 Person 對象也將發(fā)生改變,這不是我們希望發(fā)生的。對于類似數(shù)組或者字典這樣的容器類來說,也是這樣的情況。

要注意的是這里的 copy 是淺拷貝;容器里還是會包含可變對象。比如,如果你有一個 NSMutableArray* a,其中有一些 NSMutableDictionary 的元素,那么 [a copy] 將返回一個不可變的數(shù)組,但是里面的元素依然是同樣的 NSMutableDictionary 對象。我們稍后會看到,對于不可變對象的 copy 是沒有成本的,只會增加引用計數(shù)而已。

因為屬性是相對最近才加入到 Objective-C 的,所以在較老的代碼中,你有可能不會見到屬性。取而代之,可能會有自定義的 getter 和 setter,或者直接是實例變量。對于最近的代碼,看起來大家都贊同還是使用屬性比較好,這也正是我們所推薦的。

擴展閱讀

NSString: copy 還是 retain

初始化方法 (Initializers)

如果我們需要的是不可變對象,那么我們要確保它在被創(chuàng)建后就不能再被更改。我們可以通過使用初始化方法并且在接口中將我們的屬性聲明為 readonly 來實現(xiàn)這一點。我們的接口看起來是這樣的:

@interface Person : NSObject

@property (nonatomic,readonly) NSString* name;
@property (nonatomic,readonly) NSDate* birthDate;
@property (nonatomic,readonly) NSUInteger numberOfKids;

- (instancetype)initWithName:(NSString*)name
                   birthDate:(NSDate*)birthDate
                numberOfKids:(NSUInteger)numberOfKids;

@end

在初始化方法的實現(xiàn)中,我們必須使用實例變量,而不是屬性。

編者注 在初始化方法或者是 dealloc 中最好不要使用屬性,因為你無法確定 `self` 到底是不是確實調(diào)用的是你想要的實例

@implementation Person

- (instancetype)initWithName:(NSString*)name
                   birthDate:(NSDate*)birthDate
                numberOfKids:(NSUInteger)numberOfKids
{
    self = [super init];
    if (self) {
        _name = [name copy];
        _birthDate = birthDate;
        _numberOfKids = numberOfKids;
    }
    return self;
}

@end

現(xiàn)在我們就可以構(gòu)建新的 Person 對象,并且不能再對它們做改變了。這一點很有幫助,在寫和 Person 對象一起工作的其他類的時候,我們知道這些值是不會發(fā)生改變的。注意這里 copy 不再是接口的一部分了,現(xiàn)在它只和實現(xiàn)的細節(jié)相關(guān)。

判等

要比較相等,我們需要實現(xiàn) isEqual: 方法。我們希望 isEqual: 方法僅在所有屬性都相等的時候返回真。Mike Ash 的 Implement Equality and Hashing 和 NSHipster 的 Equality 為我們很好地闡述了如何實現(xiàn)。首先,我們需要寫一個 isEqual: 方法:

- (BOOL)isEqual:(id)obj
{
    if(![obj isKindOfClass:[Person class]]) return NO;

    Person* other = (Person*)obj;

    BOOL nameIsEqual = self.name == other.name || [self.name isEqual:other.name];
    BOOL birthDateIsEqual = self.birthDate == other.birthDate || [self.birthDate isEqual:other.birthDate];
    BOOL numberOfKidsIsEqual = self.numberOfKids == other.numberOfKids;
    return nameIsEqual && birthDateIsEqual && numberOfKidsIsEqual;
}

如上,我們先檢查輸入和自身是否是同樣的類。如果不是的話,那肯定就不相等了。然后對每一個對象屬性,判斷其指針是否相等。|| 操作符的操作看起來好像是不必要的,但是如果我們需要處理兩個屬性都是 nil 的情形的話,它能夠正確地返回 YES。比較像 NSUInteger 這樣的標量是否相等時,則只需要使用 == 就可以了。

還有一件事情值得一提:這里我們將不同的屬性比較的結(jié)果分開存儲到了它們自己的 BOOL 中。在實踐中,可能將它們放到一個大的判斷語句中會更好,因為如果這么做的話你就可以避免一些不必要的取值和比較了。比如在上面的例子中,如果 name 已經(jīng)不相等了的話,我們就沒有必要再檢查其他的屬性了。將所有判斷合并到一個 if 語句中我們可以自動地得到這樣的優(yōu)化。

接下來,按照文檔所說,我們還需要實現(xiàn)一個 hash 函數(shù)。蘋果如是說:

如果兩個對象是相等的,那么它們必須有同樣的 hash 值。如果你在一個子類里定義了 isEqual: 方法,并且打算將這個子類的實例放到集合類中的話,那么你一定要確保你也在你的子類里定義了 hash 方法,這是非常重要的。

首先,我們來看看如果不實現(xiàn) hash 方法的話,下面的代碼會發(fā)生什么;

Person* p1 = [[Person alloc] initWithName:name birthDate:start numberOfKids:0];
Person* p2 = [[Person alloc] initWithName:name birthDate:start numberOfKids:0];
NSDictionary* dict = @{p1: @"one", p2: @"two"};
NSLog(@"%@", dict);

第一次運行上面的代碼是,一切都很正常,字典中有兩個條目。但是第二次運行的時候卻只剩一個了。事情變得不可預(yù)測,所以我們還是按照文檔說的來做吧。

可能你還記得你在計算機科學課程中學到過,編寫一個好的 hash 函數(shù)是一件不太容易的事情。好的 hash 函數(shù)需要兼?zhèn)?em>確定性和均布性。確定性需要保證對于同樣的輸入總是能生成同樣的 hash 值。均布性需要保證輸出的結(jié)果要在輸出范圍內(nèi)均勻地對應(yīng)輸入。你的輸出分布越均勻,就意味著當你將這些對象用在集合中時,性能會越好。

首先我們得搞清楚到底發(fā)生了什么。讓我們來看看沒有實現(xiàn) hash 函數(shù)時候的情況下,使用 Person 對象作為字典的鍵時的情況:

NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];

NSDate* start = [NSDate date];
for (int i = 0; i < 50000; i++) {
    NSString* name = randomString();
    Person* p = [[Person alloc] initWithName:name birthDate:[NSDate date] numberOfKids:i++];
    [dictionary setObject:@"value" forKey:p];
}
NSLog(@"%f", [[NSDate date] timeIntervalSinceDate:start]);

這在我的機子上花了 29 秒時間來執(zhí)行。作為對比,當我們實現(xiàn)一個基本的 hash 方法的時候,同樣的代碼只花了 0.4 秒。這并不是精確的性能測試,但是卻足以告訴我們實現(xiàn)一個正確的 hash 函數(shù)的重要性。對于 Person 這個類來說,我們可以從這樣一個 hash 函數(shù)開始:

- (NSUInteger)hash
{
    return self.name.hash ^ self.birthDate.hash ^ self.numberOfKids;
}

這將從我們的屬性中取出三個 hash 值,然后將它們做 XOR (異或) 操作。在這里,這個方法對我們的目標來說已經(jīng)足夠好了,因為對于短字符串 (以前這個上限是 96 個字符,不過現(xiàn)在不是這樣了,參見 CFString.c 中 hash 的部分) 來說,NSString 的 hash 函數(shù)表現(xiàn)很好。對于更正式的 hash 算法,hash 函數(shù)應(yīng)該依賴于你所擁有的數(shù)據(jù)。這在 Mike Ash 的文章其他一些地方有所涉及。

hash 文檔中,有下面這樣一段話:

如果一個被插入集合類的可變對象是依據(jù)其 hash 值來決定其在集合中的位置的話,這個對象的 hash 函數(shù)所返回的值在該對象存在于集合中時是不允許改變的。因此,要么使用一個和對象內(nèi)部 狀態(tài)無關(guān)的 hash 函數(shù),要么確保在對象處于集合中時其內(nèi)部狀態(tài)不發(fā)生改變。比如說,一個可 變字典可以被放到一個 hash table 中,但是只要這個字典還在 hash table 中時,你就不能 更改它。(注意,要知道一個給定對象是不是存在于某個集合中是一件很困難的事情。)

這也是你需要確保對象的不可變性的另一個重要原因。只要確保了這一點,你就不必再擔心這個問題了。

擴展閱讀

NSCopying

為了讓我們的對象更有用,我們最好實現(xiàn)一下 NSCopying 接口。這能夠使我們能在容器類中使用它們。對于我們的類的一個可變的變體,可以這么實現(xiàn) NSCopying

- (id)copyWithZone:(NSZone *)zone
{
    Person* p = [[Person allocWithZone:zone] initWithName:self.name
                                                birthDate:self.birthDate
                                             numberOfKids:self.numberOfKids];
    return p;
}

然而,在接口的文檔中,他們提到了另一種實現(xiàn) NSCopying 的方式:

對于不可變的類和其內(nèi)容來說,NSCopying 的實現(xiàn)應(yīng)該保持原來的對象,而不是創(chuàng)建一份新的拷貝。

所以,對于我們的不可變版本,我們只需要這樣就夠了:

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

NSCoding

如果我們想要序列化對象,我們可以實現(xiàn) NSCoding。這個接口中有兩個 required 的方法:

- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder

實現(xiàn)這個和實現(xiàn)判等方法同樣直接,也同樣機械化:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        _name = [aDecoder decodeObjectForKey:@"name"];
        _birthDate = [aDecoder decodeObjectForKey:@"birthDate"];
        _numberOfKids = [aDecoder decodeIntegerForKey:@"numberOfKids"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:self.birthDate forKey:@"birthDate"];
    [aCoder encodeInteger:self.numberOfKids forKey:@"numberOfKids"];
}

可以在 NSHipsterMike Ash 的博客上了解這方面的更多內(nèi)容。順帶一提,在處理比如來自網(wǎng)絡(luò)的數(shù)據(jù)這樣不信任的來源的數(shù)據(jù)時,不要使用 NSCoding,因為數(shù)據(jù)可能被篡改過。通過修改歸檔的數(shù)據(jù),很容易實施遠程代碼運行攻擊。在處理這樣的數(shù)據(jù)時,應(yīng)該使用 NSSecureCoding 或者像 JSON 這樣的自定義格式

Mantle

現(xiàn)在,我們還有一個問題:這些能自動化么?答案是能。一種方式是1代碼生成,但是幸運的是有一種更好的替代:Mantle。Mantle 使用自舉 (introspection) 的方法生成 isEqual:hash。另外,它還提供了一些幫助你創(chuàng)建字典的方法,它們可以被用來讀寫 JSON。當然,一般來說在運行時做這些不如你自己寫起來高效,但是另一方面,自動處理這個流程的話犯錯的可能性要小得多。

可變性

在 C 中可變值是默認的,其實在 Objective-C 中也是這樣的。一方面,這非常方便,因為你可以在任何時候改變它。在構(gòu)建相對小的系統(tǒng)外,這一般不成問題。但是正如我們中很多人的經(jīng)驗一樣,在構(gòu)建較大的系統(tǒng)時,使用不可變的對象會容易得多。在 Objective-C 中,我們一直是使用不可變對象的,現(xiàn)在其他的語言也逐漸開始添加不可變對象了。

我們來看看使用可變對象的兩個問題。其中一個是它們有可能在你不希望的時候發(fā)生改變,另一個是在多線程中使用可變對象。

不希望的改變

假設(shè)我們有一個 table view controller,其中有一個 people 屬性:

@interface ViewController : UITableViewController

@property (nonatomic) NSArray* people;

@end

在實現(xiàn)中,我們僅僅把數(shù)組中的每個元素映射到一個 cell 中:

 - (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView
 {
     return 1;
 }

 - (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
 {
     return self.people.count;
 }

現(xiàn)在,在設(shè)定上面的 view controller 中,我們的代碼可能是這樣的:

self.items = [NSMutableArray array];
[self loadItems]; // Add 100 items to the array
tableVC.people = self.items;
[self.navigationController pushViewController:tableVC animated:YES];

table view 將開始執(zhí)行 tableView:numberOfRowsInSection: 之類的方法,一開始,一切都 OK。但是假設(shè)在某個時候,我們進行了這樣的操作:

[self.items removeObjectAtIndex:1];

這改變了 items 數(shù)組,但是它同時也改變了我們的 table view controller 中的 people 數(shù)組。如果我們沒有進一步地同 table view controller 進行通訊的話,table view 還會認為有 100 個元素需要顯示,然而我們的數(shù)組卻只包括 99 個元素。你大概知道我們會面臨怎樣的窘境了。在這里,我們應(yīng)該做的是將屬性聲明為 copy

 @interface ViewController : UITableViewController

 @property (nonatomic, copy) NSArray* items;

 @end

現(xiàn)在,我們在將可變數(shù)組設(shè)置給 items 的時候,會生成一個不可變的 copy。如果我們設(shè)定的是一個通常 (不可變) 的數(shù)組,那么 copy 操作是沒有開銷的,它僅僅只是增加了引用計數(shù)。

多線程

假設(shè)我們有一個用來表示銀行賬號的可變對象 Account,其有一個 transfer:to: 方法:

- (void)transfer:(double)amount to:(Account*)otherAccount
{
    self.balance = self.balance - amount;
    otherAccount.balance = otherAccount.balance + amount;
}

多線程的代碼可能會在以很多方式掛掉。比如線程 A 要讀取 self.balance,線程 B 有可能在 A 繼續(xù)之前就修改了這個值。對于這其中可能造成的各種風險,請參看我們的話題二。

如果我們使用的是不可變對象的話,事情就簡單多了。我們不能改變它們,這個規(guī)則迫使我們在一個完全不一樣的層級上來提供可變性,這將使代碼簡單得多。

緩存

不可變性還能在緩存數(shù)值方面幫助我們。比如,假設(shè)你已經(jīng)將一個 markdown 文檔解析成一個帶有表示各種不同元素的結(jié)點的樹結(jié)構(gòu)了。在你想從這個結(jié)構(gòu)中生成 HTML 的時候,因為你知道這些元素都不會再改變,所以可以該將這些值都緩存下來。如果你的對象是可變的,你可能就需要每次都從頭開始生成 HTML,或者是為每一個對象做構(gòu)建優(yōu)化和觀察操作。如果是不可變的話,你就不必擔心緩存會失效了。當然,這可能會帶來性能的下降,但是在絕大多數(shù)情況下,簡單帶來的好處相比于那一點輕微的性能下降是值得的。

其他語言的不可變性

不可變對象是從像 Haskell 這樣的函數(shù)式編程語言中借鑒過來的概念。在 Haskell 中,值默認都是不可變的。Haskell 程序一般都有一個單純函數(shù)式 (purely functional) 作為核心,在其中沒有可變對象,沒有狀態(tài),也沒有像 I/O 這樣的副作用。

在 Objective-C 程序中我們可以借鑒這些。在任何可能的地方使用不可變的對象,我們的程序會變得容易測試得多。Gary Bernhardt 做了一個很棒的演講,向我們展示了使用不可變對象如何幫助我們開發(fā)更好的軟件。在演講中他用的是 Ruby,但是在 Objective-C 中,概念其實是相通的。

擴展閱讀