《Effective Objective-C 2.0編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法》讀書小記

第一條:了解Objective-C語言的起源

Objective-C語言是一門面向?qū)ο蟮恼Z言滥壕,與C++而账、Java等有所相似性雄。然而在語法上娄周,則有許多差別涕侈。這個(gè)差別產(chǎn)生的原因在于,Objective-C是由Smalltalk演化而來煤辨,直接借用了或者說繼承了很多后者的思想裳涛,使用“消息結(jié)構(gòu)”(messaging stricture)而非“函數(shù)調(diào)用”(function calling)。這個(gè)特性使得Objective-C語言众辨,在運(yùn)行時(shí)才回去查找所要執(zhí)行的方法端三。實(shí)際上,編譯器甚至都不關(guān)心接受消息的對(duì)象是何種類型泻轰。

Smalltalk之父Alan Kay曾經(jīng)說過

I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is “messaging” – that is what the kernel of Smalltalk/Squeak is all about.

對(duì)象和類只是消息的載體技肩。面向?qū)ο笏枷雽⒊绦虬垂δ芎瓦壿嫹殖闪巳舾蓚€(gè)類,每個(gè)類包含自己的代碼浮声、功能實(shí)現(xiàn)并提供對(duì)外接口虚婿,以黑箱模式運(yùn)行,使得外部無需了解內(nèi)部而協(xié)同使用泳挥,分解了復(fù)雜度并控制在一個(gè)個(gè)較小規(guī)模中然痊,以消息作為其間所有的協(xié)作方式。消息才是Objective-C的核心屉符。
Objective-C語言是C的“超集”(superset)剧浸,亦或者說“Objective-C == C + runtime”。
所有C語言中的功能在編寫Objective-C代碼的時(shí)候依然適用矗钟。所以唆香,掌握C語言有助于寫出高效的Objective-C代碼。而同時(shí)由于許多跨平臺(tái)的第三方庫是用C++編寫的吨艇,我們最好也要了解一些C++躬它。

第二條:在類的頭文件中盡量少引入其他頭文件

在Objective-C中,以類名做文件名东涡,分別創(chuàng)建兩個(gè)文件冯吓,頭文件后綴使用.h倘待,實(shí)現(xiàn)文件后綴使用.m。
我們?cè)谙胍褂闷渌募臅r(shí)候组贺,往往并不需要知曉其他文件的全部細(xì)節(jié)凸舵,只需要知道有一個(gè)類名就可以。我們使用@class方法去實(shí)現(xiàn)

使用@class時(shí)失尖,若需要引入多個(gè)啊奄,標(biāo)準(zhǔn)寫法為@class classA,ClassB雹仿,ClassC

這種方法叫做“前向聲明”增热。
在實(shí)現(xiàn)文件中,需要實(shí)現(xiàn)的時(shí)候胧辽,就可以使用#import ClassA寫法峻仇。
這樣使用的原因在于:減少了很多根本用不到的內(nèi)容,減少編譯時(shí)間邑商,提升性能摄咆;同時(shí)也可以避免兩個(gè)類互相引用的問題,避免“循環(huán)引用”人断。
假如在某些情況下吭从,必須要在.h文件中使用#import時(shí),比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議恶迈,這種情況下涩金,盡量吧“該類遵循某個(gè)協(xié)議”這條聲明移到“class-continuation分類”中。若是還不行暇仲,就把協(xié)議單獨(dú)放到一個(gè)頭文件中步做,然后將其引入。

第三條:多用字面量語法奈附,少用與之等價(jià)的方法

字面數(shù)值

將整數(shù)全度、浮點(diǎn)數(shù)、布爾值放入Objective-C對(duì)象中斥滤,我們一般需要NSNumber将鸵,有兩種創(chuàng)建方法

NSNumber *someNumber = [NSNumber numberWithInt:1];
NSNumber *someNumber = @1; 

字面量數(shù)組

NSArray *animals = [NSArray arraywithObjects:@"cat",@"dog",@"mouse",@"badger",nil];
NSArray *animals = @[@"cat",@"dog",@"mouse",@"badger",nil];

不過,我們使用這種方法的時(shí)候佑颇,數(shù)組元素不能為nil顶掉,因?yàn)閯?chuàng)建數(shù)組的時(shí)候,到nil為止挑胸。使用第一種方法創(chuàng)建的數(shù)組一喘,可能會(huì)缺少元素,而使用第二種即字面量方法創(chuàng)建的數(shù)組嗜暴,會(huì)直接拋出異常凸克。這也表明了使用字面量會(huì)更加安全,拋出異常令程序終止執(zhí)行闷沥,比創(chuàng)建好一個(gè)數(shù)組后發(fā)現(xiàn)少了要好萎战。我們可以通過異常直接發(fā)現(xiàn)這個(gè)錯(cuò)誤。

字面量字典

NSDictionary *personData = [NSDictionary dictionaryWithObjectsAndKey:@"Matt",@"firstName",@"galloway",@"lastName",[NSNumber numberWithInt:28],@"age",nil];
NSDictionary *personData = 
@{@"firstName":@"Matt",
  @"lastName":@"Galloway"
  @"age":@28};

這里的優(yōu)勢顯而易見舆逃,鍵出現(xiàn)在對(duì)象之前蚂维,更加好理解。

局限性

除了字符串意外路狮,所有創(chuàng)建出來的對(duì)象虫啥,必須屬于Foundation框架才行。并且奄妨,創(chuàng)建出來的字符串涂籽、數(shù)組、字典對(duì)象都是不可變的砸抛。假如想使用可變版本的评雌,需要mutableCopy一份。

第四條:多用類型常量直焙,少用#define預(yù)處理指令

有時(shí)候景东,我們需要使用某個(gè)常量的時(shí)候,會(huì)這么使用

#define ANIMATION_DURATION 0.3

但是使用這個(gè)方法有時(shí)候會(huì)遇到一個(gè)問題:它會(huì)將所有遇到的ANIMATION_DURATION都替換成0.3.
為了避免這種情況

static const NSTimeInterval kANimationDuration = 0.3;

此外奔誓,我們還要注意常量的名稱斤吐,常用的做法是,若常量局限于某“實(shí)現(xiàn)文件”內(nèi)厨喂,則在前面加k和措;若是常量在類之外課件,則通常以類名為前綴杯聚。
常量一定要同時(shí)使用static與const來聲明臼婆。
假如要使得外界可見,一般我們會(huì)將其放入一個(gè)“全局符號(hào)表”中幌绍。

.h
extern NSString *const EOCStringConstant;
.m
NSString *const EOCStringConstant = @"VALUE";

extern關(guān)鍵字會(huì)告訴編譯器颁褂,在全局符號(hào)表中將會(huì)有一個(gè)名叫EOCStringConstant的符號(hào),也就是說傀广,編譯器無需查看其定義颁独,即允許代碼使用此常量。因?yàn)樗牢北?dāng)鏈接成二進(jìn)制文件之后誓酒,肯定能找到這個(gè)常量。
此類常量必須要定義,且只能定義一次靠柑。通常將其定義的與聲明該常量的頭文件相關(guān)的實(shí)現(xiàn)文件中寨辩。由實(shí)現(xiàn)文件生成目標(biāo)文件時(shí),編譯器會(huì)在“數(shù)據(jù)段”為字符串分類存儲(chǔ)空間歼冰。鏈接器會(huì)把此目標(biāo)文件與其他目標(biāo)文件相鏈接靡狞,以生成最終的二進(jìn)制文件,凡是用到EOCStringConstant這個(gè)全局符號(hào)的地方隔嫡,鏈接器都能將其解析甸怕。
這樣要優(yōu)于使用#define預(yù)處理指令,因?yàn)榫幾g器會(huì)確保常量值不變腮恩。一旦在一處文件中定義好梢杭,即可隨處使用;而采用預(yù)處理指令所定義的常量可能會(huì)無意中遭人修改秸滴,從而導(dǎo)致應(yīng)用程序各個(gè)部分所使用的值互不相同武契。

第5條:用枚舉表示狀態(tài)、選項(xiàng)缸榛、狀態(tài)碼

應(yīng)用枚舉來表示狀態(tài)機(jī)的狀態(tài)吝羞、傳遞給方法的選項(xiàng)及狀態(tài)碼等值,給這些值起個(gè)易懂的名字内颗。
如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型钧排,而多個(gè)選項(xiàng)又可同時(shí)使用,那么就將各選項(xiàng)的值定義為2的冪均澳,以便通過按位或操作將其組合起來恨溜。
在處理枚類型的switch語句中不要出現(xiàn)default分支。這樣的話找前,加入新枚舉之后糟袁,編譯器就會(huì)提示開發(fā)者:switch語句并未處理所有枚舉。

我們知道躺盛,在Objective-C中项戴,“對(duì)象”(object)就是最基本的構(gòu)造單元,開發(fā)者通過對(duì)象來存儲(chǔ)和傳遞數(shù)據(jù)槽惫。在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)周叮。

第6條:理解“屬性”這一概念

“屬性”(property)是Objective-C的一個(gè)特征,用于封裝對(duì)象中的數(shù)據(jù)界斜。Objective-C對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量仿耽。實(shí)例變量一般通過“存取方法”來訪問。其中各薇,“獲取方法”(getter)用于讀取變量值项贺,而“設(shè)置方法”(setter)用于寫入變量值。
Objective-C會(huì)把實(shí)例變量當(dāng)做一種存儲(chǔ)偏移量的特殊變量,交給“類對(duì)象”保管开缎。偏移量會(huì)在運(yùn)行期查找棕叫,如果類的定義變了,那么存儲(chǔ)的偏移量也就變了啥箭。這樣的話谍珊,無論何時(shí)訪問實(shí)例變量,總能找到正確的偏移量急侥。甚至可以在運(yùn)行期向類中新增實(shí)例變量
當(dāng)然侮邀,我們還可以使用屬性的方法去解決這個(gè)問題坏怪,也就是不不要直接訪問實(shí)例變量,而通過存取方法來做绊茧。

@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

與下面等價(jià)

@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end

使用“點(diǎn)語法”的效果與直接調(diào)用存取方法相同铝宵。

EOCPerson *aPerson = [Person new];
aPerson.firstName = @"Bob";//Same as
[aPerson setFirstName:@"Bob"];
NSString *lastName = aPerson.lastName;//same as
NSString *lastName = [aPerson lastName];

屬性特質(zhì)

屬性可以分為四類:

原子性

使用同步鎖,為nonatomic(默認(rèn))华畏,不適用則為atomic
iOS程序中鹏秋,絕大部分都是nonatomic,atomic會(huì)帶來非常大的性能問題亡笑。而且往往也達(dá)不到“線程安全”侣夷,如果要實(shí)現(xiàn),還需要更加深層的鎖定機(jī)制仑乌。

讀寫權(quán)限

具備readwrite特質(zhì)的屬性擁有setter和getter方法百拓。編譯器會(huì)自動(dòng)生成
具備readonly特質(zhì)的屬性僅擁有獲取方法。你可以利用此特質(zhì)晰甚,把某個(gè)屬性對(duì)外公開為只讀屬性衙传,然后在“class-continuation分類”中將其重新定義為讀寫屬性。

內(nèi)存管理語義

