Effective Objective-C 2.0 編寫高質量iOS與OSX代碼的52個有效方法 《讀書筆記》

說明:
1.此文章不是原創,是讀書筆記,大多數是摘抄自書籍的要點,作為自己日后復習使用
2.此文純手打,包括代碼,難免有錯誤,請指(qing)正(pen)

1.了解C語言

2.在類的頭文件中盡量少引入其他頭文件

  • 一般來說,應該在某個類頭文件中使用前向聲明來提及別的類,并在實現文件中引入那些類的頭文件,這樣做可以盡量降低類之間的耦合性
  • 假如聲明某個類遵循一項協議,這樣的情況下,盡量把”該類遵循某協議“的這條聲明移至“class-continuation 分類”中,如果不行的話,就把協議單獨放在一個頭文件中,然后將其引入。

3.多用字面量語法,少用與之等價的方法

  • 應該使用字面量語法來創建字符串、數值、數組、字典,優點就是簡明扼要。
  • 應該通過取下標操作來訪問數組下標或字典中的鍵所對應的元素。
  • 用字面量語法創建數組或字典時,若值中有nil,則會拋出異常。因此務必確保值中不含nil。

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

  • 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前據此執行查找與替換操作。即使有人重新定義了常量值,編譯器也不會產生警告信息,這將導致應用程序中常量值不一致的問題。
  • 在實現文件中使用 static const 來定義“只在編譯單元內可見的常量”,由于此類常量不在全局符號表中,所以無需為其名稱加前綴
  • 在頭文件中使用extern來生命全局常量,并在相關文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應加以區分,通常使用與之相關的類名做前綴。

5.用枚舉表示狀態,選項,狀態碼

  • 應該用枚舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起個簡單易懂的名字。
  • 如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那么就將個選項值定義為2的冪,以便通過按位或操作將其組合起來。
  • 用NS_ENUM 與NS_OPTIONS宏來定義枚舉類型,并指明其底層數據類型.這樣做可以確保枚舉是用開發者所選的底層數據類型實現出來的,而不會采用編譯器所選的數據類型。
  • 在處理枚舉類型的switch 語句中不要default分支。這樣的話,加入新枚舉后,編譯器就會提示開發者:switch語句并未處理所有枚舉類型。

6.理解屬性的概念

  • 可以使用@property語法來定義對象中所封裝的數據
  • 通過“特質”來指定存儲數據所需的正確語義
  • 在設置屬性所對應的實例變量時,一定要遵從該屬性所聲明的語義
  • 開發iOS程序時應該使用nonatomic屬性,因為atomic屬性會嚴重影響性能

7.在對象內部盡量直接訪問實例變量

  • 不解釋

8.理解“對象等同性”這一概念

  • == 操作符比較的是兩個指針本身
  • 應該使用“isEqual”來比較兩個對象的等同性
  • 若想檢測對象的等同性,請提供“isEqual:”與hash方法
  • 相同的對象必須具有形同的哈希碼,但是兩個哈希碼相同的對象卻未必相同
  • 不要盲目地逐個檢測每條屬性,而是應該依照具體需求來指定檢測方案
  • 編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法
// 8條的代碼示例
@interface EOCPerosn : NSObject
@property (nonatomic, copy) NSString *firstName;
@Property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end


- (BOOL)isEqual:(id)object {
    if(self == object) return YES;
    if([self class] != [object class]) return NO;
    
    EOCPerson *otherPerson = (EOCPerson *)object;
    if(![_firstName isEqualToString:otherPerson.firstName]) {
        return NO;
    }
    
    if([_lastName isEqualToString:otherPerson.lastName]) {
        return NO;
    }
    
    if([_age != otherPerson.age]) {
        return NO;
    }
    
    return YES;
}

9.以“類族模式”隱藏實現細節

  • 類族模式可以把實現細節隱藏在一套簡單的公共接口后面
  • 系統框架中經常使用類族
  • 從類族的公共抽象基類中繼承子類是要小心,若有開發文檔,則應該首先閱讀

