【Objective-C】5 特有语法


目录
  • 第五节 特有语法
    • 01 类的本质
      • 1.1 继承的本质
      • 1.2 结构体 & 类
      • 1.3 类的本质:类对象
    • 02 SEL
      • 2.1 方法的存储
      • 2.2 方法调用的本质:SEL 消息
    • 03 点语法
    • 04 @property
    • 05 @synthesize
    • 06 @property 增强
    • 07 动态类型 & 静态类型
      • 7.1 概念
      • 7.2 编译检查
      • 7.3 运行检查
    • 08 id类型
      • 8.1 NSObject 指针
      • 8.2 id 指针
      • 8.3 instancetype
    • 09 动态类型检测
    • 10 构造方法
      • 10.1 new 方法的本质
      • 10.2 构造方法(init)
      • 10.3 自定义构造方法

第五节 特有语法


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