assign(默認(rèn)):assign用于值類型厕九,如int蓖捶、float、double和NSInteger扁远,CGFloat等表示單純的復(fù)制俊鱼。還包括不存在所有權(quán)關(guān)系的對(duì)象,比如常見的delegate穿香。
strong:strong是在IOS引入ARC的時(shí)候引入的關(guān)鍵字亭引,是retain的一個(gè)可選的替代。表示實(shí)例變量對(duì)傳入的對(duì)象要有所有權(quán)關(guān)系皮获,即強(qiáng)引用焙蚓。strong跟retain的意思相同并產(chǎn)生相同的代碼,但是語意上更好更能體現(xiàn)對(duì)象的關(guān)系。
weak:在setter方法中购公,需要對(duì)傳入的對(duì)象不進(jìn)行引用計(jì)數(shù)加1的操作萌京。
unsafe_unretailed 此特質(zhì)的語義和assign相同,但適用于“對(duì)象類型”宏浩,該特質(zhì)表達(dá)一種“非擁有關(guān)系”知残,當(dāng)對(duì)象遭到摧毀時(shí),屬性值不會(huì)自動(dòng)清空比庄,這一點(diǎn)與weak有區(qū)別求妹。
copy:與strong類似,但區(qū)別在于實(shí)例變量是對(duì)傳入對(duì)象的副本擁有所有權(quán)佳窑,而非對(duì)象本身制恍。

方法名

getter=<name>指定“獲取方法“的方法名。
setter=<name>指定“設(shè)置方法“的方法名神凑。

第7條:在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量

由于不經(jīng)過Objective-C的“方法派發(fā)”步驟净神,所以直接訪問實(shí)例變量的速度回比較快。編譯器所生成的代碼會(huì)直接訪問對(duì)象實(shí)例變量的那塊內(nèi)存溉委;直接訪問實(shí)例變量時(shí)鹃唯,不會(huì)調(diào)用其“設(shè)置方法”,這就繞過了為相關(guān)屬性所定義的“內(nèi)存管理語義”瓣喊;直接訪問實(shí)例變量坡慌,不會(huì)觸發(fā)“鍵值觀測(KVO)”通知;通過屬性來訪問會(huì)有助于排查與之相關(guān)的錯(cuò)誤型宝,因?yàn)榭梢栽凇矮@取方法”和“設(shè)置方法”中新增斷點(diǎn)八匠,監(jiān)控該屬性的調(diào)用者及其訪問時(shí)機(jī)。
在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí)趴酣,應(yīng)該直接通過實(shí)例變量來讀梨树,而寫入數(shù)據(jù)時(shí),則應(yīng)使用屬性來寫岖寞。
在初始化方法和dealloc方法中抡四,總是應(yīng)該使用實(shí)例變量(self.xxx)來讀寫數(shù)據(jù)。
有時(shí)會(huì)使用懶加載技術(shù)配置某份數(shù)據(jù)仗谆,這種情況下指巡,需要使用屬性來讀取數(shù)據(jù)。

第8條:理解“對(duì)象等同性”這一概念

按照==操作符比較出來的結(jié)果未必是我們想要的隶垮,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身藻雪,而不是其所指的對(duì)象。應(yīng)該使用NSObject協(xié)議中聲明的“isEqual”方法狸吞,來判斷兩個(gè)對(duì)象的等同性勉耀。
在NSString中有相應(yīng)的比較方法指煎,叫“isEqualToString:”。該方法比“isEqual”要快便斥,后者還要執(zhí)行額外的步驟至壤,以判斷對(duì)象的類型。
NSObject協(xié)議中有兩個(gè)用于判斷等同性的關(guān)鍵方法:

- (BOOL)isEqual:(id)object;
- (NSUInteger)hash;

NSObject類對(duì)這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)其“內(nèi)存地址”完全相等時(shí)枢纠,兩個(gè)對(duì)象才相等像街。如果“isEqual”方法判定兩個(gè)對(duì)象相等,那么其hash方法必須返回同一個(gè)值晋渺,但是镰绎,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值,那么“isEqual”方法未必會(huì)認(rèn)為兩者相等些举。
在編寫hash判斷方法時(shí)跟狱,要格外的注意性能問題。因?yàn)閏ollection在檢索hash table的時(shí)候户魏,會(huì)用對(duì)象的哈希碼做索引。江入某個(gè)collection使用set實(shí)現(xiàn)的挪挤,那么set可能會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組中叼丑。在向set中添加新對(duì)象時(shí),要根據(jù)其哈希碼找到與之相關(guān)的那個(gè)數(shù)組扛门,依次檢查檢查其中各個(gè)元素鸠信,看數(shù)組中以后的對(duì)象時(shí)候和將要添加的新對(duì)象相等。如果相等论寨,那就說明要添加的對(duì)象已經(jīng)在set里面了星立。由此可知,如果令每個(gè)對(duì)象都返回相同的哈希碼葬凳,那么在set中已有100000個(gè)對(duì)象的情況下绰垂,若是繼續(xù)向其中添加對(duì)象,則需將這1000000個(gè)對(duì)象全部掃描一遍火焰。
若是計(jì)算哈希碼劲装,可以使用如下方法

-  (NSUIntger)hash {
  NSUIntger firstNameHash = [_firstName hash];
  NSUIntger lastNameHash = [_lastName hash];
  NSUIntger ageHash = _age;
  return firstNameHash ^ lastNameHash ^ ageHash;
}

這種方法既能保持較高效率,又能使生成的哈希碼至少位于一定范圍內(nèi)昌简,而不會(huì)過于頻繁的重復(fù)占业。
當(dāng)然,我們也可以自己實(shí)現(xiàn)等同性判斷方法纯赎,在寫這樣的方法時(shí)谦疾,也應(yīng)一并覆寫“isEqual”方法。

- (BOOL)isEqualToPerson:(EOCPerson *)otherPerson {
  if(self == object )  return YES;
  if(![_first那么isEqualToString:otherPerson.firstName]){
    return NO;  
  }
  return YES;
}
- (BOOL)isEqual:(id)object {
  if([self class] == [object class]) {
    return [self isEqualToPerson:(EOCPerson *)object];
  }else{
    return [super isEqual:object];
  }
}

不要盲目的逐個(gè)監(jiān)測每條屬性犬金,而是應(yīng)該依照具體需求來制定檢測方案念恍。比如判斷數(shù)組六剥,我們就可以先判斷數(shù)組的所含對(duì)象的數(shù)量是否相等。
如果把對(duì)象放入collection之后改變其內(nèi)容樊诺,可能會(huì)造成嚴(yán)重的后果仗考,要小心對(duì)待

第9條:以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)

“類族”是一種很有用的模式,可以隱藏“抽象基類”背后的實(shí)現(xiàn)細(xì)節(jié)词爬⊥菏龋“工廠模式”是創(chuàng)建類族的辦法之一。
如果對(duì)象所屬的類位于某個(gè)類族中顿膨,那么在查詢其類型信息時(shí)就要當(dāng)心了世澜。你可能覺得創(chuàng)建了某個(gè)類的實(shí)現(xiàn),然而實(shí)際上穿件的卻是其子類的實(shí)例板惑。
在系統(tǒng)框架中枫攀,很多地方都使用了類族模式。比如NSArray囊咏。
我們?cè)诮oCocoa中的類族添加子類的時(shí)候恕洲,要注意一下幾點(diǎn):

  • 子類應(yīng)該繼承自類族中的抽象基類
  • 子類應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式
  • 子類應(yīng)該復(fù)寫超類文檔中指明需要覆寫的方法。

第10條:在既有類中梅割,使用關(guān)聯(lián)對(duì)象(Associated Object)存放自定義數(shù)據(jù)

有時(shí)需要在對(duì)象中存放相關(guān)信息霜第,這是我們通常對(duì)從對(duì)象所屬的類中繼承一個(gè)子類,然后改用這個(gè)子類對(duì)象户辞。然而有時(shí)候我們無法這么做泌类,這時(shí)候就要使用關(guān)聯(lián)對(duì)象了。

關(guān)聯(lián)類型 等效的@property屬性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy

下列方法可以管理關(guān)聯(lián)對(duì)象:

  • void objc_setAssociatedObject(id object,void *key,id value,objc_AssociationPolicy policy)
    此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
  • id objc_getAssociatedObject(id object,void *key)
    此方法根據(jù)給定的鍵和策略為某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值
  • void objc_removeAssicuatedObjects(id object)
    此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象

在設(shè)置關(guān)聯(lián)對(duì)象值時(shí)底燎,通常使用靜態(tài)全局變量做鍵
只有在其他方法完全不可行的時(shí)候才選用關(guān)聯(lián)對(duì)象方法刃榨,因?yàn)檫@種做法通常會(huì)引入難于查找的bug。

第11條:理解objc_msgSend的作用

在C語言當(dāng)中双仍,C語言使用“靜態(tài)綁定”枢希,也就是說,在編譯期就能決定運(yùn)行時(shí)所應(yīng)調(diào)用的函數(shù)殊校。
而Objective-C使用“動(dòng)態(tài)綁定”晴玖,在底層,所有方法都是普通的C語言函數(shù)为流,然而對(duì)象收到消息后呕屎,究竟該調(diào)用那個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變敬察。
給對(duì)象發(fā)送消息可以這樣來寫:

id returnValue = [someObject messageName:parameter];

在這里秀睛,someObject叫做“接收者”,messageName叫做“選擇子”莲祸,選擇子與參數(shù)合起來稱之為“消息”蹂安。編譯器看到此消息后椭迎,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言函數(shù)調(diào)用,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù)田盈,叫做objc_msgSend,其原型如下:

void objc_msgSend(id self,SEL cmd,...)

編譯器會(huì)把它轉(zhuǎn)換成

id returnValue = objc_msgSend(someObject,
                              @selector(messageName:),
                              parameter);

為了完成此操作畜号,該方法需要在接受者所屬的類中搜尋其“方法列表”如果能找到與選擇子名稱相符的防范,就跳轉(zhuǎn)至其代碼允瞧。若是找不到简软,那就沿著繼承體系繼續(xù)向上查找,等找到合適的方法之后再跳轉(zhuǎn)述暂。如果最終還是找不到相符的方法痹升,那就執(zhí)行“消息轉(zhuǎn)發(fā)”操作。
objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表”里面畦韭,每個(gè)類都有這一塊緩存疼蛾,若是稍后還想該類發(fā)送與選擇子相同的消息,那么執(zhí)行起來就會(huì)很快了艺配。
除了以上的方法察郁,還有一些其他的函數(shù):

  • objc_msgSend_stret 如果待發(fā)送的消息要返回結(jié)構(gòu)體,就由它處理转唉。
  • objc_msgSend_fpret 如果待發(fā)送的消息要返回浮點(diǎn)數(shù)绳锅,就由它處理。
  • objc_msgSendSuper 如果要給超類發(fā)消息酝掩,例如[super message:parameter],那么久交由此處理。

在每個(gè)類中都有一張表格眷柔,其中的指針都會(huì)指向這種函數(shù)期虾,而選擇子的名稱則是查表所用的“鍵”。
這里為了優(yōu)化驯嘱,使用了尾調(diào)用優(yōu)化镶苞。
如果某函數(shù)的最后一項(xiàng)操作是調(diào)用另外一個(gè)函數(shù),那么就會(huì)調(diào)用這個(gè)方法鞠评。編譯器會(huì)生成調(diào)轉(zhuǎn)至另一函數(shù)所需的指令碼茂蚓,而且不會(huì)向調(diào)用堆棧中推入新的“棧幀”。這么做法非常關(guān)鍵剃幌,如果不這么做的話聋涨,會(huì)過早的發(fā)生棧溢出。

第12條:理解消息轉(zhuǎn)發(fā)機(jī)制

在編譯期間向類發(fā)送了其無法解讀的消息并不會(huì)報(bào)錯(cuò)负乡,因?yàn)樵谶\(yùn)行期可以繼續(xù)向類中添加方法牍白,所以編譯器會(huì)在編譯時(shí)還無法確定類中到底會(huì)不會(huì)有某個(gè)方法實(shí)現(xiàn)。當(dāng)對(duì)象收到了無法解讀的消息時(shí)抖棘,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制茂腥。
消息轉(zhuǎn)發(fā)分為兩大階段狸涌。第一階段先征詢接受者,所屬的類最岗,看其是否能動(dòng)態(tài)添加方法帕胆,以處理這個(gè)未知的選擇子,這叫做動(dòng)態(tài)方法解析般渡。第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”懒豹。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接受者自己就無法再以動(dòng)態(tài)新增方法的手段來響應(yīng)包含該選擇子的消息了诊杆。此時(shí)歼捐,運(yùn)行期系統(tǒng)會(huì)請(qǐng)求接受者以其他手段來處理與消息相關(guān)的方法調(diào)用。這里又分兩步晨汹,首先豹储,請(qǐng)接受者看看有沒有其他對(duì)象能處理這條消息。若有淘这,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象剥扣。若沒有備用的接受者,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制铝穷,運(yùn)行期系統(tǒng)會(huì)把與消息相關(guān)的全部細(xì)節(jié)都封裝到NSInvocation當(dāng)中钠怯,再給接受者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息曙聂。