10. 在既有類中使用關聯對象存放自定義數據(盡量不使用)

  • 可以通過“關聯對象”機制來把兩個對象連起來
  • 定義關聯對象時可指定內存管理語義,用以模仿定義屬性時采用的“擁有關系”與非擁有關系
  • 只有在其他做法不可行時才選用關聯對象,因為此種做法通常會引入難以查找的bug
// 10條的代碼示例
#import <objc/runtime.h>
static void *EOCMyAlertViewKey = "EOCMyAlertViewKey";

- (void)askUserAQuestion {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Question" message:@"what do you want to do?" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"continue",nil];
    
    void(^block)(NSInteger) = ^(NSInteger buttonIndex) {
        if(buttonIndex == 0) {
            [self doCancel];
        }else {
            [self doContinue];
        }
    };
    
    objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, BJC_ASSOCIATION_COPY);
    
    [alert show];
    
}

// UIAlertViewDelegate protocol method
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    
    void(^block)(NSInteger) = objec_getAssociatedObject(alertView, EOCMyAlertViewKey);
    
    block(buttonIndex);
    
}

11.理解objc_msgSend的作用

  • 消息由接受者、選擇子及參數構成。給某對象“發送消息(invoke a message)”也就相當于在該對象上“調用方法(call a method)”
  • 發給某對象的全部消息都要由“動態消息派發系統(dynamic message dispatch)”來處理,該系統會查出對應的方法,并執行起代碼
// 11 條代碼示例
// OC中給對象發送消息
id returnValue = [someObject messageName:parameter];

// objc_msgSend的原型(prototype)
void objc_msgSend(id self, SEL cmd, ...)

// 每個OC方法都可以視為簡單的C函數,原型如下
<return_type> Class_selector (id self, SEL _cmd, ... )

12. 理解消息轉發機制

  • 若對象無法響應某個選擇子,則進入消息轉發流程
  • 通過運行期的動態方法解析功能,我們可以在需要用到某個方法時再加入類中
  • 對象那個可以把其無法解讀的默寫選擇子轉交給其他對象來處理
  • 經過上述兩部后,如果還是不能處理選擇子,就啟動完整的消息轉發機制

13. 用“方法調配技術” 調試“黑盒方法”

  • 在運行期,可以向類中新增或替換選擇子所對應的方法實現
  • 使用另一份實現來替換原有的方法實現,這道工序叫做“方法調配”,開發者常用此技術向原有實現中添加新功能
  • 一般來說,只有調試程序的時候才需要在運行期修改方法實現,這種做法不宜濫用
// 13條的代碼示例
// 交換方法
void method_exchangeImplementations(Method m1, Method m2)

// 取出方法
Method class_getInstanceMethod(Class aClass , SEL aSelector)

// 方法交換示例(NSString 的lowercaseString 與 uppercaseString) 
Method originalMethod = class_getInstanceMethod([NSString class],@selector(lowercaseString));

Method swappedMethod = class_getInstanceMethod([NSString class],@selector(uppercaseString));

method_exchangeImplementations(originalMethod, swappedMethod);

14. 理解“類對象”的用意

  • 每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的集成體系
  • 如果對象類型無法再編譯器確定,那么就應該使用類型信息查詢方法來探知
  • 盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現了消息轉發功能
// Class 類的定義
typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    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;
};

------------------------
// id的定義
typedef struct objc_object {
    Class isa;
} *id;
-----------------------

// “isMemberOfClass:” 能夠判斷出對象是否為某個特定類的實例,而“isKindOfClass:”則能判斷除對象是否為某類或器派生類的實例
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]];    // NO
[dict isMemberOfClass:[NSMutableDictionary class]]; // YES
[dict isKindOfClass: [NSDictionary class]]; // YES
[dict isKindOfClass: [NSArray class]];      // NO

