1. 程式人生 > 其它 >【Objective-C】5 特有語法

【Objective-C】5 特有語法

目錄

第五節 特有語法


01 類的本質

1.1 繼承的本質

建立一個物件時的記憶體分配:

  1. 子類物件有自己的屬性和所有父類的屬性

  2. 程式碼段中每一個類都有一個 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. 不同點:

    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, 資料段 / 常量區

  • 三個問題:

    1. 類何時儲存到程式碼段?

      第一次訪問類的時候 ---> 類載入

    2. 類何時回收?

      不會回收,直到程式結束才會釋放空間

    3. 類以什麼形式儲存在程式碼段?

      任何儲存在記憶體中的資料都有一個數據型別(儲存的模版:儲存幾個位元組)

      任何在記憶體中申請的空間也有自己的型別

      ---> 在程式碼段中,類以什麼型別儲存?
      (Person 類顯然不是 Person 型別的,只有類的物件才是 Person 型別的)

在程式碼段中儲存類的步驟(類載入):

  1. 先在程式碼段中建立一個 Class 物件。

    Class 是 Foundation 框架中的一個類,用於儲存類

  2. 將類的資訊儲存到這個 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 時將 * 封裝起來了)

  • 類物件使用情景

    1. 使用類物件來呼叫類的類方法
    2. 使用類物件來呼叫 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 方法的儲存

  • 如何將方法儲存在類物件中?

    1. 先建立一個 SEL 物件

    2. 將方法的資訊儲存到這個物件當中

    3. 再將這個 SEL 物件作為類物件的屬性 儲存在程式碼段中

      ---> 在類物件中建立一個 SEL 指標,指向這個SEL 物件,也就是方法
      (類比 - 物件之間的關聯關係:Student 類中包含 Book *_book; 這一屬性 ,也是建立了一個指標)

  • 如何拿到儲存方法的 SEL 物件?

    SEL s = @selector(sayHi); // SEL 型別已在 typedef 中將 * 封裝起來
    NSLog(@"s = %p", s); // 輸出的是 s 中儲存的值,即【sayHi 方法所在的地址】
    