消息轉(zhuǎn)發(fā)

第13條:用method swizzling調(diào)試黑盒方法

我們?cè)诮o定的選擇子名稱相對(duì)的方法在運(yùn)行期改變晦炊,這種方法叫“方法調(diào)配”(method swizzling)。
類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上宁脊,是的“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應(yīng)該調(diào)用的方法断国。這些方法以函數(shù)指針的方法表示,這種指針叫做IMP榆苞,原型如下:

id (*IMP)(id,SEL,...)

我們要實(shí)現(xiàn)方法互換稳衬,需要以下方法

//交換方法實(shí)現(xiàn)
void method_exchangeImplementations(Method m1,Method m2)
//取出對(duì)應(yīng)方法
Method class_getInstanceMethod(Class aClass,SEL aSelector)

看起來沒有什么用處,但是結(jié)合添加方法和category坐漏,就可以達(dá)到讓人意想不到的效果薄疚。
在category中添加一個(gè)方法,與原本的方法互換赊琳,就會(huì)達(dá)到調(diào)用的效果街夭。
這里實(shí)現(xiàn)埋點(diǎn),日志意義非常大慨畸。
一般來說莱坎,只有調(diào)試程序的時(shí)候才需要在runtime中修改方法實(shí)現(xiàn),這種做法不宜濫用寸士。

第14條:理解"類對(duì)象"的用意

描述Objective-C對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)定義在運(yùn)行期程序庫的頭文件里檐什,id類型本身也在定義這里:

typedef struct objc_object {
    Class isa
}*id;

由此可見碴卧,每個(gè)對(duì)象結(jié)構(gòu)體的首個(gè)成員是Class的變量。該變量定義了對(duì)象所屬的類乃正,通常稱為"isa"指針住册。
Class對(duì)象也定義在運(yùn)行期程序庫的頭文件中:

typedef struct objc_class *Class
struct objc_class {  
    Class isa  OBJC_ISA_AVAILABILITY;  
    Class super_class;  
    const char *name;  
    long version;  
    long info;  
    long instance_size;  
    struct objc_ivar_list *ivars;  
    struct objc_method_list **methodLists;  
    struct objc_cache *cache;  
    struct objc_protocol_list *protocols;  
} ;  

在類繼承中查詢類的信息:
isMemberOfClass能夠判斷出對(duì)象是否為某個(gè)特定類的實(shí)例,而isKindOfClass則能判斷出對(duì)象是否為某類或其派生類的實(shí)例瓮具。
每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針荧飞,用以表明其類型,而這些Class對(duì)象則構(gòu)成了累的繼承體系名党。
如果對(duì)象類型無法在編譯期確定叹阔,那么就應(yīng)該使用類型信息查詢方法來探知。
盡量使用類型信息查詢方法來確定對(duì)象類型传睹,而不要直接比較類對(duì)象耳幢,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。

第15條:用前綴避免命名空間沖突

Apple宣稱其保留使用所有"兩字母前綴"的權(quán)利欧啤,所以我們選用的前綴應(yīng)該是三個(gè)字母的睛藻。 而且,如果自己開發(fā)的程序使用到了第三方庫邢隧,也應(yīng)該加上前綴店印。

第16條:提供“全能初始化方法”

有時(shí),由于要實(shí)現(xiàn)各種設(shè)計(jì)需求倒慧,一個(gè)類可以有多個(gè)創(chuàng)建實(shí)例的初始化方法按摘。我們應(yīng)該選定其中一個(gè)作為全能初始化方法,令其他初始化方法都來調(diào)用它纫谅。這個(gè)我們就叫做“全能初始化方法”院峡。
只有在全能初始化方法里面才能存儲(chǔ)內(nèi)部數(shù)據(jù)。這樣一來系宜,當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制改變時(shí),只需修改此方法的代碼就好发魄,無需改動(dòng)其他初始化方法盹牧。
在我們擁有了一個(gè)全能初始化方法后,最好還是要覆寫init方法來設(shè)置默認(rèn)值励幼。并且汰寓,每個(gè)子類的全能初始化方法都應(yīng)該調(diào)用其超類的對(duì)象方法,并且逐級(jí)向上苹粟,否則可能無法實(shí)現(xiàn)相應(yīng)的方法有滑。
假如我們并不想覆寫超類的全能初始化方法,我們可以在本類中在覆寫超類的全能初始化方法并拋出異常嵌削。

第17條:實(shí)現(xiàn)description方法

在打印我們自己定義的類的實(shí)例對(duì)象時(shí)毛好,在控制臺(tái)輸出的結(jié)果往往是只包含了類名和內(nèi)存地址望艺,它的信息顯然是不具體的,遠(yuǎn)達(dá)不到調(diào)試的要求。
這時(shí)肌访,我們就需要重寫description方法了找默。我們將這些屬性值放在字典里打印,則更具有可讀性:

- (NSString*)description {
     return [NSString stringWithFormat:@"<%@: %p, %@>",[self class],self,
   
    @{    @"title":_title,
       @"latitude":@(_latitude),
      @"longitude":@(_longitude)}
    ];
}

輸出結(jié)果:

location = <EOCLocation: 0x7f98f2e01d20, {

    latitude = "51.506";
   longitude = 0;
       title = London;
}>

如果你想要在調(diào)試器中實(shí)現(xiàn)類似效果吼驶,就需要重寫debugDescription方法了惩激。可以實(shí)現(xiàn)和description一樣的效果蟹演,并且當(dāng)你不想要在log中出現(xiàn)過分詳細(xì)的內(nèi)容的時(shí)候风钻,完全可以使用debugDescription在調(diào)試器中使用。

第18條:盡量使用不可變對(duì)象

書中作者建議盡量把對(duì)外公布出來的屬性設(shè)置為只讀酒请,在實(shí)現(xiàn)文件內(nèi)部設(shè)為讀寫骡技。具體做法是:
在頭文件中,設(shè)置對(duì)象屬性為readonly蚌父,在實(shí)現(xiàn)文件中設(shè)置為readwrite哮兰。這樣一來,在外部就只能讀取該數(shù)據(jù)苟弛,而不能修改它喝滞,使得這個(gè)類的實(shí)例所持有的數(shù)據(jù)更加安全。
而且膏秫,對(duì)于集合類的對(duì)象右遭,更應(yīng)該仔細(xì)考慮是否可以將其設(shè)為可變的。
如果在公開部分只能設(shè)置其為只讀屬性缤削,那么就在非公開部分存儲(chǔ)一個(gè)可變型窘哈。這樣一來,當(dāng)在外部獲取這個(gè)屬性時(shí)亭敢,獲取的只是內(nèi)部可變型的一個(gè)不可變版本,例如:

在公共API中:
@interface EOCPerson : NSObject

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends //向外公開的不可變集合

- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;

@end

在這里滚婉,我們將friends屬性設(shè)置為不可變的set。然后帅刀,提供了來增加和刪除這個(gè)set里的元素的公共接口让腹。

在實(shí)現(xiàn)文件里:

@interface EOCPerson ()

@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;

@end

@implementation EOCPerson {
     NSMutableSet *_internalFriends;  //實(shí)現(xiàn)文件里的可變集合
}

- (NSSet*)friends {
     return [_internalFriends copy]; //get方法返回的永遠(yuǎn)是可變set的不可變型
}

- (void)addFriend:(EOCPerson*)person {
    [_internalFriends addObject:person]; //在外部增加集合元素的操作
    //do something when add element
}

- (void)removeFriend:(EOCPerson*)person {
    [_internalFriends removeObject:person]; //在外部移除元素的操作
    //do something when remove element
}

- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName {

     if ((self = [super init])) {
        _firstName = firstName;
        _lastName = lastName;
        _internalFriends = [NSMutableSet new];
    }
 return self;
}

我們也要將內(nèi)部可變的set去開放給外邊一個(gè)不可變的set。但是這里會(huì)造成一個(gè)問題扣溺,也就是我們?cè)趦?nèi)部修改set的時(shí)候骇窍,外部可能有觀察者正在使用這個(gè)set。這時(shí)候锥余,我們需要運(yùn)用“派發(fā)隊(duì)列”等手段腹纳,把數(shù)據(jù)的存取操作都設(shè)置為同步操作。

第19條:使用清晰而協(xié)調(diào)的命名方式

類、方法及變量的命名是Objective-C編程的重要環(huán)節(jié)嘲恍。它的語法結(jié)構(gòu)使得代碼讀起來和句子一樣足画,名字中一般都帶有"in"、"for"蛔钙、"with"等介詞锌云。
方法名與變量名要使用“駝峰命名法”(camel casing),以小寫字母開頭吁脱,其后每個(gè)單詞首字母大寫桑涎。類名也用駝峰命名法,不過首字母要大寫兼贡,并且前面通常還有三個(gè)前綴字母攻冷。
作者給我們總結(jié)了幾條注意事項(xiàng):

  • 如果方法的返回值是新出創(chuàng)建的,那么方法名的首個(gè)詞應(yīng)該是返回值的類型遍希,除非前面還有修飾詞等曼。
  • 應(yīng)該把表示參數(shù)類型的名詞放在參數(shù)前面。
  • 如果方法要在當(dāng)前對(duì)象上執(zhí)行操作凿蒜,那么就應(yīng)該包含動(dòng)詞禁谦;若執(zhí)行操作還需要參數(shù),則應(yīng)該在動(dòng)詞后面加上一個(gè)或多個(gè)動(dòng)詞废封。
  • 不要使用str這種簡稱州泊,應(yīng)該使用string這樣的全稱。
  • Boolean 屬性應(yīng)該加is前綴漂洋。如果返回的非屬性的Boolean值遥皂,那么應(yīng)該根據(jù)其功能,選用has或者is當(dāng)前綴
  • 將get這個(gè)前綴留給那些借由“輸出參數(shù)”來保存返回值的方法刽漂。

第20條:為私有方法名加前綴

建議在實(shí)現(xiàn)文件里將非公開的方法都加上前綴演训,便于調(diào)試,而且這樣一來也很容易區(qū)分哪些是公共方法贝咙,哪些是私有方法样悟。因?yàn)橥卜椒ㄊ遣槐阌谌我庑薷牡摹?br> 作者推薦使用字母和_相結(jié)合的方式。比如說在私有方法前加 p

  • 不要用下劃線來區(qū)分私有方法和公共方法庭猩,因?yàn)闀?huì)和蘋果公司的API重復(fù)乌奇。

第21條:理解Objective-C錯(cuò)誤類型

首先要注意的是,ARC在默認(rèn)情況下不是“異常安全的”眯娱。也就是說如果拋出異常,那么本應(yīng)該在作用域末尾釋放的對(duì)象現(xiàn)在卻不會(huì)自動(dòng)釋放了爬凑。
現(xiàn)在Objective-C使用的策略是:只有在極其罕見的情況下才會(huì)拋出異常徙缴,異常跑出來之后,無須考慮恢復(fù)問題,而應(yīng)用也會(huì)推出于样。換一句話說:程序崩啦疏叨!
那么問題來了,程序只有在遇到嚴(yán)重錯(cuò)誤的時(shí)候會(huì)崩潰穿剖,在遇到不那么嚴(yán)重的錯(cuò)誤是蚤蔓,該如何處理呢?
會(huì)令方法返回nil/0糊余,或者是使用NSError秀又,來標(biāo)明錯(cuò)誤的發(fā)生。
返回nil/0很簡單贬芥,就是返回值會(huì)是nil或者0吐辙,這樣我們就明白發(fā)生了錯(cuò)誤。
而NSError則更加靈活蘸劈,我們可以通過NSError將導(dǎo)致錯(cuò)誤的原因返回給使用者昏苏。
使用NSError可以封裝三種信息:

