[編寫高品質iOS代碼的52個有效方法](一)Objective-C基礎

來源:互聯網
上載者:User

標籤:

[編寫高品質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基礎

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.