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

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

1. Objective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型。

這種動(dòng)態(tài)消息工作方式?jīng)Q定了其不可能實(shí)現(xiàn)真正的私有方法或私有實(shí)例變量。

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

  • 使用@class xxx;(向前聲明)的方式在頭文件導(dǎo)入新類,并在實(shí)現(xiàn)文件中再#import該類的頭文件。這樣將引入頭文件的時(shí)機(jī)盡量延后,只在確有需要時(shí)才引入,既盡可能的降低了類之間的耦合,又減少了類的使用者所需引入的頭文件數(shù)量從而減少編譯時(shí)間。
  • 向前聲明解決了兩個(gè)類互相引用的問題。

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

  • 使用字面量語法可以縮減源代碼長(zhǎng)度,使其更為易讀。
  • 字面量語法創(chuàng)建數(shù)組、字典對(duì)象更為安全,因?yàn)槿舸嬖诳罩祵?duì)象則會(huì)拋出異常,有助于查錯(cuò)。
  • 字面量語法創(chuàng)建出的字符串、數(shù)組、字典對(duì)象都是不可變的,要?jiǎng)?chuàng)建可變對(duì)象還需額外調(diào)用[obj mutableCopy]方法。

4. 多用類型常量,少用#define預(yù)處理命令

  • static + const聲明的變量作用相當(dāng)于#define,但會(huì)帶有類型信息

  • const右邊的總不能被修改,所以正確寫法是:

      static NSString * const coder = @"Hello world!";
      static const NSTimeInterval duration = .3f;
    
  • extern + const聲明并定義全局常量的寫法是:

      //.h文件中公開,也可只定義在引入其頭文件的類中
      extern NSString *const NotificationUserLogout;
      //.m
      NSString *const NotificationUserLogout = @"NotificationUserLogout";
    

5. 用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)碼

  • 使用NS_OPTIONS宏定義枚舉類型,可以通過按位或操作將多個(gè)選項(xiàng)同時(shí)組合起來
  • 處理枚舉類型的switch語句中不要實(shí)現(xiàn)default分支,應(yīng)覆蓋所有可能出現(xiàn)的情況

6. 理解“屬性特質(zhì)”

  • 屬性特質(zhì)
  • 原子性
    • atomic(defalut):原子的,內(nèi)部有自旋鎖,理論線程安全(對(duì)setter方法加鎖),耗資源
    • nonatomic:非原子屬性,非線程安全,常用
  • 讀寫權(quán)限
    • readwrite(defalut):可讀可寫,合成getter和setter方法
    • readonly:只讀,只合成getter方法
  • 內(nèi)存管理語義
    • assign:設(shè)置方法只會(huì)執(zhí)行針對(duì)“純量類型”(如CGFloatNSInteger等)的簡(jiǎn)單賦值操作
    • strong:“擁有關(guān)系”。設(shè)置新值時(shí),設(shè)置方法先保留新值,并釋放舊值,再設(shè)置新值。
    • weak:“非擁有關(guān)系”。設(shè)置新值時(shí),設(shè)置方法既不保留新值,也不釋放舊值。所指對(duì)象被銷毀時(shí)自動(dòng)將指針置為nil
    • unsafe_unretained:類似于weak,但對(duì)象被銷毀時(shí)不會(huì)將指針清空,會(huì)出現(xiàn)“懸掛指針”
    • copy:類似于strong,但不保留新值,而是對(duì)其拷貝。常用于修飾“存在可變集合”的不可變集合對(duì)象(如NSString,NSArray,NSDictionary,NSSet)
  • 方法名
    • getter=<name>:指定“獲取方法”的方法名
    • setter=<name>:指定“設(shè)置方法”的方法名,不常用
  • 對(duì)應(yīng)關(guān)鍵字
  • @synthesize:未手動(dòng)實(shí)現(xiàn)settergetter方法時(shí)編譯器會(huì)自動(dòng)生成,也可用于修改實(shí)例變量名(去_,不推薦)
  • @dynamic:禁止自動(dòng)生成gettersetter方法

