《Effective Objective-C 2.0》 閱讀筆記 1

1: Objective-C語言起源

Objective-C(以下簡稱OC)由SmallTalk語言演化而來。OC采用"消息結(jié)構(gòu)"的語法方式,是一種動態(tài)語言。與傳統(tǒng)的“函數(shù)調(diào)用”式語言相比,OC實(shí)際執(zhí)行的動作由運(yùn)行時而非編譯期決定。就好像是“函數(shù)調(diào)用”式的函數(shù)是多態(tài)一樣。

OC的對象總是分配在“堆”上的。但是我們不需要使用 malloc 和 free來分配和釋放這些內(nèi)存,這些工作由OC的“引用計(jì)數(shù)“自動完成。

2: 在類的頭文件中盡量少引用其他頭文件

在C語言中我們已經(jīng)知道這一規(guī)則,即“前置聲明“(forward declaring)。在不需要知道某個類的詳細(xì)細(xì)節(jié)的時候,我們最好在頭文件中前置聲明該類,然后在實(shí)現(xiàn)文件中引用類的頭文件。如EPerson類有一個EEmployer的成員:

//EPerson.h

@class EEmployer;//前置聲明
@interface EPerson : NSObject
...
@property (nonatomic, strong) EEmployer *employer;
@end

//EPerson.mm

#import "EPerson.h"
#import "EEmployer.h"

@implementation EPerson
...
@end

這樣做有幾個好處: 一是可以優(yōu)化編譯時間;而是可以避免頭文件循環(huán)引用。
雖然#import指令可以避免死循環(huán),但意味著有一個類文件無法被正確編譯。

3. 多用字面量語法(string literal)

用類似C語言的語法,如:

NSString *somStr = @"This is a string literal";

NSNumber *someNum = @1;
NSNumber *floatNum = @2.5f;
NSNumber *boolNum = @YES;

NSArray *animals = @[@"cat",@"dog",@"mouse"];

NSDictionary *personDic = @{@"firstName":@"Matt", @"lastName":@"Galloway"};

優(yōu)點(diǎn):

  1. 簡潔易讀。
  2. 編寫、修改簡單。
  3. 對于數(shù)組和字典,還可以及早拋出異常。比如其中有nil的元素,字面量語法會直接拋出異常,但普通的alloc方法生成的數(shù)組或字典只會截取nil之前的元素,會誤導(dǎo)我們。

缺點(diǎn):

  1. 除了字符串以外,字面量語法創(chuàng)建的對象必須屬于Foundation框架,不能屬于自定義的類。
  2. 字面量語法創(chuàng)建的對象是不可變的,若要可變版本的對象,還要復(fù)制一份。

4.用類型常量代替宏定義

這點(diǎn)在C語言中就提到過,好處就是利用編譯器特性,可以驗(yàn)證類型。

如果常量只用在一個編譯單元內(nèi),則在其.m文件中用static const修飾

如果常量需要全局可見,則在一個頭文件中使用extern聲明全局變量,并在某一個實(shí)現(xiàn)文件中定義其值。這種常量出現(xiàn)在全局符號表中,通常用與之相關(guān)的類名做前綴。

5. 枚舉類型

typedef NS_ENUM(NSUInteger, EOCConnectionState) {
    EOCConnectStateDisconnected,
    EOCConnectStateConnecting,
    EOCConnectStateConnected    
}

typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {
    EOCPermittedDirectionUp     = 1 << 0,
    EOCPermittedDirectDown      = 1 << 1,
    EOCPermittedDirectLeft      = 1 << 2,
    EOCPermittedDirectRight     = 1 << 3,
}

6. 理解"屬性"

@synthesize 可以更改默認(rèn)的實(shí)例變量名,但一半不推薦使用,為了使代碼可讀性更強(qiáng)。

@dynamic 可以阻止編譯器自動合成存取方法。而且編譯時發(fā)現(xiàn)沒有定義存取方法,也不會報錯,它相信這些方法能在運(yùn)行期間找到。

  • 原子性

用在多線程同時訪問一個屬性的場景。開發(fā)中我們一般都用的是nonatomic,原因是原子性要使用同步鎖,這種開銷比較大,而且在一個線程在連續(xù)多次讀取某屬性值的時候有別的線程在同時改寫該值,那么即便將該屬性聲明為nonatomic,還是會讀到不同的屬性值,因而還是不能保證“線程安全”。若真想實(shí)現(xiàn)“線程安全“,還要更深層的鎖定機(jī)制。

  • 讀寫權(quán)限

  • 內(nèi)存管理語義

  • 方法名

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