Error domain:錯(cuò)誤范圍,類型是字符串
Error code :錯(cuò)誤碼威沫,類型是整數(shù)
User info:用戶信息贤惯,類型是字典

NSError的用法:
1.通過委托協(xié)議來傳遞NSError,告訴代理錯(cuò)誤類型棒掠。

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

2.作為方法的“輸出參數(shù)”返回給調(diào)用者

- (BOOL)doSomething:(NSError**)error

使用范例:

NSError *error = nil;
BOOL ret = [object doSomething:&error];

if (error) {
    // There was an error
}

第22條:理解NSCopying協(xié)議

使用對(duì)象時(shí)我們經(jīng)撤豕梗拷貝它。如果我們想令自己的類支持拷貝操作句柠,那就要實(shí)現(xiàn)NSCopying協(xié)議浦译,該協(xié)議只有一個(gè)方法:

- (id)copyWithZone:(NSZone*)zone

NSZone這個(gè)參數(shù)是以前的歷史遺留,現(xiàn)在每個(gè)程序都只有一個(gè)區(qū):默認(rèn)區(qū)溯职,所以精盅,我們可以不必管這個(gè)參數(shù)了。
若想使某個(gè)類支持拷貝功能谜酒,只需聲明該類遵從NSCopying協(xié)議叹俏,并實(shí)現(xiàn)其中的copyWithZone方法就可以。
如果我們想獲得某對(duì)象的不可變型僻族,統(tǒng)一調(diào)用copy方法粘驰;獲得某對(duì)象的可變型,統(tǒng)一調(diào)用mutableCopy方法述么。

例如數(shù)組的拷貝:

-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray

深淺拷貝

Foundation框架中的集合類默認(rèn)都執(zhí)行淺拷貝:只拷貝容器對(duì)象本身蝌数,而不復(fù)制其中的數(shù)據(jù)。 而深拷貝的意思是連同對(duì)象本身和它的底層數(shù)據(jù)都要拷貝度秘。
[圖片上傳失敗...(image-f73c33-1527125798111)]

- (id)initWithSet:(NSArray*)array copyItems:(BOOL)copyItems;

在這里顶伞,我們自己提供了一個(gè)深拷貝的方法:該方法需要傳入兩個(gè)參數(shù):需要拷貝的數(shù)組和是否拷貝元素(是否深拷貝)

- (id)deepCopy {
       EOCPerson *copy = [[[self class] alloc] initWithFirstName:_firstName andLastName:_lastName];
        copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
        return copy;
}

因?yàn)闆]有專門定義深拷貝的協(xié)議,所以其具體執(zhí)行方式由每個(gè)類來決定,你時(shí)許決定自己所寫的類是否要提供深拷貝方法即可唆貌。另外滑潘,不要嘉定遵從了NSCopying協(xié)議的對(duì)象都會(huì)執(zhí)行深拷貝。在絕大多數(shù)情況下锨咙,執(zhí)行的都是淺拷貝语卤。

第23條:通過委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間通信

定義:定義一套接口,某對(duì)象若想接受另一個(gè)對(duì)象的委托酪刀,則需遵從此接口粹舵,一遍成為其"委托對(duì)象"。而這"另一個(gè)對(duì)象"則可以給其委托對(duì)象回傳一些信息蓖宦,也可以在發(fā)生相關(guān)事件時(shí)通知委托對(duì)象齐婴。
對(duì)于代理模式,在iOS中分為兩種:
delegate:信息從類流向委托者稠茂,從委托對(duì)象中獲取信息柠偶。
datasource:信息從數(shù)據(jù)源流向類,將信息傳遞給類睬关。

  • 注意:用屬性定義委托對(duì)象的時(shí)候诱担,需要設(shè)置為weak,而非strong电爹。
    使用@optional關(guān)鍵字標(biāo)注方法蔫仙,表示方法未必執(zhí)行。
    在使用的時(shí)候丐箩,最好再加上一個(gè)判斷:判斷委托對(duì)象是否存在
NSData *data = /* data obtained from network */;
if ( (_delegate) && ([_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)]))
{
        [_delegate networkFetcher:self didReceiveData:data];
}

優(yōu)化

我們需要在使用的時(shí)候檢查委托對(duì)象是否響應(yīng)選擇子摇邦,但是如果頻繁的執(zhí)行操作,除了第一次操作是有用的以外屎勘,后續(xù)的操作都是多余的施籍。所以,我們可以把委托對(duì)象能否響應(yīng)某個(gè)協(xié)議方法這一信息緩存起來概漱,以便優(yōu)化程序效率丑慎。
這里我們可以使用"段位"(bitfield)數(shù)據(jù)類型來實(shí)現(xiàn)。把結(jié)構(gòu)體中某個(gè)字段所占用的二進(jìn)制位個(gè)數(shù)設(shè)為特定的值瓤摧。

@interface EOCNetworkFetcher () {
  struct {
    unsigned int didReceiveData          :1 
    unsigned int didFailWithError          :1
    unsigned int didUpdateProgressTo :1  
  }_delegate;
}
@end

這樣我們就可為代理設(shè)置緩存竿裂。

//set flag
_delegate.didReceiveData = 1;
//Check flag
if(_delegateFlags.didReceiceData){
      //yes,flag set
  }

然后我們實(shí)現(xiàn)緩存的代碼可以寫在delegate的設(shè)置方法中

- (void)setDelegate:(id<EOCNetworkFetcher>)delegate {
  _delegate = deleagte;
  _delegateFlags.didReceiveData = [delegate respondsToSelect:@selector(networkFetcher:didReceiveData:)];
}

這樣的話我們每次調(diào)用delegate的方法前,就不用檢測委托對(duì)象能否響應(yīng)給指定的選擇子了照弥,二十直接查詢結(jié)構(gòu)體的標(biāo)志:

if(_delegateFlags.didUpdateProgressTo){
  [_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
}

第24條:將類的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類中

通常一個(gè)類會(huì)有很多方法腻异,而這些方法往往可以用某種特有的邏輯來分組。我們可以利用OC的分類機(jī)制这揣,將類的這些方法按一定的邏輯劃入幾個(gè)分區(qū)中悔常。
例子:
無分類的類:

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
/* Friendship methods */
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
/* Work methods */
- (void)performDaysWork;
- (void)takeVacationFromWork;
/* Play methods */
- (void)goToTheCinema;
- (void)goToSportsGame;
@end

分類之后:

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;
- (id)initWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
@end

@interface EOCPerson (Friendship)

- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

@interface EOCPerson (Work)
- (void)performDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCPerson (Play)
- (void)goToTheCinema;
- (void)goToSportsGame;
@end

其中敢会,F(xiàn)riendShip分類的實(shí)現(xiàn)代碼可以這么寫:

// EOCPerson+Friendship.h
#import "EOCPerson.h"
@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end
// EOCPerson+Friendship.m
#import "EOCPerson+Friendship.h"
@implementation EOCPerson (Friendship)
- (void)addFriend:(EOCPerson*)person {
 /* ... */
}
- (void)removeFriend:(EOCPerson*)person {
 /* ... */
}
- (BOOL)isFriendsWith:(EOCPerson*)person {
 /* ... */
}
@end

注意:在新建分類文件時(shí),一定要引入被分類的類文件这嚣。

通過分類機(jī)制,可以把類代碼分成很多個(gè)易于管理的功能區(qū)塞俱,同時(shí)也便于調(diào)試姐帚。因?yàn)榉诸惖姆椒Q會(huì)包含分類的名稱,可以馬上看到該方法屬于哪個(gè)分類中障涯。
利用這一點(diǎn)罐旗,我們可以創(chuàng)建名為Private的分類,將所有私有方法都放在該類里唯蝶。這樣一來九秀,我們就可以根據(jù)private一詞的出現(xiàn)位置來判斷調(diào)用的合理性,這也是一種編寫“自我描述式代碼(self-documenting)”的辦法粘我。

第25條:總是為第三方類的分類名稱加前綴

分類機(jī)制通常用于向不知道源碼的既有類中新增功能鼓蜒。但是使用的時(shí)候也容易忽視其中的問題,這個(gè)問題在于:分類中的方法是直接添加在類里面的征字。將分類中的方法假如類中這一操作是在運(yùn)行期系統(tǒng)加載分類時(shí)完成的都弹。運(yùn)行期系統(tǒng)會(huì)把分類中所實(shí)現(xiàn)的每個(gè)方法都加入類的方法列表中。如果類中本來就有此方法匙姜,而分類又實(shí)現(xiàn)了一次畅厢,那么分類中的方法會(huì)覆蓋原來那一份實(shí)現(xiàn)代碼。
我們需要在分類中的方法前加上前綴氮昧,這么做的好處有以下幾點(diǎn):

  • 可以避免自己活他人實(shí)現(xiàn)的方法名重復(fù)
  • 避免和蘋果自己的api重復(fù)框杜,即使現(xiàn)在沒有孟辑,也有可能在未來的某個(gè)時(shí)間甲喝,蘋果新加了方法
  • 避免他們使用自己代碼是算谈,發(fā)生誤會(huì)

第26條:勿在分類中聲明屬性

除了實(shí)現(xiàn)文件里的class-continuation分類中可以聲明屬性外丸氛,其他分類無法向類中新增實(shí)例變量衔憨。
因此适秩,類所封裝的全部數(shù)據(jù)都應(yīng)該定義在主接口中趟济,這里是唯一能夠定義實(shí)例變量的地方稼虎。
關(guān)于分類庐杨,需要強(qiáng)調(diào)一點(diǎn):

分類機(jī)制选调,目標(biāo)在于擴(kuò)展類的功能,而不是封裝數(shù)據(jù)灵份。

第27條:使用class-continuation分類 隱藏實(shí)現(xiàn)細(xì)節(jié)

通常仁堪,我們需要減少在公共接口中向外暴露的部分(包括屬性和方法),而因此帶給我們的局限性可以利用class-continuation分類的特性來補(bǔ)償:

可以在class-continuation分類中增加實(shí)例變量填渠。
可以在class-continuation分類中將公共接口的只讀屬性設(shè)置為讀寫弦聂。
可以在class-continuation分類中遵循協(xié)議鸟辅,使其不為人知。

第28條:通過協(xié)議提供匿名對(duì)象

我們用協(xié)議把自己所寫的API之中的實(shí)現(xiàn)細(xì)節(jié)隱藏起來莺葫,將返回的對(duì)象設(shè)計(jì)為遵從此協(xié)議的純id類型匪凉。這樣,想要隱藏的類名就不會(huì)出現(xiàn)在API當(dāng)中了捺檬。若是接口背后有多個(gè)不同的實(shí)現(xiàn)類再层,而你又不想之名具體使用哪個(gè)類,那么就可以使用這個(gè)方法堡纬。
如果具體的類型不重要聂受,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里面)的特定方法,那么可使用匿名對(duì)象來表示烤镐。

第29條:理解引用計(jì)數(shù)

NSObject協(xié)議聲明了以下三個(gè)方法用于操作引用計(jì)數(shù)器蛋济。

retain 遞增保留計(jì)數(shù)
release 遞減保留計(jì)數(shù)
autorelease 自動(dòng)釋放池

