《Effective Objective C 2 0:編寫高質量iOS與OS X代碼的52個有效方法》閱讀筆記

目標

最近在看一些iOS的進階書籍,作一些簡單的筆記來加深印象。 此次讀的是《Effective+Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法》。程序員

注:20170719對文章作了一些修正算法

第1條:瞭解Objective-C語言的起源

Objective-C語言由Smalltalk語言演化而來,而Smalltalk是消息型語言的鼻祖。編程

//消息型語言
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

//函數型語言
Object *obj = new Object;
obj->preform(parameter1,parameter2);
複製代碼
  1. Objective-C爲C語言了添加面向對象特性,是其超集。Objective-C使用了動態綁定的消息結構,也就是說,在運行時纔會檢查對象類型。接收一條消息以後,究竟應執行何種代碼,由運行期環境而非編譯器決定。
  2. 理解C語言的核心概念有助於寫好Objective-C程序。尤爲要掌握內存模型和指針。

第2條:在類的頭文件中儘可能少引用其餘頭文件

  1. 除非確有必要,不然不要引入頭文件。通常來講,應在某個類的頭文件中使用向前聲明(@class語法)來說起別的類,並在實現文件中引入那些類的頭文件。這樣作能夠下降類之間的耦合。
  2. 有時沒法使用向前聲明,好比要聲明某個類遵循一項協議。這種狀況下,儘可能把「該類遵循某協議」的這條聲明移至「class-continutation分類」中。若是不行的話,就把協議單獨放在一個頭文件中,而後將其引入。

注:「class-continutation分類」說的就是「擴展」。 我的理解:少引入無用頭文件的做用: 1)減小程序編輯時間 2)下降類之間的耦合,使類更清晰,讓類的使用者更容易理解 3)有效避免相互引用的問題數組

第3條:多用字面量語法,少用與之等效的語法

NSNumber *someNumber = @(1);
NSArray *animals = @[@"dog",@"cat",@"mouse",@"badger"];
//取下標操做
NSString *dog = animal[1];
NSDictionary *personData = @{@"firstName":@"shi",@"lastName":@"xueqian",age:@(26)};
NSString *lastName = personData[@"lastName"];

//可變數組和字典
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"xueqian" forKey:@"lastName"];
//可用字面量語法來替換
mutableArray[1] = @"dog";
mutableDictonary[@"lastName"] = @"xueqian";
複製代碼
  1. 應該使用字面量語法來建立字符串、數值、數組、字典。與建立此類對象的常規方法相比,這麼作更加簡明扼要。
  2. 應用經過取下標操做來訪問數組下標或字典中的鍵所對應的元素。
  3. 用字面量語法建立數組或者字典時,若值中有nil,則會拋出異常。務必確保值裏不含nil。

第4條:多用類型常量,少用#define預處理指令

//預處理指令
#define ANIMATION_DURATION 0.3
//常量定義
static const NSTimeInterval kAnimationDuration = 0.3;

//全局常量  頭文件中  聲明
extern NSString *const EOCStringConstant;
//全局常量  實現文件中  定義
NSString *const EOCStringConstant = @"VALUE";
複製代碼
  1. 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前據此執行查找和替換操做。即便有人從新定義了常量值,編譯器也不會產生警告信息,這將致使應用程序中的常量值不一致。
  2. 在實現文件中使用static const 來定義「只在編譯單元內可見的常量」。因爲此類常量不在全局符號表中,全部無須爲其名稱加前綴。
  3. 在頭文件中使用extern來聲明全局常量,並在相關實現文件中定義其值。這種常量要出如今全局符號表中,全部其名稱要加以區隔,一般用與之相關的類名作前綴。

第5條:用枚舉表示狀態、選項、狀態碼

//普通枚舉
typedef NS_ENUM(NSUInteger, EOCConnectionState){
      EOCConnectionStateDisconnected,
      EOCConnectionStateConnecting,
      EOCConnectionStateConnected,
};
//使用枚舉做爲參數時,switch語句最好不要加defalut分支
switch(_currentState) {
    case:EOCConnectionStateDisconnected:
        //幹活
        break;
    case:EOCConnectionStateConnecting:
        //幹活
        break;
    case:EOCConnectionStateConnected:
        //幹活
        break;
}

//二進制枚舉
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){
    EOCPermittedDirectionUp       = 1 <<  0,
    EOCPermittedDirectionDown  = 1 <<  1,
    EOCPermittedDirectionLeft     = 1 <<  2,
    EOCPermittedDirectionRight   = 1 <<  3,
}
//二進制枚舉使用
EOCPermittedDirection direction = EOCPermittedDirectionUp | EOCPermittedDirectionDown;
if (direction & EOCPermittedDirectionUp){
    //有設置  EOCPermittedDirectionUp
}