15. 使用前綴避免命名空間沖突

  • 選擇與你公司、應用程序或二者皆有關聯之名作為類名的前綴,并在所有代碼中均使用這一前綴
  • 若自己所開發的程序庫中用到了第三方庫,則應為其中的名稱加上前綴

16. 提供“全能初始化方法”

  • 在類中提供一個全能初始化方法,并于文檔中指明。其他初始化方法均應調用此方法
  • 若全能初始化方法與超類不同,則應覆寫超類中的對應方法
  • 如果超類的初始化方法不適用于子類,那么應該覆寫這個超類,并在其中拋出異常

17. 實現description 方法

  • 實現description 方法返回一個有意義的字符串,用以描述改實例。
  • 若想在調試時打印出更詳細的對象描述屬性,應該實現debugDescription方法
// 17條代碼示例
- (NSString *)debugDescription {
    return [NSString stringWithFormat:@"<%@: %p, \" %@  %@ \"> ",[self class],self,_firstName,_lastName];
}

// 'po 對象'的打印結果:
(lldb) po person
(EOCPerson *) $1 = 0X123430 <EOCPerson: 0X123430, "Bob Smith">

18. 盡量使用不可變對象

  • 盡量創建不可變的對象
  • 若某屬性僅可于對象內部修改,則在“class-continuation分類”中將其由readonly 屬性擴展為readwrite 屬性
  • 不要把可變的collection 作為屬性公開,而應提供相關方法,以此修改對象中的可變collection.

19. 使用清晰而協調的命名方式

  • 起名是應遵從標準的Ojbective-C命名規范,這樣創建出來的接口更容易為開發者所理解
  • 方法名要言簡意賅,從左至右讀起來要像個日常用語中的句子才好
  • 方法名利不要使用縮略后的類型名稱
  • 給方法起名時第一要務就是確保其風格于你自己的代碼或所要集成的框架相符

20. 為私有方法名加前綴

  • 要給私有方法加前綴,這樣可以很容易地將其與公共方法區分開
  • 不要單用一個下劃線做私有方法的前綴,這種做法是預留給蘋果公司用的。

21. 理解Ojbective-C 錯誤模型

  • 只有發生了可使整個應用程序崩潰的嚴重錯誤時,才應使用異常
  • 有錯誤不那么嚴重的情況下,可以指派“委托方法”(delegate method)來處理錯誤,可以把錯誤信息放在NSError對象里,經由“輸出參數”返回給調用者
NSError 對象封裝了三條信息
* Error domain (錯誤范圍,類型為字符串)
* Error code   (錯誤碼,其類型為整數)
* User info (用戶信息,其類型為字典)

22. 理解NSCopying協議

  • 若想令自己縮寫的對象具有拷貝功能,則需實現NSCopying協議
  • 如果自定義的對象分為可變版本和不可變版本,那么就要同時實現NSCopying與NSMutableCopying協議
  • 復制對象時需要決定采用淺拷貝還是深拷貝,一般情況下應該盡量執行淺拷貝
  • 如果你寫的對象需要深拷貝,那么可考慮新增一個專門執行深拷貝的方法
---------------------------------------------------------------------------------
// 代碼示例1:
#import<Foundation/Foundation.h>
@interface EOCPerson : NSObject <NSCopying>

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;

@end

------------------- 在協議中實現下列方法 ---------------
- (id)copyWithZone:(NSZone *)zone {
    EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
    return copy;
}


--------------------------------------------------------------------------------
// 代碼示例2:
// 若copyItem 參數設為YES,則該方法會向數組中的每個元素發送copy消息,用拷貝好的元素創建新的set,并將其返回給調用者
- (id)initWithSet:(NSArray *)array copyItems:(BOOL)copyItems;
// 如果EOCPerson 中有friends數組,并且想將數組中的元素都復制過來,可以編寫一個專門的深拷貝的方法
- (id)deepCopy{
    EOCPerson *copy = [[[self class] alloc] initWithFirstName:_firstName andLastName:_lastName];
    copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES];
    return copy;
}