7. 在對(duì)象內(nèi)部盡量訪問實(shí)例變量

  • 由于不經(jīng)過Objective-C的方法派發(fā),直接訪問實(shí)例變量的的速度更快
  • 直接訪問實(shí)例變量不會(huì)調(diào)用其設(shè)置方法,故繞過了內(nèi)存管理語義,也不會(huì)觸發(fā)KVO通知
  • 在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí),應(yīng)該直接通過實(shí)例變量來讀,而寫入數(shù)據(jù)時(shí),則應(yīng)通過屬性來寫
  • 在初始化方法及dealloc方法中,總是應(yīng)該直接通過實(shí)例變量(_xx)來讀寫數(shù)據(jù)
  • 使用懶加載時(shí),要通過屬性(self.xx)來讀取數(shù)據(jù)

8. 理解“對(duì)象等同性”

  • ==比較的是指針本身
  • isEqual:默認(rèn)比較的是指針地址
    1. 先判self == object,指針相等則返回YES
    2. 再判[self class] != [object class],若所屬類不相同則直接返回NO(注:需要考慮繼承情況,可使用isKindOfClass:
    3. 依次分別對(duì)每個(gè)屬性做值判斷,一旦有一個(gè)不等則返回NO
    4. 最后默認(rèn)返回YES
  • 等同性約定:若isEqual:判定兩對(duì)象相等,則hash一定相等;但hash相同的兩對(duì)象未必相等
  • 若要檢測(cè)對(duì)象的等同性,請(qǐng)?zhí)峁?code>isEqual:與hash方法
  • 重寫hash方法時(shí),應(yīng)提供計(jì)算速度快而且哈希碼碰撞幾率低的算法,不要?jiǎng)?chuàng)建額外的開銷

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

類簇模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在公共接口之后,在系統(tǒng)框架中經(jīng)常使用。

Objective-C語言無法指明某個(gè)基類是“抽象的”,所以子類是不知道必須要實(shí)現(xiàn)父類的某個(gè)方法的。開發(fā)中常常是在超類的開發(fā)文檔中注明。
個(gè)人的做法是在父類抽象方法實(shí)現(xiàn)中加

#define MustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]

宏,這樣一旦子類實(shí)例將消息發(fā)送到了父類該方法就會(huì)拋出異常

10. 在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy)
id objc_getAssociatedObject(id object, void *key)
void objc_removeAssociatedObjects(id object)

關(guān)聯(lián)對(duì)象涉及到runtime的一些用法,需要引入#import <objc/runtime.h>,常常用于給分類(category)增加屬性

11. 理解消息發(fā)送(objc_msgSend)的作用

關(guān)于消息發(fā)送的相關(guān)知識(shí),具體可查閱《招聘一個(gè)靠譜的iOS》面試題參考答案(上):objc中向一個(gè)對(duì)象發(fā)送消息obj-foo和objc_msgsend函數(shù)之間有什么關(guān)系

12. 理解消息轉(zhuǎn)發(fā)(objc_msgForward)機(jī)制

關(guān)于消息發(fā)送的相關(guān)知識(shí),具體可查閱《招聘一個(gè)靠譜的iOS》面試題參考答案(下):objc_msgforward函數(shù)是做什么的直接調(diào)用它將會(huì)發(fā)生什么

13. “方法調(diào)配技術(shù)”(method swizzling)

實(shí)質(zhì)就是交換兩個(gè)方法的實(shí)現(xiàn),可用來在運(yùn)行期為已有類新增或替換選擇子(@selector)所對(duì)應(yīng)的方法。比如在分類的load方法中,執(zhí)行方法交換,以修改替換原有的方法實(shí)現(xiàn)。

14. 理解“類對(duì)象”結(jié)構(gòu)