複製代碼
  1. 應用枚舉來表示狀態機的狀態、傳遞給方法的選項及狀態碼等值,給這些值起個易懂的名字。
  2. 若是把傳遞給某個方法的選項表示爲枚舉類型,而多個選項又可同時使用,那麼就將各選項的值定義爲2的冪,以便經過按位或操做將其組合起來。
  3. 在處理枚類型的switch語句中不要出現default分支。這樣的話,加入新枚舉以後,編譯器就會提示開發者:switch語句並未處理全部枚舉。

###第6條:理解「屬性」這一律念緩存

Objective-C對象一般會把其所須要的數據保存爲各類實例變量。實例變量通常經過「存取方法」來訪問。其中,「獲取方法(getter)」用於讀取變量值,而「設置方法」(setter)用於寫入變量值。安全

@synthesize語法:指定實例變量的名字(較少用):bash

@implementation EOCPerson
@synthesize firstName = _myFirstName;
@end
複製代碼

@dynamic關鍵字:它會告訴編譯器,不要自動建立實現屬性所用的實例變量,也不要爲其建立存取方法。並且,在編譯訪問屬性的代碼時,即便編譯器發現沒有定義存取方法,也不會報錯,它相信這些方法能在運行期找到。網絡

@implementation EOCPerson
@dynamic firstName,lastName;
@end
複製代碼

原子性:默認atomic屬性。能夠經過鎖定機制來確保getter方法操做的原子性。可是並不能保證「線程安全」。因爲iOS中使用同步鎖開銷太大,通常只使用nonatomic。 讀/寫權限:readonly和readwrite getter=:指定getter的方法名。數據結構

@property (nonatomic, getter=isOn) BOOL on;
複製代碼
  1. 可使用@property語法來定義對象中所封裝的數據。 2.經過「特質」來定義存儲數據所需的正確語義。
  2. 在設置屬性所對應的實例變量時,必定要聽從該屬性所聲明的語義。
  3. 開發iOS程序時應使用nonatomic屬性,由於atomic屬性會嚴重影響性能。

第7條:在對象內部儘可能直接訪問實例變量

  1. 在對象內部讀取數據時,應該直接經過實例變量來讀,而寫入數據時,則應使用屬性來寫。
  2. 在初始化方法和dealloc方法中,老是應該使用實例變量來讀寫數據。
  3. 有時會使用懶加載技術配置某份數據,這種狀況下,須要使用屬性來讀取數據。

第8條:理解「對象等同性」這一律念

  1. 若想檢測對象的等同性,請提供「isEqual」和hash方法。
  2. 相同的對象必須具備相同的哈希碼,可是兩個哈希碼相同的對象卻未必相同。
  3. 不要盲目地逐個檢測每條屬性,而是應該依照具體需求來制定檢測方案。
  4. 編寫hash方法時,應該使用計算速度快並且哈希碼碰撞機率低的算法。

第9條:以「類族方式」隱藏實現細節

  1. 類族模式能夠把實現細節隱藏在一套簡單的公共接口後面。
  2. 系統框架中常用類族。
  3. 從類族的公共抽象基類中繼承子類時要小心,如有開發文檔,則應首先閱讀。

第10條:在既有類中使用關聯對象存放自定義數據

關聯類型 等效的@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
//此方法以給定的鍵和策略爲某對象設置關聯對象值
void objc_setAssociatedObject(id object, void *key, id value, objc_associationPolicy policy)
//此方法經過給定的鍵從某對象中獲取關聯對象的值
void objc_getAssociatedObject(id object, void *key)
//此方法移除某對象的所有關聯對象
void objc_removeAssociatedObject(id object)

複製代碼
  1. 能夠經過「關聯對象」機制把兩個對象連起來。
  2. 定義關聯對象時可指定內存管理語義,用以模仿定義屬性時全部採用的「擁有關係」和「非擁有關係」。
  3. 只有在其餘方法不可行時才應選用關聯對象,由於這種用法一般會引入難以查找的bug。

第11條:理解objc_msgSend的做用

  • C語言:C語言使用「靜態綁定」,也就是說,在編譯期就能決定運行時所調用的函數。
  • Objective-C:所要調用的函數直到運行期才能肯定。在底層,全部方法都是普通的C語言函數,然而對象收到消息以後,究竟該調用哪一個方法徹底由運行期決定,甚至能夠在程序運行時改變,這些特性使得Objective-C成爲一門真正的動態語言。
//給對象發送消息
id returnValue = [someObject messageName:parameter];
//objc_msgSend原型
void objc_msgSend(id self, SEL _cmd, ...)
//給對象發送消息底層
id returnValue = objc_msgSend(some Object, @selector(messageName:),parameter);
複製代碼
  1. 消息由接受者、選擇子和參數組成。給某個對象「發送消息」(invoke a message)也就至關於在該對象上「調用方法」(call a method)。
  2. 發送給某對象的所有消息都要由「動態消息派發系統」來處理,該系統會查出對應的方法,並執行其代碼。

