一、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)上,由程序員來控制它的內存分配。而棧空間的臨時基本數據由編譯器控制。
再舉一個典型案例:
NSString *Qi = @"QiShare";
NSString *Share = Qi;
這里有兩個分配在棧空間的NSString指針:Qi和Share指向了堆空間中的同一塊內存地址。
內存結構,圖解如下:
分配在堆中的對象,內存必須由開發者管理。而分配在棧空間上的指針會在其棧幀彈出時自動清理。
OC將堆內存的管理抽象成了一個機制:ARC(Automatic Reference Counting)。在OC中,不需要用malloc及free來分配或釋放對象所占的內存。OC在運行期環境中把這部分工作抽象為一套內存管理架構,我們稱之為“引用計數”。之后,我們會有專門的一篇文章講解ARC機制
二、為了減少編譯時間,.h文件中盡量少引入其他頭文件。
必要時可以考慮在.h文件里"向前聲明"該類
@class QiShareClass;
@interface xxx : xxx
// ...
@end
在.m文件里再引入該類
#import "QiShareClass.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定義的值只是在編譯前作字符串替換操作,并不包含類型信息。并且如果一不小心被重新定義了常量值,編譯器不會產生任何警告??,最終導致常量值不一致。
#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 宏來定義枚舉類型可以指明底層的數據類型。由開發者決定,而不是編譯器決定。
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
};