自定义内工厂方法及构造方法(instancetype与id类型)
构造方法
构造方法知识:
1. 【掌握】重写init方法
2. 【理解】练习
3. 【掌握】构造方法使用注意
4. 【掌握】instancetype的作用
1.重写init方法
- 想在对象创建完毕后,成员变量马上就有一些默认的值就可以重写init方法
- 重写init方法格式:
- (id)init {
self = [super init];
if (self) {
// Initialize self.
}
return self;
}
+ [super init]的作用:
面向对象的体现,先利用父类的init方法为子类实例的父类部分属性初始化。
+ self 为什么要赋值为[super init]:
简单来说是为了防止父类的初始化方法release掉了self指向的空间并重新alloc了一块空间。还有[super init]可能alloc失败,这时就不再执行if中的语句
- 重写init方法其它格式
- (id)init {
if (self = [super init]) {
// Initialize self.
}
return self;
2.练习
- 要求通过Person类创建出来的人初始化值都是10岁。
@implementation Person
- (instancetype)init
{
if (self = [super init]) {
_age = 10;
}
return self;
}
@end
- 让学生继承人类,要求学生对象初始化之后,年龄是10,学号是1,怎么办?
@implementation Person
- (instancetype)init
{
if (self = [super init]) {
_age = 10;
}
return self;
}
@end
@implementation Student
- (instancetype)init
{
if (self = [super init]) {
_no = 1;
}
return self;
}
@end
}
3.构造方法使用注意
- 子类拥有的成员变量包括自己的成员变量以及从父类继承而来的成员变量,在重写构造方法的时候应该首先对从父类继承而来的成员变量先进行初始化。
+ 原则:先初始化父类的,再初始化子类的。
* 先调用父类的构造方法[super init];
* 再进行子类内部成员变量的初始化。
- 千万不要把`self = [super init]`写成self `==` [super init]
- 重写构造方法的目的:为了让对象方法一创建出来,成员变量就会有一些固定的值。
4.instancetype的作用
- instancetype与id相似,不过instancetype只能作为方法返回值,它会进行类型检查,如果创建出来的对象,赋值了不相干的对象就会有一个警告信息,防止出错。
// init此时返回值是id
NSString *str = [[Person alloc] init];
// Person并没有length方法, 但是id是动态类型, 所以编译时不会报错
NSLog(@"length = %i", str.length);
// init此时返回值是instancetype
// 由于instancetype它会进行类型检查, 所以会报警告
NSString *str = [[Person alloc] init];
NSLog(@"length = %i", str.length);
instancetype *p = [[person alloc] init];
// 错误写法instancetype只能作为返回值
自定义构造方法
有时候仅仅靠重写构造方法(初始化方法),不能满足需求。比如一个班级中不可能所有学生的年龄都一样,这时候我们需要在创建某个学生的时候能够传入这个学生的年龄。这时候就需要来自定义构造函数(初始化函数)
自定义构造方法的规范
(1)一定是对象方法,以减号开头
(2)返回值一般是instancetype类型
(3)方法名必须以initWith开头
自定义工厂方法
什么是工厂方法(快速创建方法)
类工厂方法是一种用于分配、初始化实例并返回一个它自己的实例的类方法。类工厂方法很方便,因为它们允许您只使用一个步骤(而不是两个步骤)就能创建对象. 例如new
自定义类工厂方法的规范
(1)一定是+号开头
(2)返回值一般是instancetype类型
(3)方法名称以类名开头,首字母小写
一般来说,自定义内工厂方法一般有与之对应的构造方法
.h中
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic, assign)int age;
//自定义内工厂方法快速创建对象并设置age的值
+ (instancetype)personWithAge:(int)age;
//自定义构造方法当对象被创建时设置age的值
- (instancetype)initWithAge:(int)age;
@end
.m中
#import "Person.h"
@implementation Person
//自定义构造方法的实现
- (instancetype)initWithAge:(int)age
{
if (self = [super init]) {
_age = age;
}
return self;
}
//自定义内工厂方法的实现
+ (instancetype)personWithAge:(int)age
{
// 调用与之对应的构造方法
return [[Person alloc]initWithAge:age];
}
@end
自定义构造方法的使用注意
(1)自己做自己的事情
(2)父类的属性交给父类的方法来处理,子类的方法处理子类自己独有的属性
自定义构造方法必须以intiWith开头,并且’W’必须大写
id类型
int main(int argc, const char * argv[]) {
id是一个数据类型, 并且是一个动态数据类型
既然是数据类型, 所以就可以用来
1.定义变量
2.作为函数的参数
3.作为函数的返回值
默认情况下所有的数据类型都是静态数据类型
静态数据类型的特点:
在编译时就知道变量的类型,
知道变量中有哪些属性和方法
在编译的时候就可以访问这些属性和方法,
并且如果是通过静态数据类型定义变量, 如果访问了不属于静态数据类型的属性和方法, 那么编译器就会报错
动态数据类型的特点:
在编译的时候编译器并不知道变量的真实类型, 只有在运行的时候才知道它的真实类型
并且如果通过动态数据类型定义变量, 如果访问了不属于动态数据类型的属性和方法, 编译器不会报错
id == NSObject * 万能指针
id和NSObject *的区别:
NSObject *是一个静态数据类型
id 是一个动态数据类型
Person *p = [Person new];
p.age = 30;
[p sleep];
Person *p = [Student new];
p.age = 30;
[p sleep];
// [p eat];
Student *stu = (Student *)p;
[stu eat];
NSObject *obj = [Person new];
[obj test];
NSObject *obj2 = [Student new];
通过静态数据类型定义变量, 不能调用子类特有的方法
通过动态数据类型定义变量, 可以调用子类特有的方法
通过动态数据类型定义的变量, 可以调用私有方法
弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
应用场景: 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
id obj = [Person new];
[obj sleep];
[obj test];
[obj eat];
id obj2 = [Student new];
[obj2 eat];
[obj2 test];
为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个对象的方法之前会进行一次判断, 判断当前对象是否能够调用这个方法
id obj = [Person new];
id obj = [Student new];
if ([obj isKindOfClass:[Student class]]) {
// isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
[obj eat];
}
if ([obj isMemberOfClass:[Student class]]) {
// isMemberOfClass : 判断指定的对象是否是当前指定的类的实例
[obj eat];
}
NSLog(@"------");
return 0;
}