第12條:理解消息轉發機制

OC消息傳遞機制和消息轉發機制

  1. 若對象沒法響應某個選擇子,則進入消息轉發流程。
  2. 經過運行期的動態方法解析功能,咱們能夠在須要用到某個方法時再將其加入類中。
  3. 對象能夠把其沒法解讀的某些選擇子轉交給其餘對象來處理。
  4. 通過上面兩步以後,若是仍是沒辦法處理選擇子,那就啓動完整的消息轉發機制。

第13條:用「方法調配」技術調試「黑盒方法」

//方法交換
void method_exchangeImplementations(Method m1, Method m2)
//方法實現
Method class_getInstanceMethod(Class aClass, SEL aSelector)

//demo,交換lowercaseString和uppercaseString方法
Method originalMethod = class_getInstanceMethod([NSString class],
@selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod,swappedMethod);
複製代碼
  1. 在運行期,能夠向類中新增或替換選擇子所應用的方法實現。
  2. 使用另外一份實現來替換原有的方法實現,這道工序叫作「方法調配」(method swizzing),開發者經常使用此技術向原有實現中添加新功能。
  3. 通常來講,只有調試程序的時候才須要在運行期修改方法實現,這種作法不宜濫用。

第14條:理解「類對象」的用意

Class定義

類繼承體系

isMemberOfClass:可以判斷出對象是否爲某個類的實例
isKindOfClass:可以判斷出對象是否爲某類或其派生類的對象
複製代碼
  1. 每一個實例都有一個指向Class對象的指針,用以代表其類型,而這些Class對象則構成了類的繼承體系。
  2. 若是對象類型沒法在編譯期肯定,那麼就應該使用類型信息查詢方法探知。
  3. 儘可能使用類型信息查詢方法來肯定對象類型,而不要直接比較類對象,由於某些對象可能實現了消息轉發功能。

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

Objective-C沒有其餘語言那種內置的命名空間機制。鑑於此,咱們在起名時須要避免潛在的命名衝突。 避免此問題的惟一辦法是變相實現命名空間:爲全部名稱都加上適當前綴。多線程

  1. 選擇與你的公司、應用程序或者兩者皆有關聯之名稱做爲類名的前綴,並在全部代碼中均使用該前綴。
  2. 若本身開發的程序庫中用到了第三方庫,則應爲其中的名稱加上前綴。

第16條:提供「全能初始化方法」

  1. 在類中提供一個全能初始化方法,並與文檔中指明。其餘初始化方法均應調用此方法。
  2. 若全能初始化方法此超類不一樣,則需覆寫超類中的方法。
  3. 若是超類的初始化方法不適用於子類,那麼應覆寫這個超類方法,並在其中拋出異常。

第17條:實現description方法

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@:%p %@",[self class], self, @{@"firstName":_firstName, @"lastName":_lastName}];
}

- (NSString *)description {
      return [NSString stringWithFormat:@"%@ %@",_firstName,_lastName];
}
- (NSString *)debugDescription {
      return [NSStrig stringWithFormat:@"< %@ ,%p %@ ,%@",[self class], self, _firstName, _lastName];
}
複製代碼
  1. 實現description方法返回一個有意義的字符串,用以描述該實例。
  2. 若想在調試時打印出更詳盡的對象描述信息,則應實現debugDesription方法。

第18條:儘可能使用不可變對象

這裏指的可變是「readwrite」屬性對象 不可變是「readonly」屬性對象

  1. 儘可能建立不可變的對象
  2. 若某屬性僅可用於內部修改,則在「擴展」中將其由readonly屬性擴展爲readwrite屬性。
  3. 不要把可變的collection做爲屬性公開,而應提供相關的方法,以此修改對象中的可變collection。

第19條:使用清晰而協調的命名方式

給方法命名時的注意事項:

  • 若是方法的返回值是新建立的,那麼方法名的首個詞應該是返回值的類型,除非前面還有修飾語,例如localizedString。屬性的存取方法不遵循這種命名方式,由於通常認爲這些方法不會建立新對象,即使有時返回內部對象的一份拷貝,咱們也認爲那至關於原有的對象。這些存取方法應該按照其所對應的屬性來命名。
  • 應該把表示參數類型的名詞放在參數前面。
  • 若是方法要在當前對象上執行操做,那麼就應該包含動詞;若執行操做時還須要參數,則應該在動詞後面加上一個或多個名詞。
  • 不要使用str這種簡稱,應該用string這樣的全稱。
  • Boolean屬性應加is前綴。若是某方法返回非屬性的Boolean值,那麼應該根據其功能,選用has或is當前綴。
  • 將get這個前綴留給那些藉由「輸出參數」來保存返回值的方法,好比說,把返回值填充到「C語言式數組」裏的那種方法就可使用這個詞作前綴。
  1. 起名時應聽從標準的Objective-C命名規範,這樣建立出來的接口更容易爲開發者所理解。
  2. 方法名要言簡意賅,從左至右讀起來要像個平常用語中的句子纔好。
  3. 方法名裏不要使用縮略後的類型名稱。
  4. 給方法起名時的第一要務就是確保其風格與你本身的代碼或所要集成的框架相符。

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

  1. 給私有方法的名稱加上前綴,這樣能夠很容易地將其同公共方法區分開。
  2. 不要單用一個下劃線作私有方法的前綴,由於這種作法是預留給蘋果公司用的。

