《Effective Objective-C 2.0》 - 編寫高質(zhì)量iOS與OS X代碼的52個(gè)有效方法 簡(jiǎn)介

原著作者是 Matt Galloway
出版社是機(jī)械工業(yè)出版社
共7章52個(gè)小節(jié)

第1章 熟悉Objective-C

1.了解Objetive-C語(yǔ)言的起源

* 為什么要了解OC的起源?
* OC語(yǔ)言的特性是什么?跟其他語(yǔ)言有什么區(qū)別?

OC是C語(yǔ)言的一個(gè)超集,是一門面向?qū)ο蟮恼Z(yǔ)言
OC是一門使用“消息結(jié)構(gòu)”的動(dòng)態(tài)語(yǔ)言,只有在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型

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

* 為什么?
* 怎么做?

避免了在編譯期暴露太多細(xì)節(jié)
使引入頭文件的時(shí)機(jī)盡量延后,減少編譯時(shí)間
避免循環(huán)引用頭文件
使用@class向前聲明

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

*什么是字面量語(yǔ)法?

字面量語(yǔ)法就是使用“@”的語(yǔ)法糖

NSString *str = @"abc";
NSArray *arr = @[@"a", @"b"];
NSDictionary *dict = @[@"key1":@"v1", @"key2":@"v2"];

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

當(dāng)要定義常量的時(shí)候,使用"const"代替#define,可以更清晰表明數(shù)據(jù)類型
使用"static const"定義當(dāng)前編譯單元內(nèi)可用常量
使用"extern const"暴露全局常量

xx.h
extern NSString *const SHPersonNick; // 暴露出去

xx.m
static NSString *const SHPersonName = @"Name"; // 當(dāng)前.m可用
NSStrng *const SHPersonNick = @"Nick"; // 全局常量,其他文件不可存在同名常量

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

使用NS_ENUM、NS_OPTIONS來(lái)定義狀態(tài)、選項(xiàng),編譯器會(huì)根據(jù)系統(tǒng)結(jié)構(gòu)使用不同的實(shí)現(xiàn)。
在處理枚舉的switch語(yǔ)句中不要使用default分支,這樣編譯器能自動(dòng)檢查是否已經(jīng)處理了所有的枚舉。

typedef NS_ENUM(NSUInteger, SHColor) {
    SHColorRed,
    SHColorBlue,
    SHColorGreen
};

switch (color) {
    case SHColorRed:
    //...
    break;
    
    case SHColorBlue:
    //...
    break;
    
    case SHColorGreen:
    //...
    break;
}

第2章 對(duì)象、消息、運(yùn)行期

6. 理解“屬性”這一概念

能自動(dòng)生成實(shí)例變量、setter和getter方法。
可以設(shè)置原子性、讀寫權(quán)限、內(nèi)存管理語(yǔ)義。

@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign, readonly) int age;

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

讀用實(shí)例,寫用setter

*為什么?

讀取需要速度,寫可能需要處理。

self.name = @"Tom";
NSLog(@"name = %@", _name);

但是如果使用了延遲加載,就是用getter進(jìn)行讀取。

- (NSString *)name {
  if (!_name) {
     _name = @"Sam";  
  }
  return _name;
}

NSLog(@"name = %@", self.name);

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

就是使用“==”操作符來(lái)比較對(duì)象。
可以覆寫isEqual:和hash方法實(shí)現(xiàn)自定義類的等同比較。

*實(shí)現(xiàn)了isEqual:為什么還要實(shí)現(xiàn)hash?

當(dāng)isEqual:判斷兩對(duì)象相等,hash也必須返回同一個(gè)值。
但是兩個(gè)對(duì)象擁有同樣的hash值,isEqual:方法未必判斷為相等。
實(shí)現(xiàn)hash是為了解決在colleciton中使用對(duì)象時(shí)的索引問(wèn)題。
好的hash算法能帶來(lái)性能上的提升(速度高,哈希碰撞率低)。
其實(shí)就是hash表算法,所以就可以理解相同的對(duì)象必有相同的hash值。

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

創(chuàng)建一個(gè)具有“公共接口”的類,作為抽象基類。
實(shí)現(xiàn)“工廠模式”。
使用枚舉定義抽象基類,根據(jù)枚舉值創(chuàng)建不同的的對(duì)象。

typedef NS_ENUM(NSUInteger, SHEmployeeType) {
    SHEmployeeTypeDeveloper,
    SHEmployeeTypeDesigner,
    SHEmployeeTypeFinace
};

+ (SHEmployee *)employeeWithType:(SHEmployeeType)type {
    switch (type) {
        case SHEmployeeTypeDeveloper:
            return [SHEmployeeDeveloper new];
            break;
        
        case SHEmployeeTypeDesigner:
            return [SHEmployeeDesigner new];
            break;
            
        case SHEmployeeTypeFinace:
            return [SHEmployeeFinance new];
            break;
    }   
}

