標籤:
[編寫高品質iOS代碼的52個有效方法](一)Objective-C基礎
參考書籍:《Effective Objective-C 2.0》 【英】 Matt Galloway
先睹為快
1.瞭解Objective-C語言的起源
2.在類的標頭檔中盡量少引入其他標頭檔
3.多用字面量文法,少用與之等價的方法
4.多用類型常量,少用#define前置處理器指令
5.用枚舉表示狀態、選項、狀態代碼
目錄
- 編寫高品質iOS代碼的52個有效方法一Objective-C基礎
- 先睹為快
- 目錄
- 第1條瞭解Objective-C語言的起源
- 第2條在類的標頭檔中盡量少引入其他標頭檔
- 第3條多用字面量文法少用與之等價的方法
- 第4條多用類型常量少用define前置處理器指令
- 第5條用枚舉表示狀態選項狀態代碼
第1條:瞭解Objective-C語言的起源
Objective-C與C++,Java等物件導向語言的區別在於Objective-C使用“訊息結構”,而不是“函數調用”。Objective-C語言是由訊息型語言鼻祖Smalltalk演化而來。
訊息與函數調用之間的文法區別:
// 訊息(Objective-C)Object *obj = [Object new];[obj performWith:parameter1 and:parameter2];// 函數調用(C++)Object *obj = new Object;obj->perform(parameter1,parameter2);
關鍵區別在於:使用訊息結構的語言,其運行時所應執行的代碼由運行環境來決定;而使用函數調用的語言,則由編譯器決定。Objective-C的重要工作都由運行時組件而非編譯器完成。
Objective-C是C的超集,所以C語言中的所有功能在編寫Objective-C代碼時依然適用。理解C語言的記憶體模型尤為重要,這有助於理解Objective-C的記憶體模型及其引用計數機制的工作原理。Objective-C語言中的指標是用來指示對象的。想要聲明一個變數,另其指代某個對象:
NSString *someString = @"the string";NSString *anotherString = someString;
兩個變數都是指向NSString的指標。所有Objective-C語言的對象都必須這樣聲明,因為對象所佔記憶體總是分配在堆中,而不是棧中。不在再棧上分配Objective-C對象。而兩個變數所佔記憶體都分配在棧上,且兩塊記憶體裡的值一樣,都是NSString對象的記憶體位址。
分配在堆中的記憶體必須直接管理,而分配在棧中用於儲存變數的記憶體則會在其棧幀彈出時自動清理。
在Objective-C代碼中,有時也會遇到定義裡不含*的變數,它們可能會使用棧空間,例如:
CGRect frame;
CGRect是C結構體,整個系統架構都在使用這種結構體。如果改用Objective-C對象來做,需要額外開銷,如分配及釋放堆記憶體等。如果只需要儲存int、float、double、char等非物件類型,通常使用CGRect這種結構體就可以了。
第2條:在類的標頭檔中盡量少引入其他標頭檔
與C和C++一樣,Objective-C也使用標頭檔與實現檔案來區隔代碼。
建立一個EOCPerson類,並在類中用到另一個類EOCEmployer類的執行個體
// EOCPerson.h#import <Foundation/Foundation.h>@class EOCEmployer;@interface EOCPerson : NSObject@property(nonatomic, copy) NSString *firstName;@property(nonatomic, copy) NSString *lastName;@property(nonatomic, strong) EOCEmployer *employer;@end// EOCPerson.m#import "EOCPerson.h"#import "EOCEmployer.h"@implementation EOCPerson// 實現方法@end
在標頭檔中用@class EOCEmployer;語句告訴編譯器需要用到這個類,不需要知道該類的全部細節,再在需要知道其所有介面細節的實現檔案中用#import "EOCEmployer.h"引入EOCEmployer類。這種做法叫做向前聲明。如果直接在標頭檔中引入EOCEmployer.h,則會一併引入EOCEmployer.h中的所有內容,此過程持續下去,則要引入許多根本用不到的內容,增加編譯時間,還可能造成循環參考。
但有些時候無法使用向前聲明,例如自訂的類繼承自某個超類或遵從某個協議。
// EOCRectangle.h#import "EOCShape.h"#import "EOCDrawable.h"@interface EOCRectangle : EOCShape <EOCDrawable>@property(nonatomic, assign) float width;@property(nonatomic, assign) float height;@end
如果自訂的類繼承於某個超類,則必須引入定義那個超類的標頭檔,如果要遵從某個協議,盡量把該類遵循某協議的聲明移到分類(什麼是分類?)中。如果不行的話,就把協議單獨放在一個標頭檔中,然後將其引入。
第3條:多用字面量文法,少用與之等價的方法
Foundation架構中的NSString、NSNumber、NSArray、NSDictionary這4個類的執行個體的聲明,即可以用常見的alloc及init方法,也可以直接用字面量文法來聲明:
// 將整數、浮點數封入到Objective-C對象中,可以用字面量,也可以調用NSNumber中的方法。// 字面量NSNumber *intNumber = @1;// 等價方法NSNumber *intNumber = [NSNumber numberWithInt:1];// 字面量NSNumber *doubleNumber = @3.14159// 等價方法NSNumber *doubleNumber = [NSNumber numberWithDouble:3.14159];// 字面量文法也適用於運算式int x = 5;float y = 6.32f;NSNumber *expressionNumber = @(x * y);
// 使用方法和字面量文法建立數組NSArray *animals = [NSArray arrayWithObjects:@"cat",@"dog",@"mouse",nil];NSArray *animals = @[@"cat",@"dog",@"mouse"];// 使用方法和字面量文法擷取下標對應的對象NSString *dog = [animals objectAtIndex:1];NSString *dog = animals[1];
使用字面量的好處:假如建立一個含有3個對象的數組,第二個對象為nil,其他兩個對象都有效,如果用字面量文法建立,則在運行時會拋出異常,可以更快找到錯誤。而如果用arrayWithObjects:方法,則不會拋出異常,但建立的數組會只包含第一個對象,因為該方法會依次處理各個參數,直到發現nil為止。這樣會導致錯誤不容易被發現。
// 使用方法和字面量文法建立字典NSDictionary *personData = [NSDictionary dictionaryWithObjectAndKeys:@"Matt",@"firstName",@"Galloway",@"lastName",[NSNumber numberWithInt:28],@"age",nil];NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@28};// 使用方法和字面量文法擷取下標對應的對象NSString *lastName = [personData objectForKey:@"lastName"];NSString *lastName = personData[@"lastName"];
用字面量文法建立字典也有類似優點,有助於查錯。
使用字面量文法建立出來的字串、數組、字典對象都是不可變的。若想要可變版本的對象,需要複製一份。
NSMutableArray *mutable = [@[@1,@2,@3] mutableCopy];
這麼做會多調用一個方法,而且還需要再建立一個對象,但使用字面量文法利大於弊。
第4條:多用類型常量,少用#define前置處理器指令
編寫代碼時經常要定義常量,例如,要寫一個UI視圖類,此視圖顯示出來之後就播放動畫,然後消失。如果想將播放動畫的時間提取為常量,通常會這麼寫:
#define ANIMATION_DURATION 0.3
但是這樣定義出來的常量沒有類型資訊,且預先處理過程會把碰到的ANIMATION_DURATION一律替換成0.3,假設此指令聲明在某個標頭檔中,那麼所有引入這個標頭檔的代碼都會進行替換。更好的方式是定義一個類型為NSTimeInterval的常量
// EOCAnimatedView.h#import <UIKit/UIKit.h>@interface EOCAnimatedView : UIView@end// EOCAnimatedView.m#import "EOCAnimatedView.h"// 聲明定義常量static const NSTimeInterval EOCAnimationDuration = 0.3;@implementation EOCAnimatedView@end
變數一定要同時用static於const來聲明。const修飾符可以保護變數不被修改。static修飾符則意味著僅在定義此變數的編譯單元中可見。如果不加static,在另一個編譯單元也聲明了同名變數就會報錯。這樣建立的常量是不公開的。
如果需要對外公開某個常量,就需要常量放在全域符號表中,以便可以在定義該常量的編譯單元之外使用:
// EOCAnimatedView.h#import <UIKit/UIKit.h>// 聲明常量extern const NSTimeInterval EOCAnimationDuration;@interface EOCAnimatedView : UIView@end// EOCAnimatedView.m#import "EOCAnimatedView.h"// 定義常量const NSTimeInterval EOCAnimationDuration = 0.3;@implementation EOCAnimatedView@end
此類常量必須要定義,而且只能定義一次。因為要放到全域符號表裡,所以命名常量時需謹慎,避免名稱衝突。
第5條:用枚舉表示狀態、選項、狀態代碼
枚舉是一種常量命名方式。某個對象所經曆的各種狀態就可以定義為一個簡單的枚舉集:
// 通訊端串連狀態enum EOCConnectionState{ EOCConnectionStateDisconnected, EOCConnectionStateConnecting, EOCConnectionStateConnected,};
編譯器會為每個枚舉值分配一個專屬的編號,從0開始,每個枚舉遞增1。實現枚舉所用的資料類型取決於編譯器,不過其二進位位(bit)的個數必須能完全表示下枚舉編號才行。
C++11標準擴充了枚舉的特性,Objective-C也能得益於C++11標準。其中一項改動是:可以指明用何種底層資料類型來儲存枚舉類型的變數。這樣做的好處是,可以向前聲明枚舉變數了。
// 指定底層資料類型enum EOCConnectionState : NSInteger {/* . . . */};// 向前聲明枚舉變數enum EOCConnectionState : NSInteger;// 手動指定枚舉成員的值,接下來的枚舉值都會在上一個的基礎上自動遞增1enum EOCConnectionState{ EOCConnectionStateDisconnected = 1, EOCConnectionStateConnecting, EOCConnectionStateConnected,};
還有一種情況應該使用枚舉類型,那就是定義選項的時候。若這些選項可以彼此組合,則更應如此:
// 裝置支援方向enum EOCPermittedDirection{ EOCPermittedDirectionUp = 1 << 0, // 0001 上 EOCPermittedDirectionDown = 1 << 1, // 0010 下 EOCPermittedDirectionLeft = 1 << 2, // 0100 左 EOCPermittedDirectionRight = 1 << 3, // 1000 右};// direction枚舉值為0101,表示支援上和左兩個方向enum EOCPermittedDirection direction = EOCPermittedDirectionUp|EOCPermittedDirectionLeft;
使用宏建立枚舉類型(NS_ENUM與NS_OPTIONS都是Foundation架構中定義的輔助宏)
// 普通枚舉類型typedef NS_ENUM(NSUInteger, EOCConnectionState){ EOCConnectionStateDisconnected = 1, EOCConnectionStateConnecting, EOCConnectionStateConnected,};// 選項枚舉類型typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){ EOCPermittedDirectionUp = 1 << 0, EOCPermittedDirectionDown = 1 << 1, EOCPermittedDirectionLeft = 1 << 2, EOCPermittedDirectionRight = 1 << 3,};
在switch語句中使用枚舉:
typedef NS_ENUM(NSUInteger, EOCConnectionState){ EOCConnectionStateDisconnected, EOCConnectionStateConnecting, EOCConnectionStateConnected,};EOCConnectionState state = EOCConnectionStateConnected;switch (state) { case EOCConnectionStateDisconnected: // Handle disconnected state break; case EOCConnectionStateConnecting: // Handle connecting state break; case EOCConnectionStateConnected: // Handle connected state break;}
在switch語句中使用枚舉時,最好不要有default分支,如果枚舉中加入了一個新狀態,編譯器會發出警告資訊提示有狀態未在switch語句中處理,假如寫上了default分支,那麼就會導致編譯器不會發出警告資訊。通常要確保switch語句能正確處理所有枚舉值。
[編寫高品質iOS代碼的52個有效方法](一)Objective-C基礎