第21條:理解Objective-C錯誤模型

NSError對象裏封裝了三條信息:

  • Error domain:錯誤發生的範圍,其類型爲字符串,一般用一個特有的全局變量來定義。
  • Error code:獨有的錯誤代碼,其類型爲整數。用以指明在某個範圍內具體發生了何種錯誤,一般採用enum來定義。
  • User info:用戶信息,其類型爲字典。有關此錯誤的額外信息,其中或許包含一段「本地化描述」。
- (BOOL)doSomething:(NSError **)error {

    if (/*  there was an error  */) {
        if (error) {
            *error = [NSError errorWithDomain:domain code:code userInfo:userInfo];
        }
        return NO;
    else {
        return YES;
    }
}

NSError *error = nil;
BOOL ret = [object doSomethig:&error];
if (error) {

}
複製代碼
  1. 只有發生了可以使整個應用程序崩潰的嚴重錯誤時,才應使用異常。
  2. 在錯誤不那麼嚴重的狀況下,能夠指派「委託方法」來處理錯誤,也能夠把錯誤信息放在NSError對象裏,經由「輸出參數」返回給調用者。

第22條:理解NSCopying協議

//一個類支持拷貝功能須要實現 NSCopying協議只有這一個方法。其中NSZone目前只有一個默認值,可無論。
- (id)copyWithZone:(NSZone *)zone

- (id)copyWithZone:(NSZone *)zone {
    EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
    return copy;
}
複製代碼
  1. 若想令本身所寫的對象具備拷貝功能,則需實現NSCopying協議。
  2. 若是自定義的對象分爲可變版本與不可變版本,那麼就要同時實現NSCopying與NSMutableCopying協議。
  3. 複製對象時需決定採用淺拷貝仍是深拷貝,通常狀況下應該儘可能 執行淺拷貝。
  4. 若是你所寫的對象須要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法。

第23條:經過委託與數據源協議進行對象間通訊

對象把應對某個行爲的責任委託給另一個類了。 常規的委託模式:信息從類流向受委託者(delegate)。 數據源模式:信息從數據源(Data Source)流向類。

信息流向

  1. 委託模式爲對象提供了一套接口,使其可由此將相關事件告知其餘對象。
  2. 將委託對象應該支持的接口定義爲協議,在協議中把可能須要處理的事件定義成方法。
  3. 當某對象須要從另一個對象獲取數據時,可使用委託模式。這種情境下,該模式亦稱「數據源協議」。
  4. 如有必要,可實現含有位段的結構體,將委託對象是否能相應相關協議方法這一信息緩存至其中。

第24條:將類的實現代碼分散到便於管理的數個分類之中

  1. 使用分類機制把類的實現代碼劃分到便於管理的小塊。
  2. 將應該視爲「私有」的方法納入名叫Private的分類中,以隱藏實現細節。

第25條:老是爲第三方類名稱加前綴

  1. 向第三方類中添加分類時,總應給其名稱加上你專用的前綴。
  2. 向第三方類中添加分類時,總應給其中的方法名加上你專用的前綴。

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

  1. 把封裝數據所用的所有屬性都定義在主接口裏。
  2. 在「class-continuation分類」以外的其餘分類中,能夠定義存取方法,但儘可能不要定義屬性。

第27條:使用「class-continuation分類」隱藏實現細節

注:這裏的「class-continuation分類」其實就是咱們日常所說的「擴展」。

  1. 經過「class-continuation分類」向類中新增實例變量。
  2. 若是某屬性在主接口中聲明爲「只讀」,而類的內部又要用設置方法修改此屬性,那麼就在「class-continuation分類」中將其擴展爲「可讀寫」。
  3. 把私有方法的原型聲明在「class-continuation分類」裏面。
  4. 若想使類所遵循的協議不爲人所知,則可於「class-continuation分類」中聲明。

第28條:經過協議提供匿名對象

  1. 協議可在某種程度上提供匿名類型。具體的對象類型能夠淡化成聽從某協議的id類型,協議裏規定了對象所對應實現的方法。
  2. 使用匿名對象來隱藏類型名稱(或類名)。
  3. 若是具體類型不重要,重要的是對象可以相應(定義在協議裏的)特性方法,那麼可使用匿名對象來表示。

第29條:理解引用計數

從Mac OS X 10.8開始,「垃圾收集器」已經正式廢棄了,以Objective-C代碼編寫Mac OS X程序時不該再使用它,而iOS則從未支持過垃圾收集。

  • Retain 遞增保留計數
  • release 遞減保留計數
  • autorelease 待稍後清理「自動釋放池」時,再遞減保留計數。 調用autorelease,此方法會在稍後遞減計數,一般是在下一次「事件循環」時遞減。
  1. 引用計數機制經過能夠遞增遞減的計數器來管理內存。對象建立好以後,其保留計數至少爲1。若保留計數爲正,則對象繼續存活。當保留計數降爲0時,對象就被銷燬了。
  2. 在對象生命期中,其他對象經過引用來保留或釋放此對象。保留與釋放操做分別會遞增及遞減保留計數。

第30條:以ARC簡化引用計數

不能在ARC模式下調用retainrelease,autoreleasedealloc方法 以alloc,new,copy,mutableCopy``開頭的方法,返回的對象歸調用者全部(返回的對象保留計數會+1)。

  1. 有ARC以後,程序員就無須擔憂內存管理問題了。使用ARC來編程,可省去類中的許多「樣板代碼」。
  2. ARC管理對象生命期的辦法基本上就是:在合適的地方插入「保留」及「釋放「操做。在ARC環境下,變量的內存管理語義能夠經過修飾符指明,而原來則須要手工執行「保留」及「釋放」操做。
  3. ARC只負責管理Objective-C對象的內存。尤爲要注意:CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRealease。

第31條:在dealloc方法中只釋放引用並解除監聽

dealloc方法毫不能主動調用。

  1. 在dealloc方法裏,應該作的事情就是釋放指向其餘對象的引用,並取消原來訂閱的「鍵值觀測」(KVO)或NSNotificationCenter等通知,不要作其餘事情。
  2. 若是對象持有文件描述符等系統資源,那麼應該專門編寫一個方法來釋放此種資源。這樣的類要和使用者約定:用完資源後必須調用close方法。
  3. 執行異步任務的方法不該在dealloc裏調用;只能在正常狀態下執行的那些方法也不該在dealloc裏調用,由於此時對象已處於正在回收的狀態了。

第32條:編寫「異常安全代碼」時留意內存管理問題

  1. 捕獲異常時,必定要注意將try塊內所創立的對象清理乾淨。
  2. 在默認狀況下,ARC不生成安全處理異常所需的清理代碼。開啓編譯器標誌後,可生成這種代碼,不過會致使應用程序變大,並且會下降運行效率。

第33條:以弱引用避免保留環

  1. 將某些引用設爲weak,可避免出現「保留環」。
  2. weak引用能夠自動清空,也能夠不自動清空。自動清空是隨着ARC而引入的新特性,由運行期系統來實現。在具有自動清空功能的弱引用上,能夠隨意讀取其數據,由於這種引用不會指向已經回收過的對象。

第34條:以「自動釋放池塊」下降內存峯值

iOS系統會自動建立一些線程,這些線程默認都有自動釋放池,每次執行「事件循環」(event loop)時,就會將其清空。 自動釋放池的範圍:左括號到右括號({自動釋放池範圍})。在該範圍內的對象,將會在末尾處收到release消息。

  1. 自動釋放池排布在棧中,對象收到autorelease消息後,系統將其放入最頂端的池裏。
  2. 合理運用自動釋放池,可下降應用程序的內存峯值。
  3. @autoreleasepool這種新式寫法能建立出更爲輕便的自動釋放池。

第35條:用「殭屍對象」調試內存管理問題

  1. 系統在回收對象時,能夠不將其真的回收,而是把它轉化爲殭屍對象。經過環境變量NSZomebieEnabled可開啓此功能。
  2. 系統會修改對象的isa指針,令其指向特殊的殭屍類,從而使該對象變爲殭屍對象。殭屍類可以響應全部的選擇子,響應方式爲:打印一條包含消息內容及其接收者的消息,而後終止應用程序。

第36條:不要使用retainCount

  1. 對象的保留計數看似有用,實則否則,由於任何給定時間點上的「絕對保留計數」都沒法反映對象生命期的全貌。
  2. 引入ARC以後,retainCount方法就正式廢止了,在ARC下調用該方法會致使編譯器報錯。

第37條:理解「塊」這一律念

//塊的聲明語法結構
return_type (^block_name)(parameters)
//塊的定義語法結構:
^ return_type (parameters){函數體},其中``return_type``和``parameters``均可以省略。即^{}
複製代碼

塊的強大之處是:在聲明它的範圍裏,全部變量均可覺得其所捕獲。也就是說,那個範圍內的所有變量,在塊裏依然可用。 若是塊所捕獲的變量是對象類型,那麼就會自動保留它。 定義塊的時候,其所佔的內存區域是分配在棧中的。也就是說,塊只在定義它的那個範圍內有效。 爲解決此問題,可給塊對象發送copy消息以拷貝之。這樣的話,就能夠把塊從棧複製到堆了。 全局塊;不會捕捉任何狀態(好比外圍的變量等),運行時也無須有狀態來參與。

  1. 塊是C、C++、Objective-C中的語法閉包。
  2. 塊可接受參數,也可返回值。
  3. 塊能夠分配在棧或堆上,也能夠是全局的。分配在棧上的塊可拷貝到堆裏,這樣的話,就和標準的Objective-C對象同樣,具有引用計數了。

第38條:爲經常使用的塊類型建立typedef

//塊類型的語法結構:
return_type (^block_name)(parameters)
//typedef
typedef return_type(^block_name)(parameters);
複製代碼
  1. 以typedef從新定義塊類型,可令塊變量用起來更加簡單。
  2. 定義新類型時應聽從現有的命名習慣,勿使其名稱與別的類型相沖突。
  3. 不妨爲同一個塊簽名定義多個類型別名。若是要重構的代碼使用了塊類型的某個別名,那麼只需修改相應typedef中的塊簽名便可,無須改動其餘typedef。

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

委託模式有個缺點:若是類要分別使用多個獲取器下載不一樣數據,那麼就得在delegate回到方法里根據傳入的獲取器來切換。

  1. 在建立對象時,可使用內聯的handler塊將相關業務邏輯一併聲明。
  2. 在有多個實例須要監控時,若是採用委託模式,那麼常常須要根據傳入的對象來切換,若改用handler塊來實現,則可直接將塊與相關對象放在一塊兒。
  3. 設計API時若是用到了handler塊,那麼能夠增長一個參數,使調用者可經過此參數來決定應該把塊安排在哪一個隊列上執行。

第40條:用塊引用其所屬對象時不要出現保留環

  1. 若是塊所捕獲的對象直接或間接地保留了塊自己,那麼就得小心保留環問題。
  2. 必定要找個適當的時機解除保留環,而不能把責任推給API的調用者。

第41條:多用派發隊列,少用同步鎖

  1. 派發隊列可用來表示同步語義,這種作法要比使用@synchronized塊或NSLock對象更簡單。
  2. 將同步與異步派發結合起來,能夠實現與普通加鎖機制同樣的同步行爲,而這麼作卻不會阻塞隊列執行異步派發的線程。
  3. 使用同步隊列及柵欄塊,能夠令同步行爲更加高效。

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

//能夠在運行時調用方法
- (id)performSelector:(SEL)selector
//可帶一個參數
- (id)performSelector:(SEL)selector withObject:(id)object
//可帶兩個參數
- (id)performSelector:(SEL)selector withObject:(id)object withObject:(id)object
//可延時執行方法
- (void)performSelector:(SEL)selector withObject:(id)argument afterDelay:(NSTimeInterval)delay
//可放到另外一個線程中執行
- (void)performSelector:(SEL)selector onThread:(NSThread *)thread withObject:(id)argument waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)selector withObject:(id)argument waitUntilDone:(BOOL)wait