2.2 方法呼叫的本質:SEL 訊息

  • 呼叫方法的本質

    內部原理:[p1 sayHi];

    1. 根據呼叫語句,先拿到儲存對應的方法: sayHi 方法的 SEL 物件,即拿到儲存 sayHi 的 SEL 資料
    2. 將這個 SEL 資料(通常叫做 SEL 訊息)傳送給 p1 物件(向 p1 物件傳送一條 sayHi 訊息)
    3. p1 物件接收到這個 SEL 訊息,明確了即將呼叫的方法
    4. 根據物件的 isa 指標找到儲存類的類物件
    5. 在類物件中搜索當前類中是否存在和傳入的 SEL 訊息相匹配的方法,若有則執行
    6. 若沒有在當前類找到,則根據類物件中的 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 訊息?

    1. 注意!此時方法名為【 sayHiWith: 】,:冒號不可缺!

    2. 如果有引數,可以呼叫其他方法來發送 SEL 訊息

      // 以下為SEL 類的有引數的 performSelector 方法宣告:
      // 帶一個引數:
      - (id)performSelector:(SEL)aSelector withObject:(id)object;
      // 帶兩個引數:
      - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
      
    3. 如果有更多引數,可以將引數封裝在一個物件中

      // 方法的原始版本:
      - (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 方法

---> 使用點語法更便於呼叫,後面直接寫點語法即可

注意:

  1. 在 setter & getter 方法中,慎用點語法(有可能造成無限遞迴)

    - (void)setAge:(int)num{  
       self.age = age; // self.age 等價於 [self setAge:]; 相當於當前物件迴圈呼叫 setAge 方法
     }
    
  2. 如果 setter & getter 方法的命名不符合規範 或 沒有為屬性封裝 setter & getter,點語法失效無法使用


04 @property

到目前為止,每建立一個類,就要手動書寫大量的 setter & getter 方法

---> @property:自動生成屬性的 setter & getter 方法的宣告

@interface Person : NSObject
{  
  int _age;
}
@property int age; // 注意:屬性名要去掉下劃線@end

實現原理:編譯器在編譯時,會根據 @property 生成 setter & getter 方法的宣告

注意:

  1. @property 的名稱和去掉下劃線的屬性名一致,決定了 setter & getter 方法的名稱
  2. @property 的型別和屬性型別一致,對應了 setter 的引數型別和 getter 的返回值型別
  3. 位置:應書寫在 @interface 中的大括號外 --> 方法宣告的位置
  4. @property 只能生成宣告,方法的實現需要自己寫

05 @synthesize

@property 簡化了方法的宣告,如何簡化方法的實現?

---> @synthesize:自動生成 getter & setter 方法的實現

@implementation Person
@synthesize age;
@end

實現原理:

  1. 生成了一個真私有的屬性,屬性的型別和名字(不帶下劃線) 與 @synthesize 對應的 @property 一致
  2. 生成 setter 方法,在 setter 方法內部:將引數直接直接賦給自動生成的私有屬性(並沒有賦給宣告中的屬性)
  3. 生成 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 的值

注意:

  1. 使用@synthesize 生成的別名 & 方法實現是不包含邏輯驗證的(若需要,直接重寫方法即可)
  2. @property 可以批量宣告資料型別相同的屬性
  3. @synthesize 可以批量宣告所有屬性(無論型別是否相同)

06 @property 增強

上述內容為 Xcode 4.4 前的語法(依舊可用)。此後,Xcode 對 @property 做了增強:

當寫下一個 @property 語句後,編譯器會自動:

  1. 生成私有屬性,且會在屬性名稱前自動新增下劃線
  2. 生成 getter & setter 的宣告
  3. 生成 getter & setter 的實現

---> 此後,無需再寫屬性 及 @synthesize

注意:

  1. 注意書寫規範:@property的型別一定要與屬性一致,@property的名稱應為去掉下劃線的屬性名
  2. 可以批量宣告
  3. 方法的實現中不包含邏輯驗證 ---> 可重寫
  4. 如果同時重寫了 setter & getter,@property 不會再建立對應的私有屬性 --> 自己寫屬性
  5. 子類可繼承,但不可以通過 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 的區別
    1. instancetype 只能作為方法的返回值,而 id 指標不受限
    2. instancetype 有型別(當前類),id 指標無型別

09 動態型別檢測

  • 注意:嚴格意義上講,儲存在堆區的物件中只包含屬性,不包含方法。方法只儲存在類中,若要呼叫方法,需要根據物件的私有屬性 isa 指標,找到位於程式碼段中的類,並在類中搜索方法。若找到,則執行;若未找到,則根據類中 isa指標,訪問當前類的父類,再在父類中查詢 。。。直到到達 NSObject

    ---> 因此,當提到【物件中的方法】,實際上是指【物件所屬的類的方法以及當前類從父類繼承的方法】

在第 7 節中,會發現就算通過了編譯檢查,也不一定能執行成功(因為可能存在動態型別,即在指標指向的地址空間中儲存的並不是宣告指標時指定的類 / 型別)
---> 希望能夠先判斷該方法是否存在於當前物件 / 類中,若不存在就放棄執行函式呼叫語句

查詢方法(前兩個方法是最常用的方式)

  1. - (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{...}
    
  2. 判斷指標指向的物件是否是指定的類或其子類的物件

    NSSSSString *str = [NSSSSString new];
    BOOL b = [str isKindOfClass:[NSString class]];
    // 檢查 str 是否對應一個 NSString類或其子類的物件
    // 即檢查 NSSSSString 是否是 NSString 的子類
    
  3. 判斷指標指向的物件是否為指定類的物件

     NSSSSString *str = [NSSSSString new];
     BOOL b = [str isMemberOfClass:[NSString class]];
     // 檢查 str 是否對應一個 NSString 類的物件
    
  4. 判斷當前類是否是另一個類的子類

      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 方法的規範:

  1. 必須先呼叫當前物件的類的父類的 init 方法,然後將該方法的返回值賦給 self
  2. 呼叫 init 方法初始化物件有可能失敗,若失敗則會返回 nil (即物件賦值為空 --> 返回一個空地址 --->無法通過物件名訪問物件屬性,可以使用物件名訪問方法(因為方法在類中,類非空),但方法不會執行)
  3. 判斷父類是否初始化成功 --> 判斷 self 是否為 nil
  4. 若初始化成功(self 的值非空),就繼續初始化當前物件的屬性
  5. 返回 self 的值
// Student 是 Person 類 的子類
@implementation Student
- (instancetype)init{
  self = [super init];
  if(self){
    // 在此處給子類的屬性做初始化賦值
    self.name = @"jack";
  }
  return self;
}
@end

注意:

  1. 為什麼要呼叫父類的 init 方法?

    邏輯上講:先有父後有子

    程式碼上講:因為要給當前物件中的從父類繼承來的屬性做初始化

  2. 為什麼要賦值給 self?

    [super init] :使用super 來呼叫方法時,返回值是子類的型別(super 只是告知系統應跨過當前類,直接去它的父類中尋找該方法)

    呼叫方法後,需要用當前待賦值的物件(alloc 方法建立的新物件)來接收,所以用 self

  3. 分析:如果初始化失敗,返回了 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 值為空,不指向物件。

  4. 簡化寫法:

    - (instancetype)init{
     if(self = [super init]){
    // 判斷語句合法,判斷的是 self 的值是否為 nil(0)
    // 注意:要與 == 區別開
       self.name = @"jack";
     }	
     return self;
    }
    
  5. 當屬性的型別是一個類時,可以在 init 方法中呼叫 [屬性型別的類名 new] 來直接給這個屬性建立一個物件並初始化

  6. 在重寫 init 之後,再呼叫 new 方法時,會使用重寫後的方法做初始化賦值

10.3 自定義構造方法

使用 init 方法做初始化,會使每一個新建的物件的屬性初始值全部相同
怎樣能將自定義的初值賦給新建的物件?

---> 自定義構造方法

書寫規範:

  1. 返回值必須是 instancetype ---> 與原 init 方法保持一致
  2. 命名:名稱必須以 initWith 開頭(嚴格區分大小寫!!否則系統無法識別)
  3. 方法的實現與原 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