----------20170303 update-----

協議與分類

23.通過委托與數據源協議進行對象間通信

  • 委托模式為對象提供了一套接口,使其可由此將相關事件告知其他對象
  • 將委托對象應該支持的接口定義成協議,在協議中把可能需要處理的事件定義成方法
  • 當某些對象需要從另外一個對象中獲取數據時,可以使用委托模式。這種情況下,該模式也稱為“數據源協議”(data source protocal)
  • 若有必要,可實現含有位段的結構體,將委托對象是否能響應相關協議方法這一信息緩存至其中。

24.將類的實現代碼分散到便于管理的數個分類中

  • 使用分類機制把類的實現代碼劃分成易于管理的小塊
  • 將應該視為“私有”的方法歸入名叫Private的分類中,以隱藏實現細節

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

  • 向第三方類中添加分類時,總應給其名稱加上你專用的前綴
  • 向第三方類中添加分類時,總應給其中的方法名加上你專用的前綴
@interface NSString (ABC_HTTP)

- (NSString *)abc_urlEncodedString;

- (NSString *)abc_urlDecodedString;

26.勿在分類中聲明屬性

  • 把封裝數據所用的全部屬性都定義在主接口里。
  • 在“class-continuation”分類之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。

------------- 20170308 update -------------

27.使用“class-continuation分類”隱藏實現細節

  • 通過"class-continuation" 分類向類中新增示例變量
  • 如果某屬性在主接口中聲明為“只讀”,而類的內部又要用設置方法修改此屬性,那么就在“class-continuation”分類中將其擴展為“可讀寫”
  • 把私有方法的原型聲明在“class-continuation”分類中
  • 若想使類所遵循的協議不為人所知,則可于“class-continuation”分類中聲明
// 第27條示例代碼

// 這樣總得按照Objective-C++來編譯
#import <Foundation/Foundation.h>
#include "SomeCppClass.h"
@interface EOCClass : NSObject {
@private 
    SomeCppClass _cppClass;
}
@end

// ------------------
// 而使用“class-continuation”分類來解決,就不用每個引用此類的類都使用Objecetive-C++環境編譯
// EOCClass.h
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
@end

// EOCClass.mm
#import "EOCClass.h"
#include "SomeCppClass.h"

@interface EOCClass () {
    SomeCppClass _cppClass;
}
@end

@implemenetation EOCClass
@end
// 將public接口中聲明為“只讀”的屬性擴展為“可讀寫”,以便在類的內部設置其值。
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end


在“class-continuation”分類中把這兩個屬性擴展為“可讀寫”:
@interface EOCPerson ()

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

@end

28.通過協議提供匿名對象

  • 協議可以在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某協議的id類型,協議里規定了對象所應實現的方法
  • 使用匿名對象來隱藏類型名稱(或類名)
  • 如果具體類型不重要,重要的是對象能夠響應(定義在協議里的)特定的方法,那么可使用匿名對象來表示

內存管理

29.理解引用計數

  • 引用計數機制通過可以遞增遞減的計數器來管理內存。對象創建好之后,其保留計數至少為1.若保留計數為正,則對象繼續存活。當保留計數降為0時,對象就被銷毀了。
  • 在對象生命期中,其余對象通過引用來保留或釋放此對象。保留與釋放操作分別會遞增及遞減保留計數。

30. 以ARC簡化引用計數

  • 使用ARC來編程,可省去類中的許多“樣板代碼”
  • ARC管理對象生命期的辦法基本上就是:再合適的地方插入“保留”及“釋放”操作。在ARC環境下,變量的內存管理語義可以通過修飾符指明,而原來則需要手工執行“保留”和“釋放”操作。
  • 由方法所返回的對象,其內存管理語義總是通過方法名來體現。ARC將此確定為開發者必須遵守的規則。
  • ARC只負責管理Objective-C對象的內存。尤其要注意:CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRelease.

