【Objective-C】5 特有語法
第五節 特有語法
01 類的本質
1.1 繼承的本質
建立一個物件時的記憶體分配:
-
子類物件有自己的屬性和所有父類的屬性
-
程式碼段中每一個類都有一個 isa 指標,指向當前類的父類(最終指向 NSObject)
例:[p sayHi]; // p 是 Person 類的物件
1)先根據 p 指標(棧)找到 p 指向的物件(堆)
2)然後根據物件中的 isa 指標找到其指向的 Person 類(程式碼段)
3)在 Person 類中搜索是否包含 sayHi 這一方法,若有則執行
4)若沒有找到,就根據 Person 類中的 isa指標 找到其指向的父類,繼續搜尋
5)如果直至 NSObject 類中仍沒有 sayHi 方法,則報錯
- 勘誤:4)是錯誤的 !!!
解析:
結合 1.3 的內容,其實 OC 中無論是物件(instance)還是類(class),本質上他們都是物件,只不過 instance 物件可以依照其所宣告的類建立多個,而每種 class 物件只有唯一一個(邏輯上理解就是每個型別的模版只有一個也只需要有一個)。在此前的講解中,我們知道 instance 物件中儲存的是該物件具體的屬性值,而 class 物件中儲存的是這個類的屬性模板以及方法。但實際上,類中的方法是在兩個地方儲存的:物件方法儲存在 class 物件中,而類方法是儲存在 meta-class 物件中(也稱為元類)。
無論是 instance 物件,還是 class 物件 & meta-class 物件,他們都包含 isa 指標。instance 的 isa 指標指向 class,class 的 isa 指標指向 meta-class。而 meta-class 的 isa 指標比較特殊,全部直接指向了基類的 meta-class(包括基類的 meta-class自身)
除此之外,class 物件 & instance 物件中還包含 superclass 指標。class 的 superclass 指標指向其父類的 class,meta-class 的 superclass 指標指向其父類的 meta-class。instance 不具有 superclass 指標,因為一個具體的例項是沒有父類(物件)的概念的。(只有型別有父與子關係,例項之間不存在繼承關係)而關於基類的 superclass指標(基類沒有父類),meta-class 指向 class,class 指向 nil。
因此,當通過一個 instance 物件來呼叫物件方法時(此處僅描述指標流動方向,不包含 SEL相關內容),首先是根據 instance 物件中的 isa 指標找到其對應的模板 class 物件,在 class 物件中檢索指定的方法,若沒有找到,則通過當前 class 物件中的 superclass 指標前往其父類的 class 物件...,直到最終找到該方法為止。
當通過 class 呼叫類方法時,首先通過 class 的 isa 指標找到 meta-class,後續過程類似物件方法呼叫。
1.2 結構體 & 類
-
相同點:都可以將多個數據封裝為一個整體
-
不同點:
1)結構體只能封裝資料,而類還可以封裝行為(方法)
2)結構體變數分配在棧空間(如果是區域性變數),類的物件儲存在堆空間
-
棧的特點:空間相對較小,但儲存在棧中的資料訪問效率較高
-
堆的特點:空間相對大,訪問資料效率相對低
---> 結論:只有當表示的實體僅包含屬性,無行為,且屬性較少的話,才推薦用結構體儲存(效率高)
3)賦值的本質不同
// Student - 結構體,Person - 類 Student s1 = {@"jack", 19, GenderMale}; Person *p1 = [Person new]; Student s2 = s1; // 賦值時,將 s1 的值拷貝給 s2 Person *p2 = p1; // 賦值時,將 p1 地址賦給 p2 --> p1 p2 指向同一個物件
-
1.3 類的本質:類物件
-
記憶體中的五大區域:棧,堆,程式碼段,BSS, 資料段 / 常量區
-
三個問題:
-
類何時儲存到程式碼段?
第一次訪問類的時候 ---> 類載入
-
類何時回收?
不會回收,直到程式結束才會釋放空間
-
類以什麼形式儲存在程式碼段?
任何儲存在記憶體中的資料都有一個數據型別(儲存的模版:儲存幾個位元組)
任何在記憶體中申請的空間也有自己的型別
---> 在程式碼段中,類以什麼型別儲存?
(Person 類顯然不是 Person 型別的,只有類的物件才是 Person 型別的)
-
在程式碼段中儲存類的步驟(類載入):
-
先在程式碼段中建立一個 Class 物件。
Class 是 Foundation 框架中的一個類,用於儲存類
-
將類的資訊儲存到這個 Class 物件中
Class 物件至少有 3 個屬性:
1)類名:儲存當前類的名稱
2)屬性:儲存當前類具有哪些屬性
3)方法:儲存當前類具有哪些方法
儲存類的 Class 物件被稱為類物件,類(儲存類的 Class 物件)中的 isa 指標實際指向的是儲存父類的類物件。
注意:此處關於 isa 指標的說法是錯誤的!詳見 ---> 1.1 節 勘誤解析
-
如何拿到儲存在程式碼段中的類物件
方法一:呼叫類的類方法 class ,就可以得到儲存類的 class 物件的地址
方法二:呼叫物件的物件方法 class,可以得到儲存這個物件所屬的類的class物件的地址
Class c1 = [Person class]; //此處 class 是類方法,用類名呼叫 // 此時 c1 完全等價於 / 就是 Person 類 Person *p = [Person new]; Class c2 = [p class]; // 此處 class 是物件方法,用物件名呼叫 NSLog(@"%p, %p", c1, c2); // c1 儲存的地址 = c2 儲存的地址 // 該地址為程式碼段中儲存 Person 類的 class 物件的地址 // 該地址就是物件中的 isa指標 儲存的值
注意:宣告 Class 類指標變數時,不需要加 * (系統內部已經在 typedef 時將 * 封裝起來了)
-
類物件使用情景
- 使用類物件來呼叫類的類方法
- 使用類物件來呼叫 new 方法
類物件 = Class 物件中儲存的類,二者完全等價 ---> 可以指定一個類物件,用類物件名來替換類名
Class c = [Person class];
[c sayHi]; // 完全等價於 [Person sayHi];
Person *p = [c new]; // 完全等價於 [Person new];
注意:不可以用於呼叫類的物件方法(類名 != 物件名)
02 SEL
SEL := selector 選擇器,是一個數據型別 ---> 用於在記憶體中申請空間儲存資料
本質上,SEL 是一個類,一個SEL 物件用來儲存一個方法(再儲存到 Class 物件中)
注意:這種說法只是用來理解,實際上並不正確
SEL 型別的變數本質型別是 const char *,也就說,儲存的是方法名的字串
2.1 方法的儲存
-
如何將方法儲存在類物件中?
-
先建立一個 SEL 物件
-
將方法的資訊儲存到這個物件當中
-
再將這個 SEL 物件作為類物件的屬性 儲存在程式碼段中
---> 在類物件中建立一個 SEL 指標,指向這個SEL 物件,也就是方法
(類比 - 物件之間的關聯關係:Student 類中包含 Book *_book; 這一屬性 ,也是建立了一個指標)
-
-
如何拿到儲存方法的 SEL 物件?
SEL s = @selector(sayHi); // SEL 型別已在 typedef 中將 * 封裝起來 NSLog(@"s = %p", s); // 輸出的是 s 中儲存的值,即【sayHi 方法所在的地址】
2.2 方法呼叫的本質:SEL 訊息
-
呼叫方法的本質
內部原理:[p1 sayHi];
- 根據呼叫語句,先拿到儲存對應的方法: sayHi 方法的 SEL 物件,即拿到儲存 sayHi 的 SEL 資料
- 將這個 SEL 資料(通常叫做 SEL 訊息)傳送給 p1 物件(向 p1 物件傳送一條 sayHi 訊息)
- p1 物件接收到這個 SEL 訊息,明確了即將呼叫的方法
- 根據物件的 isa 指標找到儲存類的類物件
- 在類物件中搜索當前類中是否存在和傳入的 SEL 訊息相匹配的方法,若有則執行
- 若沒有在當前類找到,則根據類物件中的 isa 指標找到其父類,繼續搜尋,直到到達 NSObject
---> OC 中最重要的機制:訊息機制
呼叫方法其實就是為物件傳送一條 SEL 訊息。(實際在物件方法呼叫時,會轉化為Objc_msgSend(self, SEL, ...)函式,然後通過 SEL 在 class 物件中查詢方法進行呼叫)
方法預設有id,SEL兩種型別的引數;id是訊息的接收者(也就是 class 物件),SEL是該類方法的編號;
方法查詢的本質就是通過物件 & SEL 查詢該方法對應的IMP( IMP 表示方法儲存的地址)
方法查詢的具體過程:
首先通過彙編查詢cache中是否快取了該方法,如果快取了返回對應方法的IMP;如果沒有快取,在方法列表中查詢該方法IMP,如果找到了對該方法進行快取並且返回IMP;
那麼在方法列表中是怎樣查詢的呢?
首先,查詢當前類的方法列表中,是否有該方法的實現;如果沒有向父類中查詢,一直判斷父類是否為nil;如果不為nil一直向上查詢,直至找到對應的IMP;找父類時先呼叫cache_getImp,父類是否快取了該方法,如果快取了直接返回,如果沒有快取查詢對應類的方法列表;如果是類方法,在元類的方法列表中查詢,直至查詢到對應的IMP或者父類為nil;
-
手動為物件傳送 SEL 訊息 ---> 可行且合法
Person *p = [Person new]; // [p sayHi]; 執行的本質(以下兩條語句) SEL s = @selector(sayHi); // 獲取方法的 SEL資料 [p performSelector:s1]; // 效果完全等價於 [p sayHi]; // SEL 類的 performSelector 方法: // - (id)performSelector:(SEL)aSelector;
-
問題:如果方法有引數( sayHiWith:(Person *)p ),如何手動傳送SEL 訊息?
-
注意!此時方法名為【 sayHiWith: 】,:冒號不可缺!
-
如果有引數,可以呼叫其他方法來發送 SEL 訊息
// 以下為SEL 類的有引數的 performSelector 方法宣告: // 帶一個引數: - (id)performSelector:(SEL)aSelector withObject:(id)object; // 帶兩個引數: - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
-
如果有更多引數,可以將引數封裝在一個物件中
// 方法的原始版本: - (void)test1With:(int)num1 And:(int)num2 And:(int)num3; // 封裝後: @interface Paras : NSObject{ int _num1; int _num2; int _num3; } @end @interface Person : NSObject - (void)test2With:(Paras *)paras; // 將三個引數封裝到一個物件中,將一個物件作為方法的唯一的引數傳入方法 @end int main(){ Person *p = [Person new]; Paras *paras = [Paras new]; // 先將三個引數的值存入 paras 指向的物件(過程略) // 然後手動傳送: SEL s = @selector(test2With:); [p performSelector:s withObject:paras]; }
-
03 點語法
OC 中可以使用點語法來訪問物件的屬性(注意:OC中的點語法與 Java C# 不同)
使用點語法訪問物件的屬性:【物件名.去掉下劃線的屬性名】
(注意:這種說法只是方便初學者理解,實際上是用點語法直接呼叫 setter / getter 方法)
Person *p = [Person new];
p.name = @"jack"; // 將 @"jack" 賦值給 p 的 _name 屬性
內部實現原理:
編譯器編譯時,會將點語法轉化為呼叫 setter / getter 的程式碼
p.age = 10; // 賦值時,轉換為:[p setAge:10]; 執行 setAge: 方法
int num = p.age; // 取值時,轉換為:int num = [p age]; 執行 age 方法
---> 使用點語法更便於呼叫,後面直接寫點語法即可
注意:
-
在 setter & getter 方法中,慎用點語法(有可能造成無限遞迴)
- (void)setAge:(int)num{ self.age = age; // self.age 等價於 [self setAge:]; 相當於當前物件迴圈呼叫 setAge 方法 }
-
如果 setter & getter 方法的命名不符合規範 或 沒有為屬性封裝 setter & getter,點語法失效無法使用
04 @property
到目前為止,每建立一個類,就要手動書寫大量的 setter & getter 方法
---> @property:自動生成屬性的 setter & getter 方法的宣告
@interface Person : NSObject
{
int _age;
}
@property int age; // 注意:屬性名要去掉下劃線@end
實現原理:編譯器在編譯時,會根據 @property 生成 setter & getter 方法的宣告
注意:
- @property 的名稱和去掉下劃線的屬性名一致,決定了 setter & getter 方法的名稱
- @property 的型別和屬性型別一致,對應了 setter 的引數型別和 getter 的返回值型別
- 位置:應書寫在 @interface 中的大括號外 --> 方法宣告的位置
- @property 只能生成宣告,方法的實現需要自己寫
05 @synthesize
@property 簡化了方法的宣告,如何簡化方法的實現?
---> @synthesize:自動生成 getter & setter 方法的實現
@implementation Person
@synthesize age;
@end
實現原理:
- 生成了一個真私有的屬性,屬性的型別和名字(不帶下劃線) 與 @synthesize 對應的 @property 一致
- 生成 setter 方法,在 setter 方法內部:將引數直接直接賦給自動生成的私有屬性(並沒有賦給宣告中的屬性)
- 生成 getter 方法,在 getter 方法內部:將自動生成的私有屬性的值返回
// @implementation Person
// @synthesize age;
// @end
//// 等價於:
@implementation Person{
int age;
}
- (void)setAge:(int)age{
self->age = age;
}
- (int)age{
return age;
}
@end
@synthesize 會額外生成與宣告中對應的一套私有屬性,如何不生成?
@synthesize age = _age;
// 可以自動生成 setter & getter,且不會生成私有屬性(可看作是給 _age 這個成員變數添加了一個別名 age)
// setter 會將值直接賦給指定的屬性 _age
// getter 會直接返回指定屬性 _age 的值
注意:
- 使用@synthesize 生成的別名 & 方法實現是不包含邏輯驗證的(若需要,直接重寫方法即可)
- @property 可以批量宣告資料型別相同的屬性
- @synthesize 可以批量宣告所有屬性(無論型別是否相同)
06 @property 增強
上述內容為 Xcode 4.4 前的語法(依舊可用)。此後,Xcode 對 @property 做了增強:
當寫下一個 @property 語句後,編譯器會自動:
- 生成私有屬性,且會在屬性名稱前自動新增下劃線
- 生成 getter & setter 的宣告
- 生成 getter & setter 的實現
---> 此後,無需再寫屬性 及 @synthesize
注意:
- 注意書寫規範:@property的型別一定要與屬性一致,@property的名稱應為去掉下劃線的屬性名
- 可以批量宣告
- 方法的實現中不包含邏輯驗證 ---> 可重寫
- 如果同時重寫了 setter & getter,@property 不會再建立對應的私有屬性 --> 自己寫屬性
- 子類可繼承,但不可以通過 self 訪問(因為生成的是私有屬性),但可以使用 super 訪問
07 動態型別 & 靜態型別
OC 是一門弱語言 ---> 編譯器在編譯階段檢查語法時沒有那麼嚴格(例:int num = 12.12;)
---> 優點:靈活;缺點:太靈活。。
什麼是強語言?-- 編譯器嚴格檢查語法(Java / ...)
7.1 概念
靜態型別:表示一個指標指向的物件是一個當前類的物件
動態型別:表示一個指標指向的物件是非當前類的物件
Book *b = @"jack"; // 合法
NSLog(@"%@", b); // 輸出 jack
7.2 編譯檢查
Xcode 中編譯器名稱:LLVM(可編譯 C / OC / C++)
編譯器在編譯時,會判斷能不能通過指標呼叫指標指向的物件中的方法
判斷原則:指標所屬的型別之中是否包含該方法。有則編譯通過;沒有就報錯,編譯失敗
---> 能不能呼叫方法由指標的型別決定
// Pig 是 Animal 的子類,eat 是 Pig 中的方法
Animal *a = [Pig new];
// 指標 a 是Animal 類的,但指向的物件是 Pig 類的物件
[a eat]; // a --> Animal,Animal 中找不到 eat,編譯失敗
[(Pig *)a eat]; // 將 a 強制轉換為指向 Pig 物件的指標,此時可通過編譯
// 注意:此時 a 仍是一個 Animal 型別指標
7.3 執行檢查
在執行時,會檢查物件中是否真的存在這個方法。有則執行;沒有就報錯
[(Pig *)a eat];
// 該語句可以通過編譯,也可以執行。因為 a 指向的物件是 Pig 類的,類中有 eat 方法
08 id型別
8.1 NSObject 指標
NSObject:是OC 中所有類的基類
---> 根據 LSP(里氏替換原則),NSObject 指標可以指向任意的OC物件,是一個萬能指標
缺點:如果要呼叫 NSObject 指標指向的子類物件中的方法,必須要有型別轉換才能通過編譯
8.2 id 指標
-
id 指標是一個萬能指標,可以指向任意的 OC 物件
-
id 是一個 typedef 自定義型別,在定義該型別時已經封裝了 * ,所以宣告 id 指標時無需新增 *
-
與 NSObject 指標對比
使用 id 指標呼叫物件的方法可直接通過編譯檢查,不會報錯
---> 使用 id指標 更好
-
id 指標不能使用點語法訪問屬性(會報錯),只能用來呼叫方法
8.3 instancetype
父類的類方法可以被子類繼承。舉例:
// Student 為Perosn 的子類,Person 中包含一個類方法 person,該方法返回一個新建立的 Person 物件
// + (Person *)person;
Person *p = [Person new];
Student *s = [Student person];
// 可以用子類呼叫 person,因為子類繼承了父類的全部成員,包括類方法
但此時指標 s 接收的是一個 Person 物件,怎樣能接收到一個當前類的物件呢?
---> 改變父類中該方法的返回值型別和實現
@implementation Person
+ (id)person{
// id 指標可接收任意的 OC 物件,這樣就不必指定傳出的物件型別
return [self new];
// self 表示呼叫這個類方法的當前類 -> 哪個類呼叫方法,就建立哪個類的物件並返回
}@end
--> 在寫方法的實現時,不要寫死。使用關鍵字 (self / super)來規避明確的類名
存在的問題:使用 id 指標會使任意指標都能接收這個方法的返回值(編譯器甚至不會警告)
如何指定接收返回值的指標型別?---> 令返回值型別為 instancetype
instancetype 表示方法的返回值為當前這個類的物件
- id 與 instancetype 的區別
- instancetype 只能作為方法的返回值,而 id 指標不受限
- instancetype 有型別(當前類),id 指標無型別
09 動態型別檢測
-
注意:嚴格意義上講,儲存在堆區的物件中只包含屬性,不包含方法。方法只儲存在類中,若要呼叫方法,需要根據物件的私有屬性 isa 指標,找到位於程式碼段中的類,並在類中搜索方法。若找到,則執行;若未找到,則根據類中 isa指標,訪問當前類的父類,再在父類中查詢 。。。直到到達 NSObject
---> 因此,當提到【物件中的方法】,實際上是指【物件所屬的類的方法以及當前類從父類繼承的方法】
在第 7 節中,會發現就算通過了編譯檢查,也不一定能執行成功(因為可能存在動態型別,即在指標指向的地址空間中儲存的並不是宣告指標時指定的類 / 型別)
---> 希望能夠先判斷該方法是否存在於當前物件 / 類中,若不存在就放棄執行函式呼叫語句
查詢方法(前兩個方法是最常用的方式)
-
- (BOOL)respondsToSelector:(SEL)aSelector; // 判斷當前類 / 指標所指向的物件中是否有這個方法
Person *p = [Person new]; BOOL b1 = [p respondsToSelector:@selector(length)]; // 檢查 p 能否呼叫 length 物件方法 // @selector(length) 是一個 SEL 型別的指標,即一個對應著 length 物件方法 SEL 訊息 // 本質上,該函式檢查的是物件 p 會不會響應這個 SEL 訊息 BOOL b2 = [Person respondsToSelector:@selsector(setName:)]; // 檢查 Person 類能否呼叫 setName: 這個類方法 if(b1 == YES){...} // b1 = 1 else{...}
-
判斷指標指向的物件是否是指定的類或其子類的物件
NSSSSString *str = [NSSSSString new]; BOOL b = [str isKindOfClass:[NSString class]]; // 檢查 str 是否對應一個 NSString類或其子類的物件 // 即檢查 NSSSSString 是否是 NSString 的子類
-
判斷指標指向的物件是否為指定類的物件
NSSSSString *str = [NSSSSString new]; BOOL b = [str isMemberOfClass:[NSString class]]; // 檢查 str 是否對應一個 NSString 類的物件
-
判斷當前類是否是另一個類的子類
BOOL b = [NSSSSString isSubclassOfClass:[NSString class]]; // 檢查 NSSSSString 是否為 NSString 的子類
10 構造方法
在此之前,若需要建立物件,需要用類名呼叫 new 方法,new 方法會完成:
1)在堆區開闢一塊空間,按照類模版建立一個物件;2)初始化該物件;3)將物件的地址返回
本質上,new 方法是通過【先呼叫 alloc 方法,再呼叫 init 方法】來實現上述內容的
10.1 new 方法的本質
// Person *p1 = [Person new]; 該語句等價於:
Person *p = [Person alloc];
Person *p1 = [p init];
// 也等價於:Person *p1 = [[Person alloc] init];
alloc 方法:一個類方法,依照呼叫該方法的類建立一個物件,並將該物件返回
init 方法:一個物件方法,用於初始化物件
注意:雖然使用未初始化的物件是合法的,但這種做法極其危險,不建議使用
10.2 構造方法(init)
呼叫 init 方法,會為物件做初始化賦值,也就是屬性的預設值
(基本資料型別 - 0;C 指標 - NULL; OC 指標 -nil)
如何改變這些預設值?令屬性在初始化時的預設值不是 0 / NULL / nil?
---> 重寫 init 方法,在方法中實現新的初始化
重寫 init 方法的規範:
- 必須先呼叫當前物件的類的父類的 init 方法,然後將該方法的返回值賦給 self
- 呼叫 init 方法初始化物件有可能失敗,若失敗則會返回 nil (即物件賦值為空 --> 返回一個空地址 --->無法通過物件名訪問物件屬性,可以使用物件名訪問方法(因為方法在類中,類非空),但方法不會執行)
- 判斷父類是否初始化成功 --> 判斷 self 是否為 nil
- 若初始化成功(self 的值非空),就繼續初始化當前物件的屬性
- 返回 self 的值
// Student 是 Person 類 的子類
@implementation Student
- (instancetype)init{
self = [super init];
if(self){
// 在此處給子類的屬性做初始化賦值
self.name = @"jack";
}
return self;
}
@end
注意:
-
為什麼要呼叫父類的 init 方法?
邏輯上講:先有父後有子
程式碼上講:因為要給當前物件中的從父類繼承來的屬性做初始化
-
為什麼要賦值給 self?
[super init] :使用super 來呼叫方法時,返回值是子類的型別(super 只是告知系統應跨過當前類,直接去它的父類中尋找該方法)
呼叫方法後,需要用當前待賦值的物件(alloc 方法建立的新物件)來接收,所以用 self
-
分析:如果初始化失敗,返回了 nil ...
首先,如果父類屬性的初始化就失敗了,那麼子類必定也失敗(邏輯上:沒有父就不應該有子)
當 self 指標的值為 nil 時,表示返回的當前物件指標值是空 --> 指標指向空
即 [[Student alloc] init] 方法呼叫返回的值為 nil
此時,Student *s = [[Student alloc] init]; 等號左側等待接收物件地址的 Student 型別指標 s 被賦值為 nil,即s 指標指向空地址
那麼,就無法通過 s 指標訪問物件的屬性,因為 s 指標根本就不指向一個物件,空地址也不可能有屬性
但是,可以通過 s 指標(也就是【物件名】)訪問物件方法,因為 s 指標已宣告是 Student 型別的,可以找到儲存在類中的方法,所以可以通過編譯。但方法不會執行,因為根據 2.2節中所述,在給 s 指標傳送與方法對應的 SEL訊息時,s 指標不會有響應,因為 s 值為空,不指向物件。
-
簡化寫法:
- (instancetype)init{ if(self = [super init]){ // 判斷語句合法,判斷的是 self 的值是否為 nil(0) // 注意:要與 == 區別開 self.name = @"jack"; } return self; }
-
當屬性的型別是一個類時,可以在 init 方法中呼叫 [屬性型別的類名 new] 來直接給這個屬性建立一個物件並初始化
-
在重寫 init 之後,再呼叫 new 方法時,會使用重寫後的方法做初始化賦值
10.3 自定義構造方法
使用 init 方法做初始化,會使每一個新建的物件的屬性初始值全部相同
怎樣能將自定義的初值賦給新建的物件?
---> 自定義構造方法
書寫規範:
- 返回值必須是 instancetype ---> 與原 init 方法保持一致
- 命名:名稱必須以 initWith 開頭(嚴格區分大小寫!!否則系統無法識別)
- 方法的實現與原 init 方法的要求一致
@implemnetation Student
- (instancetype)initWithName:(NSString *)name andAge:(int)age{
if(self = [super init]){
// 如果不遵守命名規範,系統無法由 init 轉到自定義的構造方法
// 但 init 不等價於 initWith,在其他位置呼叫方法時都應該寫 initWith
// 所以,在給新建立的物件做初始化時,如果要使用 initWith 來初始化,就不要使用 new 方法
self.name = name;
self.age = age;
}
return self;
}
@end
int main(){
Student *s = [[Student alloc] initWithName:@"jack" andAge:18];
}
---> 此後應使用 [[類名 alloc] initWith方法名] 來建立物件並初始化,而不再使用 new