typedef struct objc_class *Class;
struct objc_class {
    Class isa; //isa指針,指向metaclass(該類的元類)
    Class super_class; //指向objc_class(該類)的super_class(父類)
    const char *name; //objc_class(該類)的類名
    long version; //objc_class(該類)的版本信息,初始化為0,可以通過runtime函數(shù)class_setVersion和class_getVersion進(jìn)行修改和讀取
    long info; //一些標(biāo)識(shí)信息,如CLS_CLASS表示objc_class(該類)為普通類。ClS_CLASS表示objc_class(該類)為metaclass(元類)
    long instance_size; //objc_class(該類)的實(shí)例變量的大小
    struct objc_ivar_list *ivars; //用于存儲(chǔ)每個(gè)成員變量的地址
    struct objc_method_list **methodLists; //方法列表,與info標(biāo)識(shí)關(guān)聯(lián)
    struct objc_cache *cache; //指向最近使用的方法的指針,用于提升效率
    struct objc_protocol_list *protocols; //存儲(chǔ)objc_class(該類)的一些協(xié)議
};

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

Objective-C沒有內(nèi)置命名空間(namespace)機(jī)制。

16. 提供“全能”(指定,designated)初始化方法

指定初始化方法應(yīng)覆蓋所有其他初始化方法,并覆寫超類對(duì)應(yīng)方法。

17. 實(shí)現(xiàn)description方法

#import <objc/runtime.h> //導(dǎo)入runtime頭文件

//重寫description方法同下,NSLog打印model詳細(xì)信息
//重寫debugDescription, lldb打印model詳細(xì)信息
- (NSString *)debugDescription {
    //聲明一個(gè)字典
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    
    //得到當(dāng)前class的所有屬性
    uint count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    //循環(huán)并用KVC得到每個(gè)屬性的值
    for (int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *name = @(property_getName(property));
        id value = [self valueForKey:name]?:@"nil";//默認(rèn)值為nil字符串
        [dictionary setObject:value forKey:name];//裝載到字典里
    }
    
    //釋放
    free(properties);
    
    return [NSString stringWithFormat:@"<%@: %p> -- %@",[self class],self,dictionary];
}

18. 盡量使用不可變對(duì)象

  • 不希望外部修改的屬性用readonly語義聲明,若內(nèi)部可修改在“class-continuation類“設(shè)置對(duì)應(yīng)的readwrite屬性
  • 不宜公開可變的collection屬性,封裝方法返回collection也最好先轉(zhuǎn)成對(duì)應(yīng)的不可變類型

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

代碼潔癖的自我修養(yǎng)

20. 為私有方法名加前綴

有助于區(qū)分于公開方法,一目了然

21. 理解Objectiove-C錯(cuò)誤模型

  • 非嚴(yán)重錯(cuò)誤無須使用異常,必須使用時(shí)參考第9條添加異常宏
  • 使用NSError對(duì)象封裝錯(cuò)誤信息,并返回給調(diào)用者

22. 理解NSCopying協(xié)議

這是一個(gè)嚴(yán)謹(jǐn)的單例模式宏,支持ARC和MRC

#if __has_feature(objc_arc) // ARC

#define DEF_SINGLETON(name) \
static id _instance; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
});\
return _instance; \
} \
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}

#else // 非ARC

#define DEF_SINGLETON(name) \
static id _instance; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (oneway void)release \
{ \
\
} \
\
- (id)autorelease \
{ \
return _instance; \
} \
\
- (id)retain \
{ \
return _instance; \
} \
\
- (NSUInteger)retainCount \
{ \
return 1; \
} \
\
+ (id)copyWithZone:(struct _NSZone *)zone \
{ \
return _instance; \
}

#endif

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

  • 屬性使用weak修飾,防止保留環(huán)
  • 對(duì)于經(jīng)常要使用到的代理和公共數(shù)據(jù)源(如UITextFieldDelegate,UITextViewDelegate,UITableViewDataSource等),可交由一個(gè)封裝的對(duì)象專門去實(shí)現(xiàn)這些方法,以節(jié)省重復(fù)代碼的書寫。
  • 進(jìn)一步優(yōu)化:如需頻繁判斷[_delegate respondsToSelector:@selector(xx)]的結(jié)果,可考慮實(shí)現(xiàn)含有位段的結(jié)構(gòu)體,將這一判斷結(jié)果緩存至其中

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