對(duì)象創(chuàng)建出來之后,引用計(jì)數(shù)至少為1.我們絕不應(yīng)該說保留計(jì)數(shù)一定是某個(gè)值炮叶,只能說你所執(zhí)行的操作是遞增了還是遞減了該計(jì)數(shù)碗旅。

在設(shè)置屬性的時(shí)候,需要使用實(shí)例變量的getter方法和setter方法悴灵。比如以下代碼

- (void)setFoo:(id)foo {
  [foo retain];
  [_foo release];
   _foo = foo;
}

這里我們先保留新值并釋放了舊值扛芽,然后更新實(shí)例變量,令其指向新值积瞒。這個(gè)順序非常重要川尖,假如還未保留新值就把舊值釋放了,然后兩個(gè)值又指向同一個(gè)對(duì)象茫孔,俺么叮喳,先執(zhí)行的release操作就可能導(dǎo)致系統(tǒng)將此對(duì)象永久回收。而后續(xù)的retain操作則無法令這個(gè)已經(jīng)徹底回收的對(duì)象復(fù)生缰贝,于是實(shí)例變量就成了懸掛指針馍悟。

autorelease

將對(duì)象放入自動(dòng)釋放池之后,不會(huì)馬上使其引用計(jì)數(shù)-1剩晴,而是在當(dāng)前線程的下一次事件循環(huán)時(shí)遞減锣咒。
使用舉例:如果我們想釋放當(dāng)前需要使用的方法返回值是,可以將其暫時(shí)放在自動(dòng)釋放池中:

- (NSString*)stringValue {
     NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
     return [str autorelease];
}

retain cycle

在其他有垃圾回收機(jī)制的語言中赞弥,幾個(gè)對(duì)象互相引用形成保留環(huán)的時(shí)候毅整,垃圾回收器會(huì)把幾個(gè)對(duì)象認(rèn)定為"孤島",將其全部收走绽左。而在Objective-C中悼嫉,往往要采用weak reference即弱引用的方法解決問題。此外拼窥,我們還可以在外界命令循環(huán)中的某個(gè)對(duì)象不再保留另外一個(gè)對(duì)象戏蔑。

第30條:以ARC簡化引用計(jì)數(shù)

在Clang編譯器中帶有一個(gè)"靜態(tài)分析器(sratic analyzer)"蹋凝,用于指明程序里引用計(jì)數(shù)出問題的地方,它可以很簡單的通過引用計(jì)數(shù)的檢查來分析出是否有內(nèi)存泄漏总棵。這個(gè)也是ARC的思路來源鳍寂。

使用ARC的時(shí)候一定要記住,引用計(jì)數(shù)實(shí)際上還是要執(zhí)行的情龄,只不過保留與釋放操作現(xiàn)在是由ARC為你自動(dòng)添加伐割。

在ARC環(huán)境下,會(huì)自動(dòng)執(zhí)行retain刃唤、release、autorelease白群、dealloc方法尚胞,所以我們不能再調(diào)用了。實(shí)際上帜慢,ARC在調(diào)用這些方法的時(shí)候笼裳,并不通過普通的Objective-C消息派發(fā)機(jī)制,二十直接調(diào)用其底層的C語言版本粱玲,這要操作性能會(huì)更好躬柬。
在ARC中,在方法返回自動(dòng)釋放的對(duì)象時(shí)抽减,要執(zhí)行一個(gè)特殊函數(shù)允青。此時(shí)不會(huì)調(diào)用對(duì)象的autorelease方法,而是改用objc_autoreleaseReturnValue方法卵沉。此函數(shù)會(huì)檢視當(dāng)前方法返回之后即將要執(zhí)行的那段代碼颠锉。若發(fā)現(xiàn)那段代碼要在返回的對(duì)象上執(zhí)行retain操作,則設(shè)置全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的一個(gè)標(biāo)志位史汗,而不執(zhí)行autorelease操作琼掠。與之相似,如果方法返回了一個(gè)自動(dòng)釋放的對(duì)象停撞,而調(diào)用方法的代碼要保留此對(duì)象瓷蛙,那么此時(shí)不直接執(zhí)行retain,而是改為執(zhí)行objc_retainAttoreleaseReturnValue函數(shù)戈毒。此函數(shù)要檢測剛才提到的那個(gè)標(biāo)志位艰猬,若已經(jīng)置位,則不執(zhí)行retain操作副硅。設(shè)置并檢測標(biāo)志位姥宝,要比調(diào)用autorelease和retain更快
在ARC中恐疲,會(huì)采用一種安全的方式來設(shè)置:先保留新值腊满,再釋放舊值套么,最后設(shè)置實(shí)例變量

ARC如何管理實(shí)例變量

在MRC下碳蛋,我們要這么寫dealloc方法:

-(void)dealloc {
  [_foo release];
  [_bar release];
  [super dealloc];
}

而在ARC中胚泌,我們就不需要再這么寫了。因?yàn)锳RC會(huì)調(diào)用Objective-C++的一項(xiàng)特性來生成清理例程(cleanup routine)肃弟$枋遥回收Objective-C++對(duì)象是時(shí),待回收的對(duì)象會(huì)調(diào)用所有C++的析構(gòu)函數(shù)(destructor)笤受。編譯器如果發(fā)現(xiàn)某個(gè)對(duì)象里含有C++對(duì)象穷缤,就會(huì)生成名為.cxx_destruct的方法。而ARC會(huì)借助此特性箩兽,在該方法中生成清理內(nèi)存所需的代碼津肛。

不過,如果有非Objective-C的對(duì)象汗贫,比如CoreFoundation中的對(duì)象或是由malloc()分配在堆中的內(nèi)存身坐,那么仍然需要清理。ARC會(huì)自動(dòng)在.cxx_destruct方法中生成代碼并運(yùn)行dealloc方法落包,兒子啊生成的代碼中會(huì)自動(dòng)調(diào)用超類的dealloc方法部蛇。ARC環(huán)境下,dealloc方法可以像這么來寫

-(void)dealloc {
    CFRelease(_coreFoundationObject);
    free(_heapAllocateMemoryBlob);
}

第31條:在dealloc方法中只釋放引用并解除監(jiān)聽

永遠(yuǎn)不要自己調(diào)用dealloc方法咐蝇,運(yùn)行期系統(tǒng)會(huì)在適當(dāng)?shù)臅r(shí)候調(diào)用它涯鲁。根據(jù)性能需求我們有時(shí)需要在dealloc方法中做一些操作。那么我們可以在dealloc方法里做什么呢有序?

  • 釋放對(duì)象所擁有的所有引用撮竿,不過ARC會(huì)自動(dòng)添加這些釋放代碼,可以不必操心笔呀。
  • 而且對(duì)象擁有的其他非OC對(duì)象也要釋放(CoreFoundation對(duì)象就必須手動(dòng)釋放)
  • 釋放原來的觀測行為:注銷通知幢踏。如果沒有及時(shí)注銷,就會(huì)向其發(fā)送通知许师,使得程序崩潰房蝉。

需要注意的是:
在dealloc方法中不應(yīng)該調(diào)用其他的方法,因?yàn)槿绻@些方法是異步的微渠,并且回調(diào)中還要使用當(dāng)前對(duì)象搭幻,那么很有可能當(dāng)前對(duì)象已經(jīng)被釋放了,會(huì)導(dǎo)致崩潰逞盆。同時(shí)檀蹋,在dealloc方法中也不能調(diào)用屬性的存取方法,因?yàn)楹苡锌赡茉谶@些方法里還有其他操作云芦。而且這個(gè)屬性還有可能處于鍵值觀察狀態(tài)俯逾,該屬性的觀察者可能會(huì)在屬性改變時(shí)保留或者使用這個(gè)即將回收的對(duì)象贸桶。

第32條:編寫“異常安全代碼”時(shí)留意內(nèi)存管理問題

在當(dāng)前的運(yùn)行期系統(tǒng)中,C++與Objective-C的異匙离龋互相兼容皇筛。也就是說,從其中一門語言里拋出的異常能用另外一門語言所編的"異常處理程序"來捕獲坠七。
在try塊中水醋,如果先保留了某個(gè)對(duì)象,然后在釋放它之前又拋出了異常彪置,那么除非在catch塊中能處理此問題拄踪,否則對(duì)象所占內(nèi)存就將泄漏。C++的析構(gòu)函數(shù)由Objective-C的異常處理例程來運(yùn)行拳魁。這對(duì)于C++對(duì)象很重要宫蛆,由于拋出異常會(huì)縮短生命周期,所以發(fā)生異常的時(shí)候必須析構(gòu)的猛,不然就會(huì)泄漏,而文件句柄等系統(tǒng)資源因?yàn)闆]有正確清理想虎,所以就更容易因此而泄漏了卦尊。

@try {
     EOCSomeClass *object = [[EOCSomeClass alloc] init];
      [object doSomethingThatMayThrow];
      [object release];
}
@catch (...) {
         NSLog(@"Whoops, there was an error. Oh well...");
}

這里,我們用release方法釋放了try中的對(duì)象舌厨,但是這樣做仍然有問題:如果在doSomthingThatMayThrow方法中拋出了異常了呢岂却?
這樣就無法執(zhí)行release方法了。
解決辦法是使用@finnaly塊裙椭,無論是否拋出異常躏哩,其中的代碼都能運(yùn)行:

EOCSomeClass *object;
@try {
    object = [[EOCSomeClass alloc] init];
    [object doSomethingThatMayThrow];
}

@catch (...) {
     NSLog(@"Whoops, there was an error. Oh well...");
}

@finally {
    [object release];
}

而在ARC狀態(tài)下的時(shí)候,我們要這么寫:

@try {
     EOCSomeClass *object = [[EOCSomeClass alloc] init];
     [object doSomethingThatMayThrow];
}
@catch (...) {
 NSLog(@"Whoops, there was an error. Oh well...");
}

我們不能使用release方法了揉燃。解決辦法就是需要打開編輯器的-fobjc-arc-exceptions標(biāo)志扫尺。不過會(huì)導(dǎo)致應(yīng)用程序過大,而且會(huì)降低運(yùn)行效率炊汤。

第33條:以弱引用避免保留環(huán)

  • 將某些引用設(shè)為weak正驻,可以避免出現(xiàn)"保留環(huán)";
  • weak引用可以自動(dòng)清空,也可以不自動(dòng)清空抢腐。自動(dòng)清空是隨著ARC而引入的新特性姑曙,由運(yùn)行期系統(tǒng)來實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上迈倍,可以隨意讀取其數(shù)據(jù)伤靠,因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過的對(duì)象。

第34條:以“自動(dòng)釋放池快”降低內(nèi)存峰值

釋放對(duì)象的兩種方式:

  • 調(diào)用release:保留計(jì)數(shù)遞減
  • 調(diào)用autorelease將其加入自動(dòng)釋放池中啼染。在將來清空自動(dòng)釋放池時(shí)宴合,系統(tǒng)會(huì)向其中的對(duì)象發(fā)送release消息焕梅。
    OSX和iOS的應(yīng)用程序分別運(yùn)行于Cocoa和CocoaTouch環(huán)境中。系統(tǒng)會(huì)自動(dòng)創(chuàng)建一些線程形纺,比如說主線程或是GCD機(jī)制中單額線程丘侠,這些線程默認(rèn)都有自動(dòng)釋放池,每次執(zhí)行runloop時(shí)逐样,就會(huì)自動(dòng)將其清空蜗字。因?yàn)椴恍枰约簛韯?chuàng)建自動(dòng)釋放池,通常只有一個(gè)地方需要?jiǎng)?chuàng)建自動(dòng)釋放池脂新,那就是在main函數(shù)里挪捕,我們用自動(dòng)釋放池來包裹應(yīng)用程序的主入口點(diǎn)。比方說争便,iOS程序的main函數(shù)經(jīng)常這樣寫:
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