大部分collection類都是類族,例如NSArray

NSArray *a = [NSArray array]; // a對(duì)象的類并不是NSArray

if ([a isMemberOfClass:[NSArray class]]) {
    // 不能進(jìn)入
}

if ([a isKindOfClass:[NSArray class]]) {
    // 能進(jìn)入
}

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

就是使用運(yùn)行時(shí)添加關(guān)聯(lián)對(duì)象,此法也可以在分類中實(shí)現(xiàn)“模擬添加屬性”。

 #import <objc/runtime.h>

const char nameKey;

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, &nameKey);
}


11. 理解objc_msgSend的作用

OC是使用“消息結(jié)構(gòu)”的語(yǔ)言,調(diào)用對(duì)象方法的實(shí)質(zhì)是使用objc_msgSend給對(duì)象發(fā)送消息。
消息由“動(dòng)態(tài)消息派發(fā)系統(tǒng)”處理,尋找方法實(shí)現(xiàn)并執(zhí)行。

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

在對(duì)象方法緩存中尋找方法實(shí)現(xiàn),若無(wú),在對(duì)象類中尋找。
若當(dāng)前對(duì)象無(wú)法響應(yīng)方法調(diào)用,就進(jìn)入消息轉(zhuǎn)發(fā)流程。

  1. 調(diào)用resolveInstanceMethod調(diào)用備用方法。
  2. 若沒(méi)有,調(diào)用forwardingTargetForSelector方法尋找備用接受者。
  3. 若沒(méi)有,調(diào)用forwardInvocation啟用完整的消息轉(zhuǎn)發(fā)機(jī)制,指定目標(biāo)接受者,此方法效果上“2”類似。
  4. 若沒(méi)有,拋出“消息未能處理”。

ps:core data中的字段屬性就是標(biāo)注了@dynamic, 使用了消息轉(zhuǎn)發(fā)機(jī)制,自己實(shí)現(xiàn)了字段屬性的setter、getter

13. 用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”

使用運(yùn)行時(shí)方法method_exchangeImplementations(Method m1, Method m2),改變方法實(shí)現(xiàn)。
此方法也可以實(shí)現(xiàn)hook(給方法加鉤子)。

Method m1 = objc_getInstanceMethod([A class], @selector(doSomething));
Method m2 = objc_getInstanceMethod([A class], @selector(doOtherthing));
objc_exchangeImplementations(m1, m2);

14. 理解“類對(duì)象”的用意

*什么是類對(duì)象?
  1. 每個(gè)對(duì)象結(jié)構(gòu)體都有一個(gè)“isa”指針,指向?qū)ο笏鶎俚念悺?/li>
  2. 類的結(jié)構(gòu)體存放的是此類的“元數(shù)據(jù)(metadata)”,存放有類的實(shí)例方法、實(shí)例變量。
  3. 類的結(jié)構(gòu)體上也有一個(gè)“isa”指針,指向“元類(metaclass)”,存放了類的類方法(可以理解成類對(duì)象的實(shí)例方法),說(shuō)明類也是Objective-C對(duì)象。
  4. 結(jié)構(gòu)體上還有一個(gè)指針“super_class”指向此類的超類(父類)。
              NSObject類--isa--> NSObject元類
                |                |
                |超類             |超類
A類對(duì)象 --isa--> A類 -- isa --> A元類

第3章 接口與API設(shè)計(jì)

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

OC沒(méi)有命名空間,項(xiàng)目?jī)?nèi)的類名不能重復(fù),所以可以使用前綴進(jìn)行區(qū)分。
原則上蘋果公司保留所有2位字符開(kāi)頭的前綴,所以最好使用3位或以上的前綴。

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

即提供包含了所需參數(shù)的初始化方法。
注意如果超類的初始化方法不適合子類,在子類中必須覆寫。

- (id)initWithString:(NSString *)string;

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

實(shí)現(xiàn)了description方法,可以在控制臺(tái)打印該實(shí)例。
如果想在調(diào)試的時(shí)候輸出更詳細(xì)的信息,可以實(shí)現(xiàn)debugDescription。

- (NSString *)description {
    return [NSString stringWithFormat:@"%@ - %@", _name, _desc];
}

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

在頭文件中,如果不是需要暴露出去的屬性,最有所有權(quán)修飾符定為"readonly",然后在實(shí)現(xiàn)文件中設(shè)置可讀寫。
可以提供修改方法來(lái)修改不可寫屬性。

//SHPerson.h
@property(nonatomic, copy, readonly) NSString *name;
- (void)modifyName:(NSString *)name;

//SHPerson.m
@property(nonatomic, copy) NSString *name;

