編寫高質量iOS有效方法總結(一)

Inlight發表於2017-12-25

1.Objective-C 起源

  • Objective-C 是 C 的 “超集”,所以 C 語言中的所有功能在編寫 Objective-C 程式碼時依然適用。
  • Objective-C 使用“訊息結構”而非“函式呼叫”。
//Messaging (Objective-C)
//訊息結構其執行時執行的的程式碼由執行環境來決定
Object *obj = [Object new];
[ob performWith:parameter1 and: parameter2];


//Function calling (C++)
//函式呼叫則由編譯器來決定
Object *obj = new Object;
obj->perform(parameter1, parameter2)
複製程式碼
  • 在執行時編譯器不關心接收訊息的物件是何種型別,接收訊息的物件問題也要在執行時處理,這個過程叫做“動態繫結”。
  • 所有 Objective-C 語言的物件都必須用 NSString * 這種型別宣告,實際是一個指向 NSString 的指標。物件所佔記憶體總是分配在堆空間(heap space)而不會在棧(stack)上。 不能在棧上分配 Objective-C 物件:
 NSString stackString; 
//error: interface type cannot be statically allocated
複製程式碼
  • 分配在堆中的記憶體必須直接管理,而棧上的記憶體則會在其棧幀彈出時自動清理。有時遇到定義裡面不含有 * 的變數,可能會被分配到棧記憶體:
CGRect frame;
frame.origin,x = 10.0f;
複製程式碼
  • 建立物件要比建立結構體有額外的開銷(分配及釋放堆記憶體),如果只需要儲存非物件型別(int,float,double,char 等)使用結構體更高效。

2.在類的標頭檔案中儘量少引入其他標頭檔案

  • 向前宣告(forward declaring)該類:在編譯一個使用了 SomeClass 類的檔案時,不需要知道 SomeClass 的全部細節,只需知道一個類名就好。 @class SomeClass 在 .m 實現檔案則需要引入 SomeClass 類的標頭檔案,因為要使用後者,則必須知道其所有介面的細節。
  • 將引入標頭檔案的時機儘量延後,只有這確有需要時才引入,因為只要引入了 .h 檔案就會一併引入這個類的所有內容,引入了許多用不到的內容會增加編譯時間。
  • “迴圈引用”:在各自的標頭檔案中引入對方的標頭檔案。 使用 #import 代替 #include 雖然不會導致死迴圈,但這卻意味著兩個類裡有一個無法被正確編譯。使用向前宣告可以解決此問題。
  • 如果你寫的類繼承自某個超類,則必須引入定義那個超類的標頭檔案。同理,如果你寫的類尊循某個協議,那麼該協議必須有完整的定義,不能使用向前宣告,向前宣告只能告訴編譯器有某個協議,卻不知道協議中的方法。儘量把該類遵循的協議宣告到“分類”中。如果不行,就把該協議單獨放在一個標頭檔案中,然後將其引入。

3.多用字面量語法,少用與之等價的方法

  • 使用字面量語法可以縮減原始碼長度,更易讀,更整潔。
    NSArray *arrayA = [NSArray arrayWithObjects:@"dog", @"cat", @"badger", nil];
    NSString *cat = [arrayA objectAtIndex:1];
    
    NSArray *arrayB = @[@"dog", @"cat", @"badger"];
    NSString *cat = arrayA[1];
    
    NSDictionary *dicA = [NSDictionary dictionaryWithObjectsAndKeys:@"Matt", @"firstName", @"Galloway", @"lastname", [NSNumber numberWithInt:28], @"age", nil];
    NSDictionary *dicB = @{@"firstName":@"Matt", @"lastname":@"Galloway", @"age":[NSNumber numberWithInt:28]};
複製程式碼

若 "cat" 變成 nil ,arrayA雖然能建立出來,但卻只有 "dog" 一個物件。因為 arrayWithObjects 會依次處理各個引數,直到發現 nil 為止。同理 dictionaryWithObjectsAndKeys 方法也是一樣。

  • 字典中的物件和鍵必須都是 Objective-C 物件,不能把整數直接放進去,要封裝成NSNumber 例項才行。
  • 使用字面量語法建立出來的字串,陣列,字典物件都是不可變的,若要可變版本則需要複製一份:
NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy]; 
複製程式碼

4.多用型別常量,少使用 #define 預處理指令

  • 很多時候定義一個動畫執行時間會有這種寫法: #define ANIMATION_DURATION 0.3 假設這個指令宣告在某個標頭檔案中,那麼所有引入了這個標頭檔案的程式碼,其 ANIMATION_DURATION 都會被替換。也就是無意間可能替換了我們並不想替換的 ANIMATION_DURATION 。所以我們應該用編譯器的特性來定義一個常量:
static const NSTimeInterval kAnimationDuration = 0.3;
複製程式碼
  1. 常用命名法:若常量侷限於某個 .m 檔案之內,則在前面加字母 k ;若常量在類之外可見,則通常以類名為字首。
  2. 這種命名法清楚地描述了常量的含義(包含型別資訊)。
  3. 一定要同時使用 static 和 const 來宣告:試圖修改 const 宣告的變數編譯器會報錯;static 修飾符意味著該變數僅在 .m 檔案中可見。
  4. 如果不加 static ,編譯器會建立一個外部符號,此時若其他 .m 中也宣告瞭同名變數,編譯器就會報錯。
  • 對外公開的常量(比如通知中用到的 NSNotificationName )。其實註冊者無須知道實際字串的值,只需以常值比變數來註冊自己想要接收的通知即可。此類常量應該放在全域性符號表,以便可以定義該常量在 .m 檔案之外。
//In the header file
extern NSString *const SomeClassNSNotificationName;

//In the implementation file
NSString *const SomeClassNSNotificationName = @"VALUE";
複製程式碼

這是一個由常量指標修飾的 NSString 物件,不能夠被修改。extern 關鍵字告訴編譯器,在全域性符號表中會有一個叫 SomeClassNSNotificationName 的符號,編譯器無需檢視其定義就允許程式碼使用,因為這個常量連結成二進位制檔案之後肯定能找到。此類常量只能定義一次,編譯器會在資料段(全域性區中的data區)為字串分配儲存空間。

相關文章