內(nèi)存峰值(high-memory waterline)是指應(yīng)用程序在某個(gè)限定時(shí)段內(nèi)的最大內(nèi)存用量(highest memory footprint)级零。新增的自動(dòng)釋放池塊可以減少這個(gè)峰值。
不用自動(dòng)釋放池減少峰值:

for (int i = 0; i < 100000; i++) {

      [self doSomethingWithInt:i];

}

在這里滞乙,doSomethingWithInt:方法可能會(huì)創(chuàng)建臨時(shí)對(duì)象奏纪。隨著循環(huán)次數(shù)的增加,臨時(shí)對(duì)象的數(shù)量也會(huì)飆升斩启,而只有在整個(gè)for循環(huán)結(jié)束后序调,這些臨時(shí)對(duì)象才會(huì)得意釋放。
這種情況是不理想的兔簇,尤其在我們無法控制循環(huán)長度的情況下发绢,我們會(huì)不斷占用內(nèi)存并突然釋放掉它們。
又比如我們要從數(shù)據(jù)庫中讀取出很多對(duì)象時(shí):

NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
         EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
         [people addObject:person];
}

這樣明顯多在內(nèi)存中多出很多不必要的臨時(shí)對(duì)象垄琐。我們就可以使用內(nèi)存釋放池:

NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
     @autoreleasepool {
             EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
            [people addObject:person];
      }
}

自動(dòng)釋放池機(jī)制就像棧一樣边酒。系統(tǒng)創(chuàng)建好自動(dòng)釋放池之后,就將其推入棧中狸窘,而清空自動(dòng)釋放池墩朦,則相當(dāng)于將其從棧中彈出。在對(duì)象上執(zhí)行自動(dòng)釋放操作翻擒,就等于將其放入棧頂?shù)哪莻€(gè)池里介杆。
而且,自動(dòng)釋放池還有一個(gè)好處:每個(gè)自動(dòng)釋放池均有其范圍韭寸,可以避免無意間誤用了那些在清空池之后已經(jīng)為系統(tǒng)所回收的對(duì)象春哨。

第35條:用“僵尸對(duì)象”調(diào)試內(nèi)存管理問題

某個(gè)對(duì)象被回收后,再向它發(fā)送消息是不安全的恩伺,這并不一定會(huì)引起程序崩潰赴背。
如果程序沒有崩潰,可能是因?yàn)椋?/p>

  • 該內(nèi)存的部分原數(shù)據(jù)沒有被覆寫。
  • 該內(nèi)存恰好被另一個(gè)對(duì)象占據(jù)凰荚,而這個(gè)對(duì)象可以應(yīng)答這個(gè)方法燃观。

如果被回收的對(duì)象占用的原內(nèi)存被新的對(duì)象占據(jù),那么收到消息的對(duì)象就不會(huì)是我們預(yù)想的那個(gè)對(duì)象便瑟。在這樣的情況下缆毁,如果這個(gè)對(duì)象無法響應(yīng)那個(gè)方法的話,程序依舊會(huì)崩潰到涂。
因此脊框,我們希望可以通過一種方法捕捉到對(duì)象被釋放后收到消息的情況。
這種方法就是利用僵尸對(duì)象践啄!
Cocoa提供了“僵尸對(duì)象”的功能浇雹。如果開啟了這個(gè)功能,運(yùn)行期系統(tǒng)會(huì)把所有已經(jīng)回收的實(shí)例轉(zhuǎn)化成特殊的“僵尸對(duì)象”(通過修改isa指針屿讽,令其指向特殊的僵尸類)昭灵,而不會(huì)真正回收它們,而且它們所占據(jù)的核心內(nèi)存將無法被重用伐谈,這樣也就避免了覆寫的情況烂完。
在僵尸對(duì)象收到消息后,會(huì)拋出異常诵棵,它會(huì)說明發(fā)送過來的消息抠蚣,也會(huì)描述回收之前的那個(gè)對(duì)象。
在設(shè)置僵尸對(duì)象之后非春,對(duì)象所屬的類會(huì)從xxxClass編程_NSZombie_xxxClass。_NSZombie_xxxClass實(shí)際上實(shí)在運(yùn)行期中生成的缓屠,當(dāng)首次碰到xxxClass類的對(duì)象要變成僵尸對(duì)象時(shí)奇昙,就會(huì)創(chuàng)建這么一個(gè)類。僵尸類從名為NSZombie的模板中輔助出來的敌完。這些僵尸對(duì)象沒有什么事情可以做储耐,只是充當(dāng)一個(gè)標(biāo)記。運(yùn)行期系統(tǒng)會(huì)給變成僵尸的類創(chuàng)建一個(gè)新類滨溉。創(chuàng)建新類的工作由運(yùn)行期函數(shù)objc_duplicateClass()來完成什湘,它會(huì)把整個(gè)NSZombie類結(jié)構(gòu)拷貝一份,并賦予其新的名字晦攒,在名字中保留原來類的名字闽撤。
NSZombie類并未實(shí)現(xiàn)出任何方法,此類沒有超類脯颜,因此和NSObject一樣是個(gè)根類哟旗。類里是有一個(gè)實(shí)例變量,就是isa。由于沒有實(shí)現(xiàn)任何方法闸餐,所以發(fā)給他的全部消息都要經(jīng)過"完整的消息轉(zhuǎn)發(fā)機(jī)制"饱亮。這樣,我們就可以在椛嵘常回溯中查看到前綴為__NSZombie __的僵尸對(duì)象近上。會(huì)打印出僵尸對(duì)象所受到的消息及其原來所屬的類。

第36條:不要使用retainCount

在非ARC得環(huán)境下使用retainCount可以返回當(dāng)前對(duì)象的引用計(jì)數(shù)拂铡,但是在ARC環(huán)境下調(diào)用會(huì)報(bào)錯(cuò)壹无,因?yàn)樵摲椒ㄒ呀?jīng)被廢棄了 。
它被廢棄的原因是因?yàn)樗祷氐囊糜?jì)數(shù)只能反映對(duì)象某一時(shí)刻的引用計(jì)數(shù)和媳,而無法“預(yù)知”對(duì)象將來引用計(jì)數(shù)的變化(比如對(duì)象當(dāng)前處于自動(dòng)釋放池中格遭,那么將來就會(huì)自動(dòng)遞減引用計(jì)數(shù))。

第37條:理解block這一概念

說過很多了留瞳,推薦看這個(gè)

第38條:為常用的塊類型創(chuàng)建typedef

如果我們需要重復(fù)創(chuàng)建某種塊(相同參數(shù)拒迅,返回值)的變量,我們就可以通過typedef來給某一種塊定義屬于它自己的新類型她倘。

int (^variableName)(BOOL flag, int value) =^(BOOL flag, int value){
     // Implementation
     return someInt;
}

這個(gè)塊有一個(gè)bool參數(shù)和一個(gè)int參數(shù)璧微,并返回int類型。我們可以給它定義類型:

typedef int(^EOCSomeBlock)(BOOL flag, int value);

再次定義的時(shí)候硬梁,就可以通過簡單的賦值來實(shí)現(xiàn):

EOCSomeBlock block = ^(BOOL flag, int value){
     // Implementation
};

這么做的好處在于:

將代碼做的更加容易使用
當(dāng)你打算重構(gòu)塊的時(shí)候會(huì)很方便

第39條:用handler塊降低代碼分散程度

在界面編碼的時(shí)候前硫,我們經(jīng)常使用異步執(zhí)行任務(wù)。這么做的好處在于:處理用戶界面的顯示及觸摸操作所用的線程荧止,不會(huì)以內(nèi)要執(zhí)行I/O或網(wǎng)絡(luò)通信這類耗時(shí)的任務(wù)而阻塞屹电。這個(gè)線程我們一般稱之為主線程。
異步方法在執(zhí)行完任務(wù)之后跃巡,需要以某種手段通知相關(guān)代碼危号。這里有很多的方法,比如說delegate和block素邪。
與實(shí)現(xiàn)delegate模式相比外莲,block寫出來的代碼更為整潔。異步任務(wù)執(zhí)行完畢后所需要運(yùn)行的業(yè)務(wù)邏輯兔朦,和啟動(dòng)異步任務(wù)所用的代碼放在了一起偷线。
同時(shí),我們還可以用block的方式來處理錯(cuò)誤沽甥。而且声邦,在某些代碼必須執(zhí)行在特定的線程上的時(shí)候,會(huì)更加有效摆舟。

第40條:用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)

在使用塊的時(shí)候翔忽,若不仔細(xì)思考英融,會(huì)很容易導(dǎo)致保留環(huán)。

情況一:

@implementation EOCClass {

     EOCNetworkFetcher *_networkFetcher;
     NSData *_fetchedData;

}


- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];

    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;

    }];

}

在這里出現(xiàn)了保留環(huán):塊要設(shè)置_fetchedData變量歇式,就需要捕獲self變量驶悟。而self(EOCClass實(shí)例)通過實(shí)例變量保留了獲取器_networkFetcher,而_networkFetcher又保留了塊材失。
解決方案是:在塊中取得了data后痕鳍,將_networkFetcher設(shè)為nil。

- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
    _networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData *data){

             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;
            _networkFetcher = nil;

    }];

}

情況二:

- (void)downloadData {

     NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
     EOCNetworkFetcher *networkFetcher =[[EOCNetworkFetcher alloc] initWithURL:url];
    [networkFetcher startWithCompletionHandler:^(NSData *data){
             NSLog(@"Request URL %@ finished", _networkFetcher.url);
            _fetchedData = data;
    }];
}

這里其實(shí)也出現(xiàn)了保留環(huán)龙巨,block要通過獲取獲取器對(duì)象來引用url笼呆,于是block就要保留獲取器,而同時(shí)獲取器反過來也保留了這個(gè)塊旨别。我們要在接下來要進(jìn)行的方法中做如下處理

-(void)p_requestCompleted{
    if(_completionHandler){
       _completionHandler(_downloadData);
    }
    self.completionHandler = nil;  
}

第41條:多用派發(fā)隊(duì)列诗赌,少用同步鎖

多個(gè)線程執(zhí)行同一份代碼時(shí),很可能會(huì)造成數(shù)據(jù)不同步秸弛。作者建議使用GCD來為代碼加鎖的方式解決這個(gè)問題铭若。

方案一:使用串行同步隊(duì)列來將讀寫操作都安排到同一個(gè)隊(duì)列里:

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);

//讀取字符串
- (NSString*)someString {

         __block NSString *localSomeString;
         dispatch_sync(_syncQueue, ^{
            localSomeString = _someString;
        });
         return localSomeString;

}

//設(shè)置字符串
- (void)setSomeString:(NSString*)someString {

     dispatch_sync(_syncQueue, ^{
        _someString = someString;
    });
}

方案二:將寫操作放入柵欄快中,讓他們單獨(dú)執(zhí)行递览;將讀取操作并發(fā)執(zhí)行叼屠。

_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//讀取字符串
- (NSString*)someString {

     __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
//設(shè)置字符串
- (void)setSomeString:(NSString*)someString {

     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });

}

顯然,數(shù)據(jù)的正確性主要取決于寫入操作绞铃,那么只要保證寫入時(shí)镜雨,線程是安全的,那么即便讀取操作是并發(fā)的儿捧,也可以保證數(shù)據(jù)是同步的荚坞。
這里的dispatch_barrier_async方法使得操作放在了同步隊(duì)列里“有序進(jìn)行”,保證了寫入操作的任務(wù)是在串行隊(duì)列里菲盾。

第42條:多用GCD颓影,少用performSelector系列方法

因?yàn)镺bjective-C語言的特性。編譯器并不知道要調(diào)用的選擇子是什么亿汞,因此瞭空,也就不了解其方法簽名及返回值揪阿,甚至連是否有返回值都不清楚疗我。而且因?yàn)椴涣私夥椒跃蜎]辦法運(yùn)用ARC的內(nèi)存管理機(jī)制來判斷返回值是否應(yīng)該釋放南捂。
使用performSelector來執(zhí)行某個(gè)方法吴裤,但是performSelector系列的方法能處理的選擇子很局限:它無法處理帶有兩個(gè)以上的參數(shù)的選擇子;返回值只能是對(duì)象類型溺健。
但是我們?nèi)绻褂胋lock和GCD麦牺,就可以解決這些問題。

  1. 延后執(zhí)行某個(gè)任務(wù)的方法:
// 使用 performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];


// 使用 dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
    [self doSomething];
});
  1. 將任務(wù)放在主線程執(zhí)行:
// 使用 performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
// 使用 dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
});

這里我們都優(yōu)先考慮第二種。

第43條:掌握GCD及操作隊(duì)列的使用時(shí)機(jī)

除了GCD剖膳,操作隊(duì)列(NSOperationQueue)也是解決多線程任務(wù)管理問題的一個(gè)方案魏颓。對(duì)于不同的環(huán)境,我們要采取不同的策略來解決問題:有時(shí)候使用GCD好些吱晒,有時(shí)則是使用操作隊(duì)列更加合理甸饱。
GCD是純C語言實(shí)現(xiàn)的API,而NSOperationQueue是Objective-C的仑濒。
使用NSOperation和NSOperationQueue的優(yōu)點(diǎn):

  • 可以取消操作:在運(yùn)行任務(wù)前叹话,可以在NSOperation對(duì)象調(diào)用cancel方法,標(biāo)明此任務(wù)不需要執(zhí)行墩瞳。但是GCD隊(duì)列是無法取消的驼壶,因?yàn)樗裱鞍才藕弥缶筒还芰耍╢ire and forget)”的原則。
  • 可以指定操作間的依賴關(guān)系:例如從服務(wù)器下載并處理文件的動(dòng)作可以用操作來表示喉酌。而在處理其他文件之前必須先下載“清單文件”热凹。而后續(xù)的下載工作,都要依賴于先下載的清單文件這一操作瞭吃。
  • 監(jiān)控NSOperation對(duì)象的屬性:可以通過KVO來監(jiān)聽NSOperation的屬性:可以通過isCancelled屬性來判斷任務(wù)是否已取消碌嘀;通過isFinished屬性來判斷任務(wù)是否已經(jīng)完成。
  • 可以指定操作的優(yōu)先級(jí):操作的優(yōu)先級(jí)表示此操作與隊(duì)列中其他操作之間的優(yōu)先關(guān)系歪架,我們可以指定它股冗。
  • 重用NSOperation對(duì)象。我們?nèi)缫獎(jiǎng)?chuàng)建NSOperation的子類和蚪,可以方便的使用其中的方法止状。

第44條:通過Dispath Group機(jī)制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務(wù)

有時(shí)需要等待多個(gè)并行任務(wù)結(jié)束的那一刻執(zhí)行某個(gè)任務(wù)攒霹,這個(gè)時(shí)候就可以使用dispath group函數(shù)來實(shí)現(xiàn)這個(gè)需求:
通過dispath group函數(shù)怯疤,可以把并發(fā)執(zhí)行的多個(gè)任務(wù)合為一組,于是調(diào)用者就可以知道這些任務(wù)何時(shí)才能全部執(zhí)行完畢催束。

//一個(gè)優(yōu)先級(jí)低的并發(fā)隊(duì)列
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

//一個(gè)優(yōu)先級(jí)高的并發(fā)隊(duì)列
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

//創(chuàng)建dispatch_group
dispatch_group_t dispatchGroup = dispatch_group_create();

//將優(yōu)先級(jí)低的隊(duì)列放入dispatch_group
for (id object in lowPriorityObjects) {
 dispatch_group_async(dispatchGroup,lowPriorityQueue,^{ [object performTask]; });
}

//將優(yōu)先級(jí)高的隊(duì)列放入dispatch_group
for (id object in highPriorityObjects) {
 dispatch_group_async(dispatchGroup,highPriorityQueue,^{ [object performTask]; });
}

//dispatch_group里的任務(wù)都結(jié)束后調(diào)用塊中的代碼
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{
     // Continue processing after completing tasks
});

是用GCD會(huì)在適當(dāng)?shù)臅r(shí)候自動(dòng)創(chuàng)建新線程或者復(fù)用舊線程集峦。在并發(fā)隊(duì)列中,執(zhí)行任務(wù)所用的并發(fā)線程數(shù)量抠刺,取決于各種因素塔淤,而GCD主要是根據(jù)系統(tǒng)資源狀況來判定這些因素的。通過dispatch group所提供的這種簡便方式速妖,既可以并發(fā)執(zhí)行一系列給定的文物高蜂,又能在全部任務(wù)結(jié)束的時(shí)候得到通知。由于GCD有并發(fā)隊(duì)列機(jī)制罕容,所以能夠根據(jù)可用的系統(tǒng)資源狀況來并發(fā)執(zhí)行任務(wù)备恤。

第45條:使用dispatch_once來執(zhí)行只需運(yùn)行一次的線程安全代碼

有時(shí)我們可能只需要將某段代碼執(zhí)行一次稿饰,這時(shí)可以通過dispatch_once函數(shù)來解決妄痪。
dispatch_once函數(shù)比較重要的使用例子是單例模式:
我們?cè)趧?chuàng)建單例模式的實(shí)例時(shí)沧踏,可以使用dispatch_once函數(shù)來令初始化代碼只執(zhí)行一次,并且內(nèi)部是線程安全的宣赔。
而且惭笑,對(duì)于執(zhí)行一次的block來說梧喷,每次調(diào)用函數(shù)時(shí)傳入的標(biāo)記都必須完全相同,通常標(biāo)記變量聲明在static或global作用域里脖咐。

+ (id)sharedInstance {
     static EOCClass *sharedInstance = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
            sharedInstance = [[self alloc] init];
    });
     return sharedInstance;
}

使用dispatch_once可以簡化代碼并且徹底保證線程安全铺敌,開發(fā)者無需擔(dān)心加鎖或者同步。另外屁擅,dispatch_once方法更加高效偿凭,此函數(shù)采用原子訪問(atomic access)來查詢標(biāo)記,以判斷所對(duì)應(yīng)的代碼是否已經(jīng)執(zhí)行過派歌。

第46條:不要使用dispatch_get_current_queue

我們無法用某個(gè)隊(duì)列來描述“當(dāng)前隊(duì)列”這一屬性弯囊,因?yàn)榕砂l(fā)隊(duì)列是按照層級(jí)來組織的。
[圖片上傳失敗...(image-74a524-1527207303216)]
安排在某條隊(duì)列中的快胶果,會(huì)在其上層隊(duì)列中執(zhí)行匾嘱,而層級(jí)地位最高的那個(gè)隊(duì)列總是全局并發(fā)隊(duì)列。
在這里早抠,B霎烙,C中的塊會(huì)在A里執(zhí)行。但是D中的塊蕊连,可能與A里的塊并行,因?yàn)锳和D的目標(biāo)隊(duì)列是并發(fā)隊(duì)列甘苍。
正因?yàn)橛辛诉@種層級(jí)關(guān)系载庭,所以檢查當(dāng)前隊(duì)列是并發(fā)的還是非并發(fā)的就不會(huì)總是很準(zhǔn)確囚聚。
dispatch_get_current_queue函數(shù)能夠用于解決由不可重入的隊(duì)列代碼所引發(fā)的死鎖靖榕,然而能用此函數(shù)解決的問題,也能用dispatch_queue_set_specific來解決靡挥。

第47條:熟悉系統(tǒng)框架

如果我們使用了系統(tǒng)提供的現(xiàn)成的框架序矩,那么用戶在升級(jí)系統(tǒng)后鸯绿,就可以直接享受系統(tǒng)升級(jí)所帶來的改進(jìn)跋破。
主要的系統(tǒng)框架:

Foundation:NSObject,NSArray,NSDictionary等
CFoundation框架:C語言API簸淀,F(xiàn)oundation框架中的許多功能,都可以在這里找到對(duì)應(yīng)的C語言API
CFNetwork框架:C語言API毒返,提供了C語言級(jí)別的網(wǎng)絡(luò)通信能力
CoreAudio:C語言API租幕,操作設(shè)備上的音頻硬件
AVFoundation框架:提供的OC對(duì)象可以回放并錄制音頻和視頻
CoreData框架:OC的API,將對(duì)象寫入數(shù)據(jù)庫
CoreText框架:C語言API拧簸,高效執(zhí)行文字排版和渲染操作

用C語言來實(shí)現(xiàn)API的好處:可以繞過OC的運(yùn)行期系統(tǒng)劲绪,從而提升執(zhí)行速度。用純C寫成的框架與用Objective-C寫成的一樣重要盆赤,若想成為優(yōu)秀的Objective-C開發(fā)者贾富,應(yīng)該掌握C語言的核心概念。

第48條:多用塊枚舉牺六,少用for循環(huán)

當(dāng)遍歷集合元素時(shí)颤枪,建議使用塊枚舉,因?yàn)橄鄬?duì)于傳統(tǒng)的for循環(huán),它更加高效票灰,而且簡潔,還能獲取到用傳統(tǒng)的for循環(huán)無法提供的值:
我們首先看一下傳統(tǒng)的遍歷:

傳統(tǒng)的for遍歷

NSArray *anArray = /* ... */;
for (int i = 0; i < anArray.count; i++) {
   id object = anArray[i];
   // Do something with 'object'
}

// Dictionary
NSDictionary *aDictionary = /* ... */;
NSArray *keys = [aDictionary allKeys];
for (int i = 0; i < keys.count; i++) {
   id key = keys[i];
   id value = aDictionary[key];
   // Do something with 'key' and 'value'
}
// Set
NSSet *aSet = /* ... */;
NSArray *objects = [aSet allObjects];
for (int i = 0; i < objects.count; i++) {
   id object = objects[i];
   // Do something with 'object'

}

我們可以看到,在遍歷NSDictionary,和NSet時(shí)屈糊,我們又新創(chuàng)建了一個(gè)數(shù)組。雖然遍歷的目的達(dá)成了昧诱,但是卻加大了系統(tǒng)的開銷盏档。

利用快速遍歷:

NSArray *anArray = /* ... */;
for (id object in anArray) {
 // Do something with 'object'
}

// Dictionary
NSDictionary *aDictionary = /* ... */;
for (id key in aDictionary) {
 id value = aDictionary[key];
 // Do something with 'key' and 'value'

}


NSSet *aSet = /* ... */;
for (id object in aSet) {
 // Do something with 'object'
}

這種快速遍歷的方法要比傳統(tǒng)的遍歷方法更加簡潔易懂懦窘,但是缺點(diǎn)是無法方便獲取元素的下標(biāo)。

利用基于block的遍歷:

NSArray *anArray = /* ... */;
[anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){

   // Do something with 'object'
   if (shouldStop) {
      *stop = YES; //使迭代停止
  }

}];
“// Dictionary
NSDictionary *aDictionary = /* ... */;
[aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop){
     // Do something with 'key' and 'object'
     if (shouldStop) {
        *stop = YES;
    }
}];


// Set
NSSet *aSet = /* ... */;
[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop){
     // Do something with 'object'
     if (shouldStop) {
        *stop = YES;
    }

我們可以看到午衰,在使用塊進(jìn)行快速枚舉的時(shí)候,我們可以不創(chuàng)建臨時(shí)數(shù)組扇单。雖然語法上沒有快速枚舉簡潔,但是我們可以獲得數(shù)組元素對(duì)應(yīng)的序號(hào)鄙信,字典元素對(duì)應(yīng)的鍵值,而且鸦采,我們還可以隨時(shí)令遍歷終止渔伯。
利用快速枚舉和塊的枚舉還有一個(gè)優(yōu)點(diǎn):能夠修改塊的方法簽名

for (NSString *key in aDictionary) {
         NSString *object = (NSString*)aDictionary[key];
        // Do something with 'key' and 'object'
}

NSDictionary *aDictionary = /* ... */;

    [aDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop){

             // Do something with 'key' and 'obj'

}];