- (void)modifyName:(NSString *)name {
    _name = [NSString stringWithFormat:@"name: %@", name];
}

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

注意類名、方法名、變量名、常量名的命名方式。
方法名盡量詳盡易懂,盡可能描述這個(gè)方法做了什么。

20. 為私有方法名加前綴

更好區(qū)分公共方法、私有方法。
蘋果公司保留使用下劃線開(kāi)頭的方法名。

- (void)p_doSomePrivateThing;

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

在發(fā)生嚴(yán)重錯(cuò)誤的時(shí)候跑出NSException,停止程序

if (/* terrible error */) {
    @throw [NSException exceptionWithName:@"Exp"
                                    reason:@"There was an error"
                                  userInfo:nil];
}

22. 理解NSCopying協(xié)議

實(shí)現(xiàn)NSCopying協(xié)議和其中的方法"copyWithZone:",調(diào)用copy方法的時(shí)候回調(diào)用此方法
zone是過(guò)時(shí)的東西,不必理會(huì)。
對(duì)于collection類來(lái)說(shuō),“copyWithZone:”對(duì)應(yīng)的是淺拷貝copy方法;如果要進(jìn)行深拷貝,可以實(shí)現(xiàn)“NSMutableCopying”協(xié)議和"mutableCopyWithZone:"方法,對(duì)應(yīng)mutableCopy方法。

//SHPerson.h
@interface SHPerson : NSObject <NSCopying>

@proeprty(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *nick;

@end

//SHPerson.m
- (id)copyWithZone:(NSZone *)zone {
    SHPerson *person = [][SHPerson alloc] init];
    person.name = _name;
    person.nick = _nick;
}

第4章 協(xié)議與分類

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

可以參考UITableView的UITableViewDataSource、UITableViewDelegate協(xié)議。

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

不多說(shuō),就是拆分邏輯,降低耦合。

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

防止和系統(tǒng)原有的類沖突。

26. 勿在分類中聲明屬性

在分類中聲明屬性也并不能自動(dòng)生成實(shí)例變量和setter、getter方法
如果要使用,可以使用runtime運(yùn)行時(shí)的關(guān)聯(lián)對(duì)象模擬setter、getter

//xx.h
 #import "PCBitch.h"

@interface PCBitch (Asshole)

@property(nonatomic, copy) NSString *assholeName;

@end

//xx.m
 #import "PCBitch+Asshole.h"
 #import <objc/runtime.h>

static char assholeNameChar;

@implementation PCBitch (Asshole)

- (void)setAssholeName:(NSString *)assholeName {
    objc_setAssociatedObject(self, &assholeNameChar, assholeName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)assholeName {
    return objc_getAssociatedObject(self, &assholeNameChar);
}

@end


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

就是使用“內(nèi)擴(kuò)展”,隱藏不必要暴露的屬性、方法。

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

參考代理模式的實(shí)現(xiàn)。

@property(nonatomic, weak) id<SHPersonDelegate> delegate; //隱藏了類型,就是無(wú)論delegate是什么類,反正只要實(shí)現(xiàn)了SHPersonDelegate協(xié)議就可以了 

第5章 內(nèi)存管理

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

通過(guò)遞增遞減的計(jì)數(shù)器來(lái)管理內(nèi)存,若計(jì)數(shù)等于0,對(duì)象會(huì)被銷毀,系統(tǒng)會(huì)標(biāo)記此內(nèi)存可回收使用。
蘋果是通過(guò)一個(gè)計(jì)數(shù)表類保存各個(gè)對(duì)象的計(jì)數(shù)和內(nèi)存地址的。

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

Automatic Reference Counting技術(shù),編譯器自動(dòng)管理引用技術(shù),再也不用手動(dòng)寫retain、release啦。
需要注意Core Foundation對(duì)象不歸ARC管理,需要手動(dòng)進(jìn)行分配和釋放。

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

在dealloc方法中必須釋放資源、取消KVO或者Notification通知,不然再次接收到通知時(shí)會(huì)crash,因?yàn)橛^察者已經(jīng)被銷毀釋放了。
除此之外的方法不要調(diào)用。