31. 在dealloc方法中只釋放引用并解除監聽

  • 在dealloc方法里,應該做的事情就是釋放指向其他對象的引用,并取消原來訂閱的“鍵值觀測”(KVO)或NSNotifacationCenter等通知,不要做其他事情。
  • 如果對象持有文件描述符等系統資源,那么應該專門編寫一個方法來釋放此種資源。這樣的類要和其使用者約定:用完資源后必須調用close方法。
  • 執行異步任務的方法不應在dealloc里調用;只能在正常狀態下執行的那些方法也不應在dealloc里調用,因為此時對象已經在回收的狀態了。

32. 編寫“異常安全代碼”時留意內存管理問題

  • 捕獲異常時,一定要注意將try塊內所創立的對象清理干凈
  • 在默認情況下,ARC不生成安全處理異常所需的清理代碼。開啟編譯器標識(-fobjc-arc-exceptions)后,可生成這種代碼,不過會導致應用程序變大,而且會降低運行效率。
// 第32條示例代碼
// 以下代碼會導致內存泄漏
@try {
    EOCSomeClass *object = [[EOCSomeClass alloc] init];
    [object doSomethingThatMayThrow];
    [object release];
}
@catch (...) {
    NSLog(@"Whoops, there was an error .");
}
// ------- 應該使用@finally塊會使其中的代碼都保證會運行,且只運行一次
EOCSomeClass *object;
@try {
    object = [[EOCSomeClass alloc] init];
    [object doSomethingThatMayThrow];
}
@catch(...) {
    NSLog(@"Whoops, there was an error .");
}
@finally {
    [object release];
}

33.以弱引用避免保留環

  • 將某些引用設為weak,可避免出現“保留環”
  • weak引用可以自動清空,也可以不自動清空。自動清空(autonilling)是隨著ARC而引入的新特性,由運行期系統來實現。在具備自動清空功能的弱引用上,可以隨意讀取其數據,因為這種引用不會指向已經回收過的對象。

34.以“自動釋放池塊”降低內存峰值

  • 自動釋放池排布在棧中,對象收到autorelease消息后,系統將其放入最頂端的池里
  • 合理運用自動釋放池,可降低應用程序的內存峰值
  • @autoreleasepool 這種新式寫法能創建出更為輕便的自動釋放池
NSArray *databaseRecords = /*...*/;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
    @autoreleasepool {
        EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
        [people addObject:person];
    }
}

35.使用“僵尸對象”調試內存管理問題

  • 系統在回收對象時,可以不將其真的回收,而是把它轉化為僵尸對象。通過環境變量NSZombieEnabled可開啟此功能。
  • 系統會修改對象的isa指針,靈氣指向特殊的僵尸類,從而使該對象變為僵尸對象。僵尸類能夠響應所有的選擇子,響應方式為:打印一條包含信息內容及其接受者的消息,然后終止應用程序。

36.不要使用retainCount

  • 對象的保留計數看似有用,實則不然,因為任何給定時間點上的“絕對保留計數”(absolute retain count)都無法反應對象聲明期的全貌
  • 引入ARC之后,retatinCount方法就正式廢除了,在ARC下調用該方法會導致編譯器報錯。

------------20170328--update------------

第6章,塊與大中樞派發

37.理解“塊”這一概念

  • 塊是C,C++,Objective-C中的語法閉包
  • 塊可接受參數,也可返回值
  • 塊可以分配在棧或堆上,可以是全局的。分配在棧上的塊可拷貝到堆里,這樣的話,就和標準的Objective-C對象一樣,具備引用計數了。

38.為常用的塊類型創建typedef

  • 以typedef重新定義塊類型,可令塊變量用起來更加簡單。
  • 定義新類型時應遵從現有的命名習慣,勿使其名稱與別的類型沖突
  • 不妨為同一個塊簽名定義的多個類型別名。如果要重構的代碼使用了塊類型的某個別名,那么只需修改相應的typedef中的塊簽名即可,無須改動其他typedef.