//延後執行方法的兩種實現方式:
[self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];

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];
});


//把任務放在主線程執行的兩種方式
[self performSlectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];

dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

複製代碼
  1. performSelector系列方法在內存管理方面容易有疏失。它沒法肯定將要執行的選擇子具體是什麼,於是ARC編譯器也就沒法插入適當的內存管理方法。
  2. performSelector系列方法所能處理的選擇子太過侷限了,選擇子的返回值類型及發送給方法的參數個數都受到限制。
  3. 若是想把任務放在另外一個線程上執行,那麼最好不要用performSelector系列方法,而是應該把任務封裝到塊裏,而後調用GCD的相關方法來實現。

第43條:掌握GCD及操做隊列的使用時機

NSOperationQueue,開發者能夠把操做以NSOperation子類的形式放在隊列中,而這些操做也可以併發執行。 GCD是純C的API,而NSOperationQueue則是Objective-C的對象。 用NSOperationQueue類的「addOerationWithBlock:」方法搭配NSBlockOperation類來使用操做隊列,其語法與純GCD很是相似。 NSOperationQueue與NSOperation類的好處以下:

  • 取消某個操做。
  • 指定操做間的依賴關係。
  • 經過KVO監控NSOperation對象的屬性。
  • 指定操做的優先級。
  • 重用NSOperation對象。
  1. 在解決多線程與任務管理問題時,派發隊列並不是惟一方案。
  2. 操做隊列提供了一套高層的Objective-C API,能實現純GCD所具有的絕大部分功能,並且還能完成一些更爲複雜的操做,那麼操做若改用GCD來實現,則需另外編寫代碼。