直接使用實(shí)例變量_firstName與使用存取方法self.firstName有幾個區(qū)別:

1). 直接訪問實(shí)例變量不需要消息轉(zhuǎn)發(fā)機(jī)制,編譯器生成帶啊直接訪問實(shí)例變量所在的內(nèi)存區(qū)域,速度快。

2). 直接訪問實(shí)例變量不會調(diào)用”設(shè)置方法“,這樣繞過了屬性相關(guān)的"內(nèi)存管理語義“,這樣不太好。

3). 直接訪問實(shí)例變量,不會觸發(fā)KVO通知,也有可能出現(xiàn)問題。

4). 使用屬性方法助于斷點(diǎn)調(diào)試

這種方案是: 寫入實(shí)例變量時,使用設(shè)置方法,讀取實(shí)例變量時,直接訪問。這樣既可以提高讀寫速度,又可以確保屬性的“內(nèi)存管理語義”。

這個方案注意亮點(diǎn):

1). 在初始化方法中基本總是應(yīng)該直接訪問實(shí)例變量,除非待初始化的變量是聲明在超類里,我們又在子類中無法直接訪問。

2). 使用了懶加載技術(shù)后,都要通過存取方法來訪問。

8. 理解“對象等同性”

比較對象時,“==”操作符只是比較兩者指針本身,應(yīng)該使用"isEqual"方法或者對象本身提供的"等同性判斷方法",后者要求受測對象屬于同一個類。

NSObject協(xié)議中,有兩個用于判斷等同性的關(guān)鍵方法:

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

如果isEqual方法判定兩個對象相等,那么hash方法也必須返回同一個值; 如果兩個對象的hash方法返回同一個值,那么isEqual方法未必認(rèn)為兩者相等。

覆寫hash方法時,既要考慮效率也要考慮碰撞率。

一些特定類具有自己的等同性判斷方法:

NSString -> isEqualToString
NSArray -> isEqualToArray
NSDictionary -> isEqualToDictionary

我們可以自己來判斷等同性,這樣既可以無須檢查參數(shù)類型,提升檢測速度,也使代碼更美觀易讀。

等同性判定有深度之分,比如NSArray可以比較每個元素是否相等(深度等同性判定),也可以只判定部分?jǐn)?shù)據(jù)是否相等。要根據(jù)具體需求制定檢測方案。

把可變對象放入容器之后,盡量不要再改變對象內(nèi)容,這樣有隱患。

9. 類族模式

類族模式可以隱藏抽象基類背后的實(shí)現(xiàn)細(xì)節(jié)。

“工廠模式”是其中之一。

Cocoa系統(tǒng)框架中有很多類族,如UIKit、NSArray等。

10. 關(guān)聯(lián)對象

這個在做method swizzling的時候會經(jīng)常用到。

將兩個對象關(guān)聯(lián)起來,再別的地方需要用到的時候再讀取出來,類似給對象動態(tài)添加屬性。

關(guān)聯(lián)時要指定存儲策略,類似于屬性添加內(nèi)存語義。

UIAlertView是一個好例子:

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

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    void (^block)(NSInteger) = objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
    block(buttonIndex);
}

11. 理解消息傳遞(objc_msgSend)

這個術(shù)語我們已經(jīng)很熟悉了,Objective-C中就是objc_msgSend,使用動態(tài)綁定機(jī)制,在運(yùn)行時才決定調(diào)用那種方法。

編譯器會將所有的消息轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語言調(diào)用:

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

objc_msgSend會依據(jù)接受者與選擇器的類型來動態(tài)調(diào)用適當(dāng)?shù)姆椒?。首先,在接收者所屬的類中搜尋“方法列表”,若找到與選擇器名稱相符合的方法,就跳轉(zhuǎn)至其實(shí)現(xiàn)的代碼;若找不到,就沿著繼承體系繼續(xù)向上查找;若最終沒有找到,就執(zhí)行“消息轉(zhuǎn)發(fā)“(message forwarding)機(jī)制。
同時,objc_msgSend會將匹配的結(jié)果緩存在類的“快速映射表”中,以后遇到與選擇器相同的消息就可以直接執(zhí)行了。

每個類中有函數(shù)指針表(類似于C++中的虛函數(shù)表),指針指向函數(shù)的實(shí)現(xiàn)地址,選擇器的名稱是查表時用的key。

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

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

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