39.用handler 塊降低代碼分散程度

  • 在創建對象時,可以使用內聯的handler塊將相關業務邏輯一并生命
  • 再有多個實例需要監控時,如果采用委托模式,那么經常需要根據傳入的對象來切換。而若改用handler塊來實現,則可直接將塊與相關對象放在一起。
  • 設計API時如果用到了handler塊,那么可以直接增加一個參數,使調用者可通過此參數來決定應該把塊安排在哪個隊列上執行。

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

  • 如果塊所捕獲的對象直接或間接地保留了塊本身,那么就得當心保留換環問題
  • 一定要找個適當的時機解除爆流緩,而不能把責任推給API的調用者
- (void)downloadData {
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.exaple.com/something.dat"];
    EOCNetworkFetcher *networkFetcher = [[EOCNetworkFetcher alloc] initWithURL]


}

41.多用派發隊列,少用同步鎖

  • 派發隊列可用來表述同步語義(synchroinzation semantic),這種做法要比使用@synchronized塊或NSLock對象更簡單
  • 將同步與異步派發結合起來,可以實現與普通加鎖機制一樣的同步行為,這么做不會阻塞執行異步派發的線程。
  • 使用同步隊列及柵欄塊,可以令同步行為更加高效

42.多用GCD,少用performSelector 系列方法

  • performSelector 系列方法在內存管理方面容易有疏失。它無法確定將要執行的選擇子具體是什么,因而ARC編譯器也就無法插入適當的內存管理
  • performSelector 系列方法所能處理的選擇子太過局限了,選擇子的返回值類型及發送給方法的參數個數都收到限制
  • 如果想把任務放在另一個線程上執行,那么最好不要用performSelector系列方法,而是應該吧任務封裝到塊里,然后調用大中樞派發機制的相關方法來實現。

43.掌握GCD及操作隊列的使用時機

  • 在解決多線程與任務管理問題時,派發隊列并非唯一方案
  • 操作隊列提供了一套高層的Objective-C API,能實現純GCD所具備的絕大部分功能,而且還能完成一些更為復雜的操作,那些操作若改用GCD來實現,則需要另外編寫代碼(比如依賴)

44.通過Dispatch Group 機制,根據系統資源狀況來執行任務

  • 一系列任務可歸入一個dispatch group中。開發者可以在這組任務執行完畢時獲得通知。
  • 通過dispatch group ,可以在并發式派發隊列里同事執行多項任務。此時GCD會根據系統資源狀況來調度這些并發執行的任務。開發者若自己來實現此功能,則需編寫大量代碼。

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

  • 經常需要編寫“只需執行一次的線程安全代碼”(thread-safe single-code execution).通過GCD所提供的dispatch_once 函數,很容易就能實現此功能。
  • 標記應該聲明在static或global作用域中,這樣的話,在把只需執行一次的塊傳給dispatch_once函數時,傳進去的標記也是相同的。
+ (id)sharedInstance {
    static EOCClass *shareInstance = nil;
    static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
           shareInstance = [[self alloc] init];
        });
        return sharedInstance;
}

46.不要使用dispatch_get_current_queue

  • dispatch_get_current_queue 函數的行為常常與開發者預想的不同。此函數已經廢棄,只應做調試之用。
  • 由于派發隊列是按層級來組織的,所以無法單用某個隊列對象來描述“當前隊列”這一概念
  • dispatch_get_current_queue函數用于解決由不可重入得代碼所引發的死鎖,然而能用此函數解決的問題,通常也能改用“隊列特定數據”來解決。