第44條:經過Dispatch Group機制,根據系統資源情況來執行任務

dispatch group是GCD的一項特性,可以把任務分組。調用者能夠等待這組任務執行完畢,也能夠在提供回調函數以後繼續往下執行,這組任務完成時,調用者會獲得通知。

//建立dispatch group
dispatch_group_t dispatch_group_create();
//把任務編組(普通dispatch_async的變體)
void dispatch_group_asunc(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
//指定任務所屬的dispatch_group
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
//等待dispatch_group執行完畢(timeout能夠取常量DISPATCH_TIME_FOREVER,表示函數一致等待dispatch_group執行完,而不會超時)
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
//等待dispatch_group執行完畢以後執行block
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
複製代碼
  1. 一系列任務可納入一個dispatch group之中。開發者能夠在這組任務執行完畢時得到通知。
  2. 經過dispatch group,能夠在併發派發隊列裏同時執行多項任務。此時GCD會根據系統資源情況來調度這些併發執行的任務,開發者若本身來實現此功能,則須要編寫大量代碼。

第45條:使用dispatch_once來執行只需運行一次的線程安全代碼

void dispatch_once (dispatch_once_t *token, dispatch_block_t block);

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

使用dispatch_once能夠簡化代碼而且完全保證線程安全,開發者根本無需擔憂加鎖或同步。 因爲每次調用時都必須使用徹底相同的標記,因此標記要聲明成static。 把該變量定義在static做用域中,能夠保證編譯器在每次執行sharedInstance方法時都會複用這個變量,而不會建立新變量。

  1. 常常須要編寫「只需執行一次的線程安全代碼」。經過GCD所提供的dispatch_once函數,很容易就能實現此功能。
  2. 標記應該聲明在static或global做用域中,這樣的話,把只需執行一次的塊傳給dispatch_once函數時,傳進去的標記也實現相同的。

第46條:不要使用dispatch_get_current_queue

  1. dispatch_get_current_queue函數的行爲經常與開發者所預期的不一樣。此函數已經廢棄,只應作調試之用。 2.因爲派發隊列是按層級來組織的,因此沒法單用某個隊列對象來描述「當前隊列」這一律念。
  2. dispatch_get_current_queue函數用於解決由不可重入的代碼所引起的死鎖,然而能用此函數解決的問題,一般也能改用「隊列特定數據」來解決。

第47條:熟悉系統框架

將一系列代碼封裝爲動態庫,並在其中放入描述其接口的頭文件,這樣作出來的東西就叫框架。有時爲iOS平臺構建的第三方框架所使用的是靜態庫,這是由於iOS應用程序不容許在其中包含動態庫。這些東西嚴格來說並非真正的框架,然而也常常視爲框架。不過,全部iOS平臺的系統框架仍然使用動態庫。 在爲Mac OS X或iOS系統開發「帶圖形界面的應用程序」時,會用到名爲Cocoa的框架,在iOS上成爲Cocoa Touch。其實Cocoa自己並非框架,可是裏面集成了一批建立應用程序時常常會用到的框架。

  1. 許多系統框架均可以直接使用。其中最重要的是Fundation與CoreFoundation,這兩個框架提供了構架應用程序所需的許多核心功能。
  2. 不少常見任務都能用框架來作。例如音頻與視頻處理、網絡通訊、數據管理等。
  3. 請記住:用純C寫成的框架與用Objective-C寫成的同樣重要,若想成爲優秀的Objective-C開發者,應該掌握C語言的核心概念。

第48條:多用塊枚舉,少用for循環

//for循環遍歷
    NSArray *arr1 = @[@1,@2,@3,@4,@5];
    for (int i = 0; i < arr1.count; ++i) {
        NSLog(@"arr1[i]=%@",arr1[i]);
    }
    
    //for循環反向遍歷
    for (NSInteger i = arr1.count-1; i >= 0; --i) {
        NSLog(@"arr1[i]=%@",arr1[i]);
    }

 //NSEnumerator遍歷法
    NSArray *arr1 = @[@1,@2,@3,@4,@5];
    NSEnumerator *enumerator = [arr1 objectEnumerator];
    id object;
    while ((object = [enumerator nextObject]) != nil) {
        NSLog(@"object=%@",object);
    }
    
    //NSEnumerator遍歷法反向遍歷
    NSEnumerator *reverseenu = [arr1 reverseObjectEnumerator];
    id object1;
    while ((object1 = [reverseenu nextObject]) != nil) {
        NSLog(@"object1=%@",object1);
    }


    //快速遍歷法
    NSArray *arr1 = @[@1,@2,@3,@4,@5];
    for (NSObject *obj in arr1) {
        NSLog(@"obj=%@",obj);
    }
    
    //快速遍歷法反向遍歷
    for (NSObject *obj1 in [arr1 reverseObjectEnumerator]) {
        NSLog(@"obj1=%@",obj1);
    }


    //塊枚舉法
    NSArray *arr1 = @[@1,@2,@3,@4,@5];
    [arr1 enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"idx=%zd,obj=%@",idx,obj);
    }];
    
    //塊枚舉法反向遍歷
    [arr1 enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"idx=%zd,obj=%@",idx,obj);
    }];
