Objective-C 基礎(chǔ)

關(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)用



memory_management_2x.png



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)引用,如圖:

QQ20180730-212815@2x.png

此時,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);
 };




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容