現(xiàn)在已采用的方法,為大文件"減負(fù)",如AppDelegate+Configuration

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

分類名和方法名都宜添加前綴,防沖突

26. 勿在分類中聲明屬性

有必要的時(shí)候還是用關(guān)聯(lián)對(duì)象實(shí)現(xiàn)吧,雖然作者不推薦

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

其實(shí)也就是在.m文件中聲明私有屬性,但并不是真的私有,仍然可以用runtime遍歷屬性列表找到該屬性。

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

通過id<xxxDelegate>這種方式聲明匿名對(duì)象,這樣使用者無須關(guān)心這種對(duì)象的具體類型,只專注于它能去做的事即可。

架構(gòu)設(shè)計(jì)

比如要同時(shí)支持百度地圖,高德地圖,谷歌地圖,這時(shí)由于這些第三方庫來自不同的類,所以無法繼承自同一基類,但它們都具有類似的特征,故可以采用這種協(xié)議方法將類似功能的方法提取公開,結(jié)合類簇模式思想調(diào)取對(duì)應(yīng)第三方庫的實(shí)現(xiàn)。

29. 理解引用計(jì)數(shù)

  • retain: 遞增保留計(jì)數(shù)
  • release: 遞減保留計(jì)數(shù)
  • autorelease: 待稍后(通常是下一次"事件循環(huán)",event loop)清理自動(dòng)釋放池(autorelease pool)時(shí),再遞減保留計(jì)數(shù)

保留計(jì)數(shù)至少為1。若保留計(jì)數(shù)為正,則對(duì)象繼續(xù)存活;若保留計(jì)數(shù)降為0,對(duì)象就被銷毀。

30. 以ARC簡(jiǎn)化引用計(jì)數(shù)

  • ARC->MRC:-fno-objc-arc
  • MRC->ARC:-fobjc-arc

alloc,new,copymutableCopy作為前綴的方法(這里要注意的是,后面接的詞語首位必須是大寫字母,即符合駝峰命名規(guī)則,詳見《Objective-C高級(jí)編程:iOS與OS X多線程和內(nèi)存管理》此書),其返回的對(duì)象"歸調(diào)用者所有"(調(diào)用這四種方法的那段代碼要負(fù)責(zé)釋放方法所返回的對(duì)象,即autorelease不生效,需要手動(dòng)額外抵消一次保留操作)。

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

ARC下dealloc方法中應(yīng)該做的

  • 打印銷毀的日志(判斷此方法是否執(zhí)行):NSLog(@"dealloc: %@",[self class]);
  • 移除KVO:[_scrollView removeObserver:self forKeyPath:@"contentOffset"];
  • 移除通知: [[NSNotificationCenter defaultCenter] removeObserver:self];
  • 釋放CoreFoundation對(duì)象:CFRelease(coreFoundationObject)
  • 不推薦清理文件資源,常見的如數(shù)據(jù)庫、播放器等等,關(guān)閉它們更應(yīng)該明確其生命期進(jìn)行管理,而不是延遲到并不一定會(huì)執(zhí)行dealloc方法
  • 不推薦在此關(guān)閉定時(shí)器(正確的定時(shí)器移除應(yīng)該由生命周期方法控制,打破循環(huán)引用):[_timer invalidate](保險(xiǎn)做法見第52條

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

  • ARC下使用@try...@catch...@finally實(shí)際上會(huì)造成try塊內(nèi)所創(chuàng)立的對(duì)象內(nèi)存泄漏,所以往往不這么干
  • ARC下打開異常需打開編譯器的-fobjc-arc-exceptions標(biāo)志,副作用是使應(yīng)用程序變大且降低運(yùn)行效率
  • 異常的處理方法是使用NSError傳回錯(cuò)誤信息,調(diào)用者根據(jù)情況作相應(yīng)處理

33. 以弱引用避免保留環(huán)

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        // use strongSelf
    }
};

Weak-Strong-Dance防止block和對(duì)象間的循環(huán)引用不用多說,偷懶的寫法是使用宏:

#ifndef weakify
#if __has_feature(objc_arc)

#define weakify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
autoreleasepool{} __weak __typeof__(x) __weak_##x##__ = x; \\
_Pragma("clang diagnostic pop")

#else

#define weakify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
autoreleasepool{} __block __typeof__(x) __block_##x##__ = x; \\
_Pragma("clang diagnostic pop")

#endif
#endif

#ifndef strongify
#if __has_feature(objc_arc)

#define strongify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
try{} @finally{} __typeof__(x) x = __weak_##x##__; \\
_Pragma("clang diagnostic pop")

#else

#define strongify( x ) \\
_Pragma("clang diagnostic push") \\
_Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
try{} @finally{} __typeof__(x) x = __block_##x##__; \\
_Pragma("clang diagnostic pop")

#endif
#endif

RAC里的高級(jí)寫法:

#define weakify(...) \\
    autoreleasepool {} \\
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

#define strongify(...) \\
    try {} @finally {} \\
    _Pragma("clang diagnostic push") \\
    _Pragma("clang diagnostic ignored \\"-Wshadow\\"") \\
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \\
    _Pragma("clang diagnostic pop")

34. 以“自動(dòng)釋放池塊”降低內(nèi)存峰值

常用于for循環(huán)中,將循環(huán)體代碼用@autoreleasepool{...}包裹起來,可以及時(shí)在每次循環(huán)后降低內(nèi)存峰值,而不用等到線程執(zhí)行下一次事件循環(huán)時(shí)

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

  • 開啟方式:Xcode -> Edit Scheme -> Run -> Diagnostics -> Enable Zombie Objects<br/
  • 原理:修改對(duì)象的isa指針,令其指向特殊的僵尸類,此僵尸類能夠響應(yīng)所有的選擇子(通過“完整的消息轉(zhuǎn)發(fā)機(jī)制”)。
  • 響應(yīng)方式:如果消息接收者是僵尸對(duì)象(名稱前綴為_NSZombie_),此時(shí)打印一條包含消息內(nèi)容及其接收者的消息,然后終止程序。

調(diào)試時(shí)通過環(huán)境變量NSZombieEnabled開啟此功能,系統(tǒng)將不再回收對(duì)象,而是將其轉(zhuǎn)化為僵尸對(duì)象。

36. 不要使用retainCount

retainCount可能永遠(yuǎn)不返回0。因?yàn)橛袝r(shí)系統(tǒng)會(huì)優(yōu)化對(duì)象的釋放行為,當(dāng)保留計(jì)數(shù)為1且執(zhí)行了遞減操作,這時(shí)為了節(jié)省對(duì)象引用計(jì)數(shù)-1的開銷,會(huì)在稍后的某個(gè)時(shí)間點(diǎn)直接回收。

  • 系統(tǒng)會(huì)盡可能把NSString實(shí)現(xiàn)成單例對(duì)象。如果是編譯期常量,編譯器會(huì)把NSString對(duì)象所表示的數(shù)據(jù)放到應(yīng)用程序的二進(jìn)制文件里,這樣運(yùn)行時(shí)無需再創(chuàng)建NSString對(duì)象。
  • NSNumber類似使用了“標(biāo)簽指針”(把與數(shù)值有關(guān)的全部消息都放在指針值里面)來標(biāo)注特定類型(不包括浮點(diǎn)數(shù)對(duì)象)的數(shù)值,同樣在運(yùn)行時(shí)就無需再創(chuàng)建NSNumber對(duì)象。
  • 進(jìn)行了以上兩種方式優(yōu)化的單例對(duì)象,其保留計(jì)數(shù)絕對(duì)不會(huì)變(很大的值)。這種對(duì)象的保留及釋放都是空操作。

37. 理解“塊”