複製代碼
  1. 遍歷collection有四種方式。最基本的辦法是for循環,其次是NSEnumerator遍歷法及快速遍歷法,最新、最早進的方式則是「塊枚舉法」。
  2. 「塊枚舉法」自己就能經過GCD來併發執行遍歷操做,無須另行編寫代碼。而採用其餘遍歷方式則沒法輕易實現這一點。
  3. 若提早知道待遍歷的collection含有何種對象,則應修改塊簽名,指出對象的具體類型。

第49條:對自定義其內存管理語義的collection使用無縫橋接

Foundation框架中的Objective-C類所具有的某些功能,是CoreFoundation框架中的C語言數據接口所不具有的,反之亦然。 __bridge:ARC仍然具有這個Objective對象的全部權。 __bridge_retained:ARC將交出對象的全部權。 __bridge_transfer:C轉化爲OC

  1. 經過無縫橋接技術,能夠在Foundation框架中的Objective-C對象與CoreFoundation框架中的C語言數據結構之間來回轉換。
  2. 在CoreFoundation層面建立collection時,能夠指定許多回調函數,這些函數表示此colection應如何處理其元素。而後,可運用無縫橋接技術,將其轉換成具有特殊內存管理語義的Objective-C collection。

第50條:構建緩存時選用NSCache而非NSDictionary

  1. 實現緩存時應選用NSCache而非NSDictionary對象。由於NSCache能夠提供優雅的自動刪減功能,並且是「線程安全的」,此外,它與字典不一樣,並不會拷貝鍵。
  2. 能夠給NSCache對象設置上限,用以限制緩存中的對象總個數及「總成本」,而這些尺度則定義了緩存刪減其中對象的時機。可是絕對不要把這些尺度當成可靠的「硬限制」,他們僅對NSCache起指導做用。
  3. 將NSPurgeableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPurgeableData對象所佔內存爲系統所丟棄時,該對象自身也會從緩存中移除。
  4. 若是緩存使用得當,那麼應用程序的相應速度就能提升。只有那種「從新計算起來很費事的」數據,才值得放入緩存,好比那些須要從網絡獲取或從磁盤讀取的數據。