- (void)dealloc {
    CFRelease(coreFoundationObject);
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

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

捕獲異常的時(shí)候要清理干凈try內(nèi)創(chuàng)建的對(duì)象。

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

就是使用__weak關(guān)鍵字修飾互相引用的其中一個(gè)對(duì)象,避免互相強(qiáng)引用。

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

系統(tǒng)在runloop中會(huì)創(chuàng)建一個(gè)autoreleasepool,所以每次循環(huán)結(jié)束都能自動(dòng)回收對(duì)象,但是例如當(dāng)使用循環(huán)創(chuàng)建大量對(duì)象并使用完成之后,并不會(huì)立刻釋放對(duì)象,造成內(nèi)存高峰。

for (int i=0; i<9999; i++) {
    @autoreleasepool { // 這樣就能每創(chuàng)建一個(gè),就釋放舊的一個(gè)
        SHPerson *p = [[SHPerson alloc] init];
        [p doSomething];
    }
}

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

在Xcode的環(huán)境變量中設(shè)置NSZomebieEnabled開(kāi)啟,可以使對(duì)象計(jì)數(shù)為0的時(shí)候不會(huì)被系統(tǒng)真正回收,而是成為僵尸對(duì)象。
系統(tǒng)會(huì)修改對(duì)象的isa指針,指向一個(gè)僵尸類,從而改變?yōu)榻┦瑢?duì)象。

36. 不要使用retainCount

第6張 塊(Block)與大中樞派發(fā)(GCD)

37. 理解“塊”這一概念

  1. block類似于函數(shù)指針。
  2. 在block聲明的范圍能,能捕獲所有變量。
  3. 如果變量是對(duì)象,block會(huì)自動(dòng)保留,知道block被釋放。
  4. block也可以有引用計(jì)數(shù)(堆block)。
  5. block會(huì)把捕獲的變量都拷貝一份。
  6. block分為全局block、棧block和堆block。
  7. 定義的時(shí)候,block默認(rèn)分配在棧中,所以如果編譯器覆蓋了該塊內(nèi)存,而調(diào)用此block會(huì)發(fā)生崩潰。
  8. 鑒于上述的情況,需要對(duì)block進(jìn)行copy操作,把這個(gè)block移到堆內(nèi)存中,并具有引用計(jì)數(shù)。
  9. 創(chuàng)建一個(gè)block屬性的時(shí)候,使用copy策略,可以把set進(jìn)來(lái)的block放入堆中。
  10. 全局block在編譯器定義,不能捕獲任何變量,有點(diǎn)像靜態(tài)常量。

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

typedef void(^doSomethingBlock)(void) DoSomethingBlock;

DoSomethingBlock block = ^(void) {
    // do something
}

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

使用block比delegate能使代碼更緊湊,邏輯更通順。

- (void)doSomethingWithParam:(id)param completion:(void (^)(void))completion;

[object doSomethingWithParam:param completion:^(void) {
    // do something after completion
}];

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

使用__weak修飾,解決reatain cycle問(wèn)題。

__weak typeof(self) weakSelf = self;

[self setDoSometingBlock:^(void) { // self持有block
    [weakSelf doOneThing]; // block持有self
}];

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

使用派發(fā)隊(duì)列代替同步鎖,能減少crash幾率。

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

由于performSelector對(duì)選擇子的返回類型、參數(shù)個(gè)數(shù)有限制,如果要使用跨線程調(diào)用方法,最好使用GCD。

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

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

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

46. 不要使用dispatch_get_current_queue

此函數(shù)已被廢棄。

第7張 系統(tǒng)框架

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

48. 多用塊枚舉,少用for循壞

遍歷一個(gè)collection有4種方式:for循環(huán)、NSEnumerator、快速遍歷、塊枚舉。
塊枚舉比f(wàn)or循環(huán)提供更多的信息。

NSArray *list = ...;

// for循環(huán)
for (int i=0; i<list.count; i++) {
    
}

// 快速遍歷
for (id object in list) {

}

// NSEnumerator
NSEnumerator *enum = [list objectEnumerator];
id object;
while (object = [enum nextObject]) {
    
}

// 塊枚舉
[list enumerateObjectUsingBlock:^(id object, NSUInteger index, BOOL stop) {
    
}]

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

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

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

  1. 加入了運(yùn)行期的每個(gè)類和分類都會(huì)調(diào)用load方法,先調(diào)用類的,再調(diào)用分類。
  2. 在load方法中調(diào)用其他類是不安全的,因?yàn)椴淮_保其他類都已經(jīng)被加載了。
  3. 如果一個(gè)類沒(méi)有實(shí)現(xiàn)load,不過(guò)其超類有沒(méi)有實(shí)現(xiàn),系統(tǒng)都不會(huì)調(diào)用。
  4. initialize該方法會(huì)在程序首次調(diào)用此類的時(shí)候調(diào)用,是惰性調(diào)用。
  5. initialize和普通方法一樣,如果類沒(méi)有實(shí)現(xiàn),就會(huì)到超類中尋找。
  6. 可以在initialize方法中初始化無(wú)法在編譯器確定的全局變量。

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

// _timer會(huì)保留self,產(chǎn)生retain cycle
_timer = 
[NSTimerscheduledTimerWithTimeInterval:1.0
                                 target:self
                               selector:@selector(doSomething)
                               userInfo:nil
                               repeast:YES];
最后編輯于
?著作權(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)容