Effective Objective-C 2.0 讀書筆記 -- 熟悉Objective-C語言

看到Effective這個詞,大家一定會想到《Effective C++》、《Effective Java》等業界名著,那些書里匯聚了多項實用技巧,又系統而深入的講解了各種編程知識。那么,《Effective Objective-C 2.0》也是如此。

作為Mac OS X與iOS應用程序的開發語言,Objective-C作為首選。那么,它有哪些需要注意的呢?

起源

Objective-C與C++、Java一樣,是面向對象的語言,是由Smalltalk演化而來。Smalltalk是消息型語言的鼻祖。消息與函數調用之間的區別看上去就像這樣:

//Messaging (Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

//Function calling (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

關鍵區別在于:使用消息結構的語言,其運行時所應執行的代碼由運行環境來決定;而使用函數調用的語言,則由編譯器決定。

Objective-C是C的“超集”(superset),所以C語言中的所有功能在編寫Objective-C代碼時依然適用。理解C語言的內存模型(memory model),有助于理解Objective-C的內存模型及其“引用計數”(reference counting)機制的工作原理。Objective-C語言中的指針是用來指示對象的。

關于使用頭文件

主要使用 import 關鍵字。然而,我們在 .h 文件中一般首選使用 @class 關鍵字,它能“向前聲明”一個類。對于不需要知道類細節的情況下我們使用它。否則不會輕易使用 import 來引入整個頭文件。

過多的引入頭文件,會增加編譯時間。這就是我們多使用 @class 關鍵字的直接原因。

除非確有必要,否則不要引入頭文件。一般來說,應在某個類的頭文件中使用“向前聲明”來提及別的類,并在實現文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合(coupling)。

有時無法使用“向前聲明”,比如要聲明某個類遵循一項協議。這種情況下,盡量把“該類遵循某協議”的這條聲明移至“class-continuation分類”中。如果不行的話,就把協議單獨放在一個頭文件中,然后將其引入。

字面量語法

在編寫Objective-C程序時,總會用到某幾個類,它們屬于Foundation框架。雖然從技術上來說,不用Foundation框架也能寫出Objective-C代碼,但是實際上卻經常要用到此框架。這幾個類是NSString、NUNumber、NSArray、NSDictionary。從類名上即可看出各自所表示的數據結構。

Objective-C以語法繁雜而著稱。不過從Objective-C 1.0起,有一種簡單的方式能創建NSString 對象。這就是“字符串字面量”(string literal),其語法如下:

NSString *string = @"Effective Objective-C 2.0";

字面數值

NSNumber *number = [NSNumber numberWithInt:10];
//等價于
NSNumber *number = @10;

更多表示:

NSNumber *intNumber = @11;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.1415926;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'ABC';

字面量語法也適用于下述表達式

int x =5;
float y = 6.5f
NSNumber *expressionNumber = @(x * y);

字面量數組

NSarray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
// 等價于
NSarray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

使用數組

NSString *dog = [animals objectAtIndex:1];
// 等價于
NSString *dog = animals[1];

字面量字典

NSDictionary *personData = [NSDictionary dictionaryWithObjectsAnsKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];
// 等價于
NSDictionary *personData = @{@"firstName":@"Matt", @"lastName":@"Galloway", @"age":[NSNumber numberWithInt:28]};

使用字典

NSString *lastName = [personData objectForKey:@"lastName"];
// 等價于
NSString *lastName = personData[@"lastName"];

可變數組和字典

[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
// 等價于
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

局限性

字面量語法有個小小的限制,就是除了字符串以外,所創建出來的對象必須屬于Foundation框架才行。如果自定義了這些類的子類,則無法用字面量語法創建其對象。要想創建自定義子類的實例,必須采用“非字面量語法”(nonliteral syntax)。

使用字面量語法創建出來的字符串、數組、字典對象都是不可變的(immutable)。若想要可變版本的對象,則需要復制一份:

NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy];

這么做會多調用一個方法,而且還要再創建一個對象,不過使用字面量語法所帶來的好處還是多于上述缺點的。

用字面量語法創建數組或字典時,若值中有nil,則會拋出異常。因此,務必確保值里不含nil。

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

編寫代碼時經常要定義常量。掌握了Objective-C與其C語言的基礎的人,也許會用這種方法來做:

#define ANIMATION_DURATION 0.3

上述預處理指令會把源代碼中的ANIMATION_DURATION字符串替換為0.3.預處理過程會把碰到的所有ANIMATION_DURATION一律替換成0.3,這樣的話,假設此指令聲明在某個頭文件中,那么所有引入了這個頭文件的代碼,其ANIMATION_DURATION都會被替換。

要解決此問題,應該設法利用編譯器的某些特性才對。

static const NSTimeInterval kAnimationDuration = 0.3;

用此方式定義的常量包含類型信息,其好處的清楚地描述了常量的含義。

常用的命名法是:

  • 若常量局限于某”編譯單元”(translation unit,也就是“實現文件”,implementation file)之內,則在前面加字母k;
  • 若常量在類之外可見,則通常以類名為前綴。

定義常量的位置很重要。在頭文件里聲明預處理指令,這樣會增加常量名稱互相沖突的可能性。

在頭文件中使用extern來聲明全局常量,并在相關實現文件中定義其值。這種常量要出現在全局符號表中,所以其名稱應加以區隔,通常用與之相關的類名做前綴。

枚舉使用

枚舉只是一種常量命名方式。某個對象所經歷的各種狀態就可以定義為一個簡單的枚舉集(enumeration set)。

enum IHConnectionState {
    IHConnectionStateDisconnected,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

默認情況下,枚舉起始值為0,以后依次遞增,1,2,3...

其實還可以我們自己指定枚舉值:

enum IHConnectionState {
    IHConnectionStateDisconnected = 1,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

也可以定義為位移值:

enum UIViewAutoresizing {
    UIViewAutoresizing = 0,
    UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
    UIViewAutoresizingFlexibleWidth = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin = 1 << 3,
    UIViewAutoresizingFlexibleHeight = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

關于枚舉,Foundation框架中定義了一些輔助的宏,用這些來定義枚舉類型時,也可以指定用于保存枚舉值的底層數據類型。

typedef NS_ENUM(NSUInteger, IHConnectionState) {
    IHConnectionStateDisconnected = 1,
    IHConnectionStateConnecting,
    IHConnectionStateConnected
};

typedef NS_OPTIONS(NSUInteger, IHPermittedDirection) {
    IHPermittedDirectionUp = 1 << 0,
    IHPermittedDirectionDown = 1 << 1,
    IHPermittedDirectionLeft = 1 << 2,
    IHPermittedDirectionRight = 1 << 3
};

這些宏的定義如下:

#if(__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))

    #define NS_ENUM(_type, _name)
        enum _name:_type _name; enum _name:_type
    #if (__cplusplus)
        #define NS_OPTIONS(_type, _name)
            type _name; enum:_type
    #else
        #define NS_OPTIONS(_type, _name)
            enum _name:_type _name; enum _name:_type
    #endif
#else
    #define NS_ENUM(_type, _name) _type _name; enum
    #define NS_OPTIONS(_type, _name) _type _name; enum
#endif

第一個#if用于判斷編譯器是否支持新式枚舉。如果不支持,那么就用老式語法來定義枚舉。

在處理枚舉類型的switch語句中不要實現default分支。這樣的話,加入新枚舉之后,編譯器就會提示開發者:switch語句并未處理所有枚舉。

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

推薦閱讀更多精彩內容