語法結(jié)構(gòu):return_type (^block_name)(parameters)

  • 塊是一種詞法閉包,與python中的匿名函數(shù)lambda不一樣,它可以無返回值,也可以作為傳參使用
  • 默認(rèn)情況下塊所捕獲的變量是不可以在塊里修改的
  • 使用__block關(guān)鍵字修飾的變量可以在塊內(nèi)部被修改
  • 塊總能修改實(shí)例變量,所以聲明時(shí)無須加__block
  • 通過讀取或?qū)懭氩僮魇箟K捕獲了實(shí)例變量,此時(shí)self變量也自動(dòng)被捕獲了(同時(shí)也會(huì)將其保留),如果此時(shí)self所指代的那個(gè)對(duì)象同時(shí)保留了塊,這種情況就導(dǎo)致了“保留環(huán)”(retain cycle)。

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

使用簡(jiǎn)單,作為屬性時(shí)應(yīng)使用copy修飾

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

建議用同一個(gè)handler塊來處理網(wǎng)絡(luò)成功與失敗情況,比如雖然網(wǎng)絡(luò)請(qǐng)求成功了,但數(shù)據(jù)不符合預(yù)期,這時(shí)方便將錯(cuò)誤信息用NSError一并返回

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

  • 正常使用單例模式的網(wǎng)絡(luò)獲取器一般不會(huì)出現(xiàn)保留環(huán),它運(yùn)行在調(diào)用者所在的隊(duì)列上,所以不用過于擔(dān)心的在返回的塊內(nèi)加@weakify...@strongify...dispatch_async(dispatch_get_main_queue(), ^{...})這樣的代碼;
  • 但如果在調(diào)用者內(nèi)部使用self.completionHandler = completion這樣的代碼將塊持有了(之所有這樣做的原因是為了稍后使用),這時(shí)往往要考慮保留環(huán)的問題,可在_completionHandler()執(zhí)行后使用self.completionHandler = nil方式將其清除。

41. 多用派發(fā)隊(duì)列,少用同步鎖

  • 關(guān)于鎖的相關(guān)知識(shí)可以查看iOS中的鎖——由屬性atomic想到的線程安全

  • GCD是可以高效的代替同步塊或鎖對(duì)象。屬性通常不設(shè)置atomic的原因在于它并非真正的線程安全,讀取操作可以并行,但寫入操作應(yīng)該限定單獨(dú)執(zhí)行。

      //指定全局并發(fā)隊(duì)列,若是跑在主線程上會(huì)出現(xiàn)死鎖
      _syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
      
      - (NSUInteger)ticketCount {
          __block NSUInteger localTicketCount;
          //不開新線程,順序執(zhí)行
          dispatch_sync(_syncQueue, ^{
              localTicketCount = _ticketCount;
          });
          return localTicketCount;
      }
      
      - (void)setTicketCount:(NSUInteger)ticketCount {
          //等待所有并發(fā)塊都執(zhí)行完畢,才會(huì)單獨(dú)執(zhí)行塊內(nèi)代碼
          dispatch_barrier_async(_syncQueue, ^{
              _ticketCount = ticketCount;
          });
      }
    

42. 多用GCD,少用performSelector方法

  • performSelector系列方法使得編譯器無法在編譯器就確定選擇子和返回值,故ARC下并沒有插入內(nèi)存管理方法,易造成內(nèi)存泄漏。
  • performSelector系列方法參數(shù)的局限性太大,都有可替代的方案。

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

NSOperation是對(duì)GCD更高層次的封裝,也提供了GCD無法實(shí)現(xiàn)(很難實(shí)現(xiàn))的特性。關(guān)于它及NSOperationQueue的用法可參照 AFNetworking

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

通過dispatch group可以在并發(fā)隊(duì)列同時(shí)執(zhí)行多項(xiàng)任務(wù),并在這組任務(wù)執(zhí)行完畢時(shí)獲得通知。

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

+ (instancetype)sharedInstance { 
    static ManageClass *sharedInstance = nil; //每次執(zhí)行會(huì)復(fù)用變量
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
        sharedInstance = [[self alloc] init]; 
    });
    return sharedInstance; 
} 

編寫“只需執(zhí)行一次的線程安全代碼”,dispatch_once方式實(shí)現(xiàn)單例性能顯然要高于同步鎖機(jī)制@synchronized

