iOS 編寫高質量Objective-C程式碼(一)—— 簡介

奇舞647發表於2019-12-29

《編寫高質量OC程式碼》已順利完成一二三四五六七八篇!
附上鍊接:
iOS 編寫高質量Objective-C程式碼(一)—— 簡介
iOS 編寫高質量Objective-C程式碼(二)—— 物件導向
iOS 編寫高質量Objective-C程式碼(三)—— 介面和API設計
iOS 編寫高質量Objective-C程式碼(四)—— 協議與分類
iOS 編寫高質量Objective-C程式碼(五)—— 記憶體管理機制
iOS 編寫高質量Objective-C程式碼(六)—— block專欄
iOS 編寫高質量Objective-C程式碼(七)—— GCD專欄
iOS 編寫高質量Objective-C程式碼(八)—— 系統框架


目前iOS開發主推的官方語言有兩種:Objective-CSwift。 今天,小編幫助大家更加熟悉Objective-C,並且聊一聊如何才能編寫高質量的OC程式碼。

一、Objective-C的起源

談到Objective-C語言的起源,可要比Java還要早十多年。 ~Java在1995年推出,而Objective-C早在1980年代就已經出現了。~

Objective-C (OC) 由Smalltalk語言演化而來,後者是訊息傳遞型語言的鼻祖。

  • 訊息傳遞?是的!引入了今天的第一個Key。 OC與C++、Java等面嚮物件語言類似,但又有很大區別。為什麼這麼說呢?首先要引入的話題就是OC使用訊息傳遞機制,而並非C++、Java使用函式呼叫機制。
// Objective-C : messaging (訊息傳遞)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

// C++: function calling(函式呼叫)
Object *obj = new Object;
obj->perform(parameter1, parameter2);
複製程式碼

區別: 訊息傳遞:執行時所執行的程式碼由執行時環境決定(runtime) 函式呼叫:執行時所執行的程式碼由編譯器決定

簡單來說,OC總在執行時才會去查詢真正所要呼叫的方法,編譯器並不用關心接收訊息的物件是什麼型別,接收訊息的物件也是在執行時才工作,其過程叫做動態繫結(dynamic binding)。而其他大部分面嚮物件語言,會在執行時查詢“虛方法表”(virtual table)來查出執行的方法(是呼叫子類的方法?還是父類的方法?)。


OC是C語言的超集,如果你熟悉C語言,那C語言裡的大部分知識在編寫OC程式碼時依然適用。 那麼,今天的第二個Key:指標

OC裡的指標主要用來指示物件,基本語法和C語言類似。

  • 例如:宣告一個字串
NSString *str1 = @"QiShare";
複製程式碼

語法解釋:宣告瞭一個名為str1的變數,其型別為NSString*。是一個指向NSString的指標。

  • 錯誤案例:
NSString str2;
複製程式碼

報錯:error:interface type cannot be statically allocated 解釋:物件不允許宣告在棧空間上

不能在棧中分配OC物件,因為OC物件所佔的記憶體會被分配在堆空間(heap space)上,由程式設計師來控制它的記憶體分配。而棧中的臨時基本資料由編譯器控制。

  • 再舉一個典型案例:
xxxClass *Qi = [[xxxClass alloc] init];
xxxClass *Share = Qi;
複製程式碼

這裡有兩個分配在棧空間的xxxClass指標:Qi和Share指向了堆空間中的同一塊記憶體地址。

記憶體結構,圖解如下:

iOS 編寫高質量Objective-C程式碼(一)—— 簡介

分配在堆中的物件,記憶體必須由開發者管理。而分配在棧空間上的指標會在其棧幀彈出時自動清理。

OC將堆記憶體的管理抽象成了一個機制:ARC(Automatic Reference Counting)。在OC中,不需要用malloc及free來分配或釋放物件所佔的記憶體。OC在執行期環境中把這部分工作抽象為一套記憶體管理架構,我們稱之為“引用計數”。~之後,我們會有專門的一篇部落格講解ARC機制~

二、為了減少編譯時間,.h檔案中儘量少引入其他標頭檔案。

必要時可以考慮在.h檔案裡"向前宣告"該類。

@class QiShareClass;

@interface xxx : xxx

// ...

@end
複製程式碼

在.m檔案裡再引入該類

#import "QiShareClass.h"

// ....
複製程式碼

同時,向前宣告也解決了兩個類可能存在互相引用的問題。 例如:

  • Qi.h中
#import "Share.h"
複製程式碼
  • Share.h中
#import "Qi.h"
複製程式碼

當解析"Qi.h"時,編譯器發現"Share.h",再導回自己本身"Qi.h"。 從而造成迴圈引用。這樣會導致兩個類中有一個類不能正確編譯。 推薦:如果用到協議,必要時可以把協議封裝在一個單獨的標頭檔案裡。不僅可以減少編譯時間,還能避免迴圈引用的問題。

三、多用字面量語法,少用等價方法

  • 好處:簡明易讀,提高程式碼的可讀性和可維護性
  • 侷限性:用字面量預防建立陣列或字典時,值不能有nil,否則會丟擲異常。

For Example:

// 字面量字串
NSString *str = @"QiShare";

// 字面量數值
NSNumber *num = @(1);
NSNumber *floatNum = @(1.0);
int x = 5;
float y = 3.14;
NSNumber *num = @(x * y);

// 字面量陣列
NSArray *animals = @[@"cat", @"dog", @"tiger", @"monkey"];
NSString *cat = animals[0];

// 字面量字典
NSDictionary *qiShareDic = @{@"englishName": @"QiShare",
                             @"chineseName": @"奇分享"}];
NSString *englishName = qiShareDic[@"englishName"];
NSString *chineseName = qiShareDic[@"chineseName"];
複製程式碼
  • 注意:用字面量語法創造出來的物件預設都是不可變物件,如果需要可變物件,執行一步 mutableCopy
NSMutableString *mutableStr = [@"QiShare" mutableCopy];
複製程式碼

四、多用型別常量,少用#define預處理指令

  • 好處:定義出來的常量包含型別資訊,不可變,可讀性高。
  • 而#define定義的值只是在編譯前作字串替換操作,並不包含型別資訊。並且如果一不小心被重新定義了常量值,編譯器不會產生任何警告⚠️,最終導致常量值不一致。

For Example:

#define ANIMATION_DURATION 0.5

// 替換成
static const NSTimeInterval kAnimationDuration = 0.5;

// 全域性常量
// QiShare.h
extern const NSTimeInterval QiShareAnimationDuration;

// QiShare.m
const NSTimeInterval QiShareAnimationDuration = 0.3;
複製程式碼

五、多用列舉表示狀態、選項、狀態碼

  • 通過列舉表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,增強了程式碼的可讀性。
  • 列舉的值如果存在多選的可能,將選項值定義為2的冪。便於底層轉成二進位制儲存。
  • 用NS_ENUM 與 NS_OPTIONS 巨集來定義列舉型別可以指明底層的資料型別。由開發者決定,而不是編譯器決定。 For Example:
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
複製程式碼

最後,特別緻謝《Effective Objective-C 2.0》第一章

相關文章