關(guān)于Objective-C
Objective-C 是一種通用、高級、面向?qū)ο蟮木幊陶Z言。它擴(kuò)展了標(biāo)準(zhǔn)的 ANSI C 編程語言,將 Smalltalk 式的消息傳遞機(jī)制加入到 ANSI C 中。目前主要支持的編譯器有 GCC 和 Clang。
歷史
Objective-C 主要由 Stepstone 公司的 Brad Cox 和 Tom Love 在 19 世紀(jì) 80 年代發(fā)明。
1981年 Brad Cox 和 Tom Love 還在 ITT 公司技術(shù)中心任職時,接觸到了 SmallTalk語言。Cox 當(dāng)時對軟件設(shè)計(jì)和開發(fā)問題非常感興趣,他很快地意識到 SmallTalk 語言在系統(tǒng)工程構(gòu)建中具有無法估量的價值。
1983 年,Cox 與 Love 合伙成立了 Productivity Products International(PPI)公司,將 Objective-C 及其相關(guān)庫商品化販?zhǔn)郏⒃谥髮⒐靖拿麨镾tepStone。
1988年,斯蒂夫·喬布斯(Steve Jobs)離開蘋果公司后成立了 NeXT Computer 公司,NeXT 公司買下 Objective-C 語言的授權(quán),并擴(kuò)展了 GCC 使之支持 Objective-C 的編譯,基于 Objective-C 開發(fā)了 AppKit 與 Foundation Kit 等庫,作為 NeXTSTEP 的的用戶界面與開發(fā)環(huán)境的基礎(chǔ)。
1996年12月20日,蘋果公司宣布收購 NeXT Software 公司,NEXTSTEP/OPENSTEP 環(huán)境成為蘋果操作系統(tǒng)下一個主要發(fā)行版本 OS X 的基礎(chǔ)。
C語言的嚴(yán)格超集
- 任何C語言程序不經(jīng)修改就可以直接通過Objective-C編譯器成功編譯
- Objective-C 源程序中可以直接使用任何C語言代碼
正是由于以上優(yōu)勢,在 Swift 推出之后,許多和 C 有直接交互的部分大多仍舊使用 Objective-C 編寫。
SmallTalk 式的消息傳遞模型
Objective-C 最大的特色是承自 Smalltalk 的消息傳遞模型(message passing),此機(jī)制與 C Family 式的主流風(fēng)格差異甚大。
如在 java 中,方法調(diào)用:
obj.method(argument);
Objective-C 里的方法調(diào)用:
[obj method:argument];
Objective-C 與其說調(diào)用對象的方法,不如說向?qū)ο髠鬟f消息更為精確。
二者并不僅僅是語法上的差異,還有基本行為上的不同。
舉個??:
[car fly];
Java 的解讀是 “調(diào)用 car 類的 fly 方法”。
若 car 類里沒有定義 fly 方法,那編譯不會通過。
Objective-C 里,則解讀為 “向 car 對象發(fā)送 fly 消息”。
若 car 類內(nèi)定義有 fly 方法就運(yùn)行方法內(nèi)的代碼,若 car 內(nèi)不存在 fly 方法,則程序依舊可以通過編譯,運(yùn)行期則拋出異常: unrecognized selector sent to instance 0x8871710
Objective-C 因?yàn)檫\(yùn)行期才處理消息,允許發(fā)送未知消息給對象。同時空對象 nil 接受消息后默認(rèn)為不做事,所以送消息給 nil 也不用擔(dān)心程序崩潰。
布爾(BOOL)
Java 的布爾數(shù)據(jù)類型 boolean 具有 true 和 false 兩個值,Objective-C 的 BOOL 具有 YES 和 NO 兩個值。
Objective-C 中的 BOOL 實(shí)際上一種對帶符號的字符類型(signed char)的類型定義。通過 #define 把 YES 定義為1,NO 定義為0。
字符串(NSString)
Objective-C 字符串由雙引號包裹,并在引號前加一個@符號,如:
NSString *name = @"Tony";
import 語句
在C語言中,使用 #include 導(dǎo)入入頭文件。在Objective-C中,類似的指令 #import 保證一個文件只會被包含一次,類似于一般頭文件中的:
#ifndef XXX
#define XXX ...
#endif
類的定義與實(shí)現(xiàn)
Objective-C 中將類的定義(interface)與實(shí)現(xiàn)(implementation)分為兩個部分。
類的定義文件遵循 C 語言慣例以 .h 為后綴,實(shí)現(xiàn)文件以 .m 為后綴。
定義部分,定義類的名稱、數(shù)據(jù)成員和方法。 以關(guān)鍵字 @interface 開始,@end 結(jié)束:
@interface MyClass : NSObject {
int memberVar1; // 實(shí)體變量
id memberVar2;
}
+ (return_type)class_method; // 類方法
- (return_type)instance_method1; // 實(shí)例方法
- (return_type)instance_method2:(int)p1;
- (return_type)instance_method3:(int)p1 andPar:(int)p2;
@end
實(shí)現(xiàn)部分,以關(guān)鍵字 @implementation 開始,@end 結(jié)束:
@implementation MyClass {
int memberVar3; // 私有實(shí)體變量
}
- (return_type)instance_method1 {
....
}
- (return_type)instance_method2:(int)p1 {
....
}
- (return_type)instance_method3:(int)p1 andPar:(int)p2 {
....
}
@end
上述代碼 Java 版對照:
public class MyClass {
protected int memberVar1;
protected pointer memberVar2;
private int memberVar3;
public (return_type) instance_method1() {
....
}
public (return_type) instance_method2(int p1) {
....
}
public (return_type) instance_method3andPar(int p1, int p2) {
....
}
}
方法前面的 +/- 代表函數(shù)的類型:加號(+)代表類方法(class method),不需要實(shí)例就可以調(diào)用,與 Java 的靜態(tài)方法相似。減號(-)即是一般的實(shí)例方法(instance method)。
創(chuàng)建對象
Objective-C 創(chuàng)建對象需通過 alloc 和 init 兩個消息。alloc 是分配內(nèi)存,init 則是初始化對象。 init 與 alloc 都是定義在 NSObject 里的方法,父對象收到這兩個信息并做出正確回應(yīng)后,新對象才創(chuàng)建完畢。
舉個??:
MyObject *obj = [[MyObject alloc] init];
MyObject *obj = [MyObject new]; // 也可以這么寫,new 相當(dāng)于 alloc + init
Java 版本:
MyObject obj = new MyObject();
協(xié)議(Protocol)
協(xié)議類似與 Java 語言中的接口。
協(xié)議的定義以 @protocol 開頭,@end 結(jié)尾:
@protocol SayHello
- (void)sayHello;
@end
協(xié)議中定義的方法分為必須實(shí)現(xiàn)的方法和可選實(shí)現(xiàn)的方法。協(xié)議中的方法默認(rèn)必須實(shí)現(xiàn),可選實(shí)現(xiàn)的方法以 @optional 為標(biāo)識。
@protocol SomeProtocol
- (void)method1; // 必須實(shí)現(xiàn)
@optional
- (void)method2; // 可選實(shí)現(xiàn)
- (void)method3; // 可選實(shí)現(xiàn)
@end
實(shí)現(xiàn)一個協(xié)議:
@interface SomeClass : SomeSuperClass <SomeProtocol>
@end
@implementation SomeClass
- (void)method1 {
// ...
}
- (void)method2 {
// ...
}
- (void)method3 {
// ...
}
@end
分類(Category)
分類可以給一個已經(jīng)存在的類增加方法,而不用去改它的源碼。類似于 Swift 和 Kotlin 中的擴(kuò)展(extension)。
比如,NSString 是 Objective-C 內(nèi)置的系統(tǒng)類,我們創(chuàng)建一個它的分類以支持加法運(yùn)算:
// interface 部分
@interface NSString (Calculation)
- (NSString *)stringByAdding:(NSString *)aString; // 加
@end
// implementation 部分
@implementation NSObject (Calculation)
- (NSString *)stringByAdding:(NSString *)aString {
// ...
}
@end
使用的時候,只要包含NSObject+Calculation.h,就可以使用了:
NSString *str = @"100";
NSString *result = [str stringByAdding:10]; // result is 110
引用計(jì)數(shù)
Objective-C 使用引用計(jì)數(shù)來管理對象的生命周期。
如果想使某個對象繼續(xù)存活,那就遞增其引用計(jì)數(shù)(reatain);用完了之后,就遞減其計(jì)數(shù)(release)。當(dāng)計(jì)數(shù)為0時,系統(tǒng)就會將它銷毀。
| 對象操作 | Objective-C方法 | 操作結(jié)果 |
|---|---|---|
| 生成并持有對象 | alloc, new, copy, mutableCopy 等方法 | 生成對象并設(shè)置引用計(jì)數(shù) =1 |
| 持有對象 | reatain 方法 | 引用計(jì)數(shù) +1 |
| 釋放對象 | release 方法 | 引用計(jì)數(shù) -1 |
| 廢棄對象 | dealloc 方法 | 系統(tǒng)自動調(diào)用 引用計(jì)數(shù) =0 時調(diào)用 |