46. 不要使用dispatch_get_current_queue

  • 理解死鎖相關(guān)的模型
  • dispatch_get_current_queue僅應(yīng)該作為調(diào)試使用
  • 隊(duì)列間有層級(jí)關(guān)系,排在某條隊(duì)列中的塊會(huì)在其上級(jí)隊(duì)列里執(zhí)行。

47. 熟悉系統(tǒng)框架

Foundation、CoreFoundation提供的API多了解一下

48. 多用塊枚舉,少用for循環(huán)

  • for循環(huán):優(yōu)點(diǎn)是方便取下標(biāo),缺點(diǎn)是會(huì)多創(chuàng)建一個(gè)中介數(shù)組,額外增加對(duì)附加對(duì)象的創(chuàng)建和釋放操作

  • 快速枚舉:高效,不允許對(duì)遍歷集合修改,獲取當(dāng)前下標(biāo)還需額外定義變量

  • 塊枚舉:提供更完善的塊信息,也可修改塊簽名,無須另行編碼就能并發(fā)執(zhí)行遍歷操作

      - (void)enumerateObjectsUsingBlock:(void(^)(id object, NSUInteger idx, BOOL *stop))block
      - (void)enumerateKeysAndObjectsUsingBlock:(void(^)(id key, id object, BOOL *stop))block
    

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

  • __bridge:OC和CF對(duì)象轉(zhuǎn)化時(shí)只涉及對(duì)象類型不涉及對(duì)象所有權(quán)轉(zhuǎn)化,由發(fā)起轉(zhuǎn)換對(duì)象控制。
  • __bridge_transfer:CF對(duì)象轉(zhuǎn)化成OC對(duì)象時(shí),移交其所有權(quán),由ARC管理,作用同CFBridgingRelease()
  • __bridge_retained:OC對(duì)象轉(zhuǎn)化成CF對(duì)象,移交其所有權(quán),由CF對(duì)象調(diào)用相應(yīng)的CFRelease()方法銷毀,作用同CFBridgingRetain()

50. 構(gòu)建緩存時(shí)使用NSCache而非NSDictionary

NSCache優(yōu)點(diǎn)在于當(dāng)系統(tǒng)資源將要耗盡時(shí),它會(huì)自動(dòng)刪減“最久未使用的”(LRU)緩存。

所以我選擇用 YYCache

51. 精簡(jiǎn)initialize與load的實(shí)現(xiàn)代碼

  • + (void)load:運(yùn)行期(通常是應(yīng)用程序啟動(dòng))每個(gè)類及分類必定且只有一次會(huì)調(diào)用此方法。無法覆寫,常用于method swizzling,盡量不在里面做多余操作
  • + (void)initialize:懶加載模式,首次使用該類之前調(diào)用且只調(diào)用一次。其未覆寫此方法的子類也會(huì)收到超類消息

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

NSTimer會(huì)保留目標(biāo),反復(fù)執(zhí)行的計(jì)時(shí)器會(huì)造成保留環(huán)是個(gè)老生常談的問題了,常常用的方法時(shí)創(chuàng)建一個(gè)分類,使用“塊”來打破保留環(huán)

+ (NSTimer *)weak_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
                                     repeats:(BOOL)repeats
                                handlerBlock:(void(^)(void))handler {
    return [self scheduledTimerWithTimeInterval:timeInterval
                                         target:self
                                       selector:@selector(handlerBlockInvoke:)
                                       userInfo:[handler copy]
                                        repeats:repeats];
}

+ (NSTimer *)weak_timerWithTimeInterval:(NSTimeInterval)timeInterval
                                repeats:(BOOL)repeats
                           handlerBlock:(void (^)(NSTimer *timer))handler {
    return [NSTimer timerWithTimeInterval:timeInterval
                                   target:self
                                 selector:@selector(handlerBlockInvoke:)
                                 userInfo:[handler copy]
                                  repeats:repeats];
}

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

筆記??GitHub鏈接

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容