第51條:精簡initialize與load的實現代碼

當程序啓動的時候,類和分類,一定會調動且僅調用一次load方法。 先調用類的load方法,再調用分類的load方法。 先調用超類的load方法,再調用子類的load方法。 沒法判斷出各個類的載入順序。 load方法須要實現得精簡一些,由於整個應用程序會在執行load方法時都會阻塞。 initialize方法會在程序首次用該類以前調用,且只調用一次。 initialize是「懶加載」的,若是某個類一直都沒有使用,就不會執行該類的initialize方法。 initialize方法能夠安全使用並調用任意類中的任意方法。 initialize方法只應該用來設置內部數據,不該該在其中調用其餘方法。

  1. 在加載階段,若是類實現了load方法,那麼系統就會調用它。分類裏也能夠定義此方法,類的load方法要比分類中的先調用。與其餘方法不一樣,load方法不參與覆寫機制。
  2. 首次使用某個類以前,系統會向其發送initialize消息。因爲此方法聽從普通的覆寫規則,因此一般應該在裏面判斷當前要初始化的是哪一個類。
  3. 沒法在編譯器設定的全局變量,能夠放在initialize方法裏初始化。

第52條:別忘了NSTimer會保留其目標對象

NSTimer對象會保留其目標,也就是說, NSTimer對象會對目標對象進行強引用。 一次性計時器:只執行一次任務,以後自動失效 重複執行模式:重複執行任務,必須本身調用invalidate方法,才能令其中止。 只有把計時器放在運行循環裏,它才能正常觸發任務。

  1. NSTimer對象會保留其目標,直到計時器自己失效爲止,調用invalidate方法可令計時器失效,另外,一次性的計時器在觸發完成任務以後也會失效。
  2. 反覆執行任務的計時器,很容易引入保留環,若是這種計時器的目標對象又保留了計時器自己,那確定會致使保留環。這種環保留關係,多是直接發生的,也多是經過對象圖裏的其餘對象發生的。
  3. 能夠擴充NSTimer的功能,用「塊」來打破保留環。不過,除非NSTimer未來在公共接口裏提供此功能,不然必須建立分類,將相關實現代碼加入其中。
相關文章
相關標籤/搜索