如果我們可以知道集合里的元素類型,就可以修改簽名玄叠。這樣做的好處是:可以讓編譯期檢查該元素是否可以實(shí)現(xiàn)我們想調(diào)用的方法,如果不能實(shí)現(xiàn)疹吃,就做另外的處理。這樣一來蒋搜,程序就能變得更加安全。

個(gè)人添加

  • block遍歷還有一個(gè)明顯的優(yōu)點(diǎn):如果數(shù)組的數(shù)量過多帮哈,除了block遍歷,其他的遍歷方法都需要添加autoreleasePool方法來優(yōu)化憾筏。block不需要,因?yàn)橄到y(tǒng)在實(shí)現(xiàn)它的時(shí)候就已經(jīng)實(shí)現(xiàn)了相關(guān)處理古拴。

第49條:對(duì)自定義其內(nèi)存管理語義的collection使用無縫橋接

通過無縫橋接技術(shù),可以再Foundation框架中的OC對(duì)象和CoreFoundation框架中的C語言數(shù)據(jù)結(jié)構(gòu)之間來回轉(zhuǎn)換满力。
創(chuàng)建CoreFoundation中的collection時(shí),可以指定如何處理其中的元素潦嘶。然后利用無縫橋接技術(shù)航厚,可以將其轉(zhuǎn)換為OCcollection。
簡單的無縫橋接演示:

NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));

這里麻顶,__bridge表示ARC仍然具備這個(gè)OC對(duì)象的所有權(quán)。CFArrayGetCount用來獲取數(shù)組的長高度矫钓。
為什么要使用無縫橋接技術(shù)呢新娜?因?yàn)橛行㎡C對(duì)象的特性是其對(duì)應(yīng)的CF數(shù)據(jù)結(jié)構(gòu)不具備的,反之亦然旁钧。因此我們需要通過無縫橋接技術(shù)來讓這兩者進(jìn)行功能上的“互補(bǔ)”。

第50條:構(gòu)建緩存時(shí)選用NSCache 而非NSDictionary

如果我們緩存使用得當(dāng)寄猩,那么應(yīng)用程序的響應(yīng)速度就會(huì)提高田篇。只有那種“重新計(jì)算起來很費(fèi)事的數(shù)據(jù),才值得放入緩存”兽赁,比如那些需要從網(wǎng)絡(luò)獲取或從磁盤讀取的數(shù)據(jù)惊科。
在構(gòu)建緩存的時(shí)候很多人習(xí)慣用NSDictionary或者NSMutableDictionary,但是作者建議大家使用NSCache蜡娶,它作為管理緩存的類淮菠,有很多特點(diǎn)要優(yōu)于字典枢赔,因?yàn)樗緛砭褪菫榱斯芾砭彺娑O(shè)計(jì)的。
NSCache優(yōu)于NSDictionary的幾點(diǎn):

  • 當(dāng)系統(tǒng)資源將要耗盡時(shí)速梗,NSCache具備自動(dòng)刪減緩沖的功能。并且還會(huì)先刪減“最久未使用”的對(duì)象位隶。
    NSCache不拷貝鍵,而是保留鍵笋妥。因?yàn)椴⒉皇撬械逆I都遵從拷貝協(xié)議(字典的鍵是必須要支持拷貝協(xié)議的,有局限性)信认。
  • NSCache是線程安全的:不編寫加鎖代碼的前提下其掂,多個(gè)線程可以同時(shí)訪問NSCache。

關(guān)于操控NSCache刪減內(nèi)容的時(shí)機(jī)
開發(fā)者可以通過兩個(gè)尺度來調(diào)整這個(gè)時(shí)機(jī):

緩存中的對(duì)象總數(shù).
將對(duì)象加入緩存時(shí)贤牛,為其指定開銷值殉簸。

對(duì)于開銷值,只有在能很快計(jì)算出開銷值的情況下蝠检,才應(yīng)該考慮采用這個(gè)尺度,不然反而會(huì)加大系統(tǒng)的開銷焰檩。

NSPurgeableData是NSMutableData的子類,把它和NSCache配合使用效果很好藤违。
因?yàn)楫?dāng)系統(tǒng)資源緊張時(shí),可以把保存NSPurgeableData的那塊內(nèi)存釋放掉特漩。
如果需要訪問某個(gè)NSPurgeableData對(duì)象,可以調(diào)用beginContentAccess方發(fā)蛤售,告訴它現(xiàn)在還不應(yīng)該丟棄自己所占據(jù)的內(nèi)存。
在使用完之后,調(diào)用endContentAccess方法炒嘲,告訴系統(tǒng)在必要時(shí)可以丟棄自己所占據(jù)的內(nèi)存空凸。

  • 如果緩存使用得當(dāng)紊选,那么應(yīng)用程序的響應(yīng)速度就能提高。只有那種"重新計(jì)算起來很費(fèi)事"的數(shù)據(jù),才值得放入緩存此蜈,比如那些需要從網(wǎng)絡(luò)獲取或者從磁盤讀取的數(shù)據(jù)跺嗽。

第51條: 精簡initialize 與 load的實(shí)現(xiàn)代碼

每個(gè)類和分類在加入運(yùn)行期系統(tǒng)時(shí)份帐,都會(huì)調(diào)用load方法,而且僅僅調(diào)用一次彬坏,可能有些小伙伴習(xí)慣在這里調(diào)用一些方法幻赚,但是作者建議盡量不要在這個(gè)方法里調(diào)用其他方法,尤其是使用其他的類佳谦。原因是每個(gè)類載入程序庫的時(shí)機(jī)是不同的奸鸯,如果該類調(diào)用了還未載入程序庫的類咪笑,就會(huì)很危險(xiǎn)。而且娄涩,load方法并不會(huì)遵從繼承規(guī)則窗怒。如果某個(gè)類本身沒實(shí)現(xiàn)load方法,那么不管其各級(jí)超類是否實(shí)現(xiàn)此方法蓄拣,系統(tǒng)都不會(huì)調(diào)用弯蚜。
initialize方法與load方法類似优构,區(qū)別是這個(gè)方法會(huì)在程序首次調(diào)用這個(gè)類的時(shí)候調(diào)用(惰性調(diào)用),而且只調(diào)用一次(絕對(duì)不能主動(dòng)使用代碼調(diào)用)快毛。值得注意的一點(diǎn)是,如果子類沒有實(shí)現(xiàn)它瑰妄,它的超類卻實(shí)現(xiàn)了地技,那么就會(huì)運(yùn)行超類的代碼:這個(gè)情況往往很容易讓人忽視庵芭。
在實(shí)現(xiàn)initalize方法時(shí),需要加判斷是否是本類:

+ (void)initialize {
   if (self == [EOCBaseClass class]) {
       NSLog(@"%@ initialized", self);
    }
}
  • 在initalize方法里應(yīng)該只用來設(shè)置內(nèi)部數(shù)據(jù)笛坦。不應(yīng)該調(diào)用方法柿扣,即使是本類方法泡仗。
  • 無法再編譯期設(shè)定的全局變量(比如說NSMutableArray),可以放在initialize里實(shí)現(xiàn)卖鲤。
load initialize
調(diào)用時(shí)間 被添加到 runtime 時(shí) 收到第一條消息前欺旧,可能永遠(yuǎn)不調(diào)用
調(diào)用順序 父類->子類->分類 父類->子類
調(diào)用次數(shù) 1次 多次
是否沿用父類的實(shí)現(xiàn) NO YES
分類中的實(shí)現(xiàn) 類和分類都執(zhí)行 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn)

第52條: 別忘了NSTimer會(huì)保留其目標(biāo)對(duì)象

在使用NSTimer的時(shí)候,NSTimer會(huì)生成指向其使用者的引用泼菌,而其使用者如果也引用了NSTimer,那么就會(huì)生成保留環(huán)。

#import <Foundation/Foundation.h>

@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end


@implementation EOCClass {
     NSTimer *_pollTimer;
}


- (id)init {
     return [super init];
}


- (void)dealloc {
    [_pollTimer invalidate];
}


- (void)stopPolling {

    [_pollTimer invalidate];
    _pollTimer = nil;
}


- (void)startPolling {
   _pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                                 target:self
                                               selector:@selector(p_doPoll)
                                               userInfo:nil
                                                repeats:YES];
}

- (void)p_doPoll {
    // Poll the resource
}

@end

在這里,在EOCClass和_pollTimer之間形成了保留環(huán),如果不主動(dòng)調(diào)用stopPolling方法就無法打破這個(gè)保留環(huán)。
那么我們要如何解決呢夭苗?用block
通過給NSTimer增加一個(gè)分類就可以解決:

#import <Foundation/Foundation.h>

@interface NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                         repeats:(BOOL)repeats;
@end



@implementation NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                        repeats:(BOOL)repeats
{
             return [self scheduledTimerWithTimeInterval:interval
                                                  target:self
                                                selector:@selector(eoc_blockInvoke:)
                                                userInfo:[block copy]
                                                 repeats:repeats];

}


+ (void)eoc_blockInvoke:(NSTimer*)timer {
     void (^block)() = timer.userInfo;
         if (block) {
             block();
        }
}
@end

我們?cè)贜STimer類里添加了方法,我們來看一下如何使用它:

- (void)startPolling {

         __weak EOCClass *weakSelf = self;    
         _pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{

               EOCClass *strongSelf = weakSelf;
               [strongSelf p_doPoll];
          }

                                                          repeats:YES];
}

在這里役首,創(chuàng)建了一個(gè)self的弱引用确虱,然后讓塊捕獲了這個(gè)self變量,讓其在執(zhí)行期間存活钳宪。
一旦外界指向EOC類的最后一個(gè)引用消失缩多,該類就會(huì)被釋放圈纺,被釋放的同時(shí)钩述,也會(huì)向NSTimer發(fā)送invalidate消息(因?yàn)樵谠擃惖膁ealloc方法中向NSTimer發(fā)送了invalidate消息)。
而且尾抑,即使在dealloc方法里沒有發(fā)送invalidate消息歇父,因?yàn)閴K里的weakSelf會(huì)變成nil,所以NSTimer同樣會(huì)失效再愈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末榜苫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子翎冲,更是在濱河造成了極大的恐慌垂睬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抗悍,死亡現(xiàn)場離奇詭異驹饺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缴渊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門赏壹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衔沼,你說我怎么就攤上這事蝌借∥羟疲” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵菩佑,是天一觀的道長自晰。 經(jīng)常有香客問我,道長擎鸠,這世上最難降的妖魔是什么缀磕? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任缘圈,我火速辦了婚禮劣光,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘糟把。我一直安慰自己绢涡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布遣疯。 她就那樣靜靜地躺著雄可,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缠犀。 梳的紋絲不亂的頭發(fā)上数苫,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音辨液,去河邊找鬼虐急。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滔迈,可吹牛的內(nèi)容都是我干的止吁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼燎悍,長吁一口氣:“原來是場噩夢啊……” “哼敬惦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谈山,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤俄删,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后奏路,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畴椰,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年思劳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迅矛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潜叛,死狀恐怖秽褒,靈堂內(nèi)的尸體忽然破棺而出壶硅,到底是詐尸還是另有隱情,我是刑警寧澤销斟,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布庐椒,位于F島的核電站,受9級(jí)特大地震影響蚂踊,放射性物質(zhì)發(fā)生泄漏约谈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一犁钟、第九天 我趴在偏房一處隱蔽的房頂上張望棱诱。 院中可真熱鬧,春花似錦涝动、人聲如沸迈勋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靡菇。三九已至,卻和暖如春米愿,著一層夾襖步出監(jiān)牢的瞬間厦凤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國打工育苟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留较鼓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓宙搬,卻偏偏與公主長得像笨腥,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勇垛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容