iOS5 開始引入了自動引用計(jì)數(shù) (ARC, Automatic Reference Counting)
簡單地說,ARC 在編譯時為代碼在合適的位置加上 retain 和 release。
屬性
屬性(property)是 Objective-C 的一項(xiàng)特性,用于封裝對象中的數(shù)據(jù)。實(shí)例變量一般通過“存取方法”(access method)來訪問。其中,“獲取方法”(getter)用于讀取變量值,而“設(shè)置方法”(setter)用于寫入變量值。
Xcode 4.4時代之前,需要一個屬性的時候:
// .h
@property NSObejct *foo;
// .m
@synthesize foo = _foo;
頭文件中加上@property,那么編譯器會自動添加下面一段代碼:
- (NSObject *)foo;
- (void)setFoo:(NSObject *)newFoo;
在 .m 中實(shí)現(xiàn):
- (NSObject *)foo {
return _foo;
}
- (void)setFoo:(NSObject *)newFoo {
[foo retain];
[_foo release];
_foo = foo;
}
自動引用計(jì)數(shù)時代,喜大普奔,一切都變得簡單了,只需要一句話:
@property (nonatomic, strong) NSObejct *foo;
@property(*) 括號中的屬性內(nèi)容:
- 原子性:atomic 和 nonatomic,默認(rèn)為atomic。
- 讀寫權(quán)限:readwrite 和 readonly, 默認(rèn)為 readwrite,同時生成setter和getter,所以本質(zhì)上是由@synthesize來實(shí)現(xiàn)的。而 readonly 就是代表只生成 getter 方法。
- 指定方法名:使用getter=<name>或setter=<name>來指定方法名。
- 內(nèi)存管理:assign,strong,weak,copy。
當(dāng) B 是 A 的 strong 屬性,A 又是 B 的 strong 屬性,此時就會造成循環(huán)引用,如圖:

此時,A 若想被釋放,需要引用計(jì)數(shù)為0。而 B 持有 A,所以想要 A dealloc,需要 B 發(fā)送 release 消息到 A。而 B 只有 dealloc 時,才會發(fā)送 release 到 A,并且 B dealloc 也需要 A 向其發(fā)送 release 消息。這樣 A 和 B 互相等待對方的 release 消息,造成循環(huán)引用,導(dǎo)致內(nèi)存無法釋放。
解決循環(huán)引用:將其中一方的屬性使用 weak 修飾。
點(diǎn)語法
屬性合成的方法可以通過點(diǎn)語法調(diào)用:
obj.foo = newFoo;
// 上述代碼等同于
[obj setFoo:newFoo];
點(diǎn)語法的本質(zhì)還是方法調(diào)用,是一種編譯器行為,編譯器會自動進(jìn)行轉(zhuǎn)換,來判斷調(diào)用 set 方法還是 get 方法。
| 操作 | 點(diǎn)語法 | 使用消息表達(dá)式 |
|---|---|---|
| setter | obj.name = val; | [obj setName:val]; |
| getter | val = obj.name; | val = [obj name]; |
代碼塊 (block)
block 對象是對函數(shù)的擴(kuò)展。除了函數(shù)中的代碼,block 還包含變量綁定。block 也被稱為閉包(closure)。block 是以 “^” 開頭為標(biāo)識的。后面跟的一個括號標(biāo)示 block 需要的參數(shù)列表。
一個簡單的??:
void (^myBlock) (int) = ^(int input) {
NSLog(@"input number is %d", input);
};
// 調(diào)用 block
myBlock(1);
// 輸出:input number is 1
block 使用局部變量:
NSString *name = @"Candy";
void (^myBlock) (void) = ^ {
NSLog(@"name is %@", name);
};
myBlock();
// 輸出:name is Candy
block 可以方便的使用局部變量 name,但是你不能修改它,當(dāng)你嘗試修改 name 時,會報錯 Variable is not assignable (missing __block type specifier)。
此時你需要用 __block 修飾 name:
__block NSString *name = @"Candy";
void (^myBlock) (void) = ^ {
name = @"Jack"
NSLog(@"name is %@", name);
};
myBlock();
// 輸出:name is Jack
block 使用實(shí)例變量:
// 定義的實(shí)例變量 userName
@property (nonatomic, copy) NSString *name;
// block 使用實(shí)例變量
void (^myBlock) (void) = ^ {
NSLog(@"ins is %@", self.name);
};
當(dāng) block 本身為實(shí)例變量,而 block 內(nèi)部又使用了 實(shí)例變量,此時就會出現(xiàn)循環(huán)引用。
舉個??:
// 定義的實(shí)例變量 userName 和 myBlock
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void (^myBlock)(void);
// block 使用實(shí)例變量
_myBlock = ^ {
NSLog(@"name is %@", self.name);
};
此時會有警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle
簡單來說,是因?yàn)?self 引用了 myBlock,myBlock 又引用了 self。
解決 block 循環(huán)引用:
// 定義的實(shí)例變量 userName 和 myBlock
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, copy) void (^myBlock)(void);
// block 使用實(shí)例變量
__weak typeof(self) weakSelf = self;
self.myBlock = ^ {
NSLog(@"name is %@", weakSelf.userName);
};