第七章,熟悉系統框架

  • 許多系統框架都可以直接使用。其中最重要的是Foundation與CoreFoundation,這兩個框架提供了構建應用程序所需的許多核心功能。
  • 很多常見任務都能用框架來做,例如音頻與視頻處理、網絡通信、數據管理等。
  • 請記住:用純C編寫的框架與用Objective-C寫成的一樣重要,若想成為優秀的objective-C開發者,應該掌握C語言的核心概念。

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

  • 遍歷collection有四種方式。最基本的辦法就是for循環,其次是NSEnumerator 遍歷法以及快速遍歷法,最新,最先進的方式則是“塊枚舉法”。
  • “塊枚舉法”本身就能通過GCD來并發執行遍歷操作,無需另行編寫代碼。而采用其他遍歷方式則無法輕易實現這一點。
  • 若提前知道待遍歷的collection含有何種對象,則應修改塊簽名,指出對象的具體類型。
NSArry *anArray = /* ... */;
NSEnumerator *enumerator = [anArray objectEnumerator];
id object;
while((object = [enumerator nextObject]) != nil) {
    // Do something with 'object'
}
NSArray *anArray = /* ... */;
[anArray enumerateObjectsUsingBlock:
    ^(id object, NSUinteger id x, BOOL *stop) {
    
        // Do something with 'object'
            if(shouldStop) {
                *stop = YES;
            }
            
}]

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

  • 通過u無縫橋接技術,可以在Foundation框架中的Objective-C 對象和CoreFoundation框架中的C語言數據結構之間來回轉換。
  • 在CoreFoundation層面創建collection時,可以指定許多回調函數,這些函數表示此Collection應如何處理器元素。然后,可運用無縫橋接技術,將其轉換成具備特殊內存管理語義的Objective-C collection。
NSArray *anNSArray = @[@1,@2,@3,@4,@5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"size of array = %li",CFArrayGetCount(aCFArray));
// Output :size of array = 5;

50. 構建緩存時選用NSCache而非NSDictionary

  • 實現緩存時應選用NSCache而非NSDictionary 對象。以為NSCache可以提供優雅的自動刪減功能,而且是“線程安全的”,此外,它與字典不同,并不會拷貝鍵。
  • 可以給NSCache對象設置上限,用以限制緩存中的對象總個數及“總成本”,而這些尺度則定義了緩存刪減其中對象的時機。但是絕對不要把這些尺度當成可靠的“硬限制(hard limit)”,他們僅對NSCache起指導作用。
  • 將NSPurgeableData與NSCache搭配使用,可實現自動清除數據的功能,也就是說,當NSPuugeableData對象所占內存為系統所丟棄時,該對象自身也會從緩存中移除。
  • 如果緩存使用得當,那么應用程序的響應速度就能提高。只有那種“重新計算起來很費事的”數據,才值得放入緩存,如果那些需要從網絡獲取或從磁盤讀取的數據。

51.精簡initialize與load的實現代碼

  • 在加載階段,如果實現了load方法,那么系統就會調用它。分類里也可以定義此方法,類的load方法要比分類中的先調用。與其他方法不同,load方法不參與覆寫機制。
  • 首次使用某個類之前,系統會向其發送inintialize消息。由于此方法遵從普通的覆寫規則,所以同城應該在里面判斷當前要初始化的是哪個類。
  • load和initialize方法都應該實現的精簡一些,這有助于保持應用程序的響應能力,也能減少引入“依賴環”的幾率。
  • 無法在編譯器設定的全局常量,可以放在initialize方法里初始化。

52.NSTimer會保留其目標對象

  • NSTimer對象會保留其目標,直到計時器本身失效為止,調用invalidate方法可令計時器失效,另外,一次性的計時器在觸發完任務之后也會失效
  • 反復執行任務的計時器(repeating timer),很容易引入保留環。這種環狀保留關系,可能是直接發生的,也可能是通過對象圖例的其他對象間接發生的。
  • 可以擴充NSTimer的功能,用“塊”來打破保留環。不過,除非NSTimer將來在公共接口里提供此功能,否則必須創建分類,將相關實現代碼加入其中。
    備注:iOS10 中已經有帶有block的計時器創建的API,可以解決循環引用問題

已完_20170328

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容