运行以下代码查看log输出,为什么NSLog的输出都Animal ?
// Person.h 文件
#import "Animal.h"
@interface Person : Animal
- (void)test;
@end
// Person.m 文件
#import "Person.h"
@implementation Person
- (void)test {
NSLog(@"super---%@", [super class]);
NSLog(@"self---%@", [self class]);
}
@end
//// Log
[19476:618282] super---Person
[19476:618282] self---Person
1. 对象初始化的两种方式
对象初始化有两种方式:[class new] 与 [[class alloc] init]。对于后者,有分配和初始化的过程,alloc 从应用程序的虚拟地址空间上为该对象分配足够的内存,并且将新对象的引用计数加1、将对象的成员变量初始为零,init 会做真正的初使化工作,为对象的实例变量赋予合理有用的值。
查看相关源码:
// new
+ new {
id newObject = (*_alloc)((Class)self, 0);
Class metaClass = self->isa;
if (class_getVersion(metaClass) > 1)
return [newObject init];
else
return newObject;
}
// 而 alloc、init
+ alloc {
return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());
}
- init {
return self;
}
发现[class new]默认调用 alloc与init方法,多了更多的局限性。而[class alloc] init] 会更方便, 当然[class alloc] init] 的设计也是由于历史的原因。
一般不推荐使用[class new],而推荐使用[[class alloc] init]。
2. init方法中的self = [super init];
常见的重写一个类的init方法中,都会有如下写法:
- (instancetype)init {
if (self = [super init]) {
// Custom initialization
}
return self;
}
-
self是什么
self代表着当前方法的调用者。在对象方法中,self代表当前"实例对象";在静态方法中,self则代表"类对象"。 -
super是什么
self 和 super 是oc 提供的两个保留字,不同的是:self是类的隐藏的参数变量,指向调用当前方法的对象;super并不是隐藏的参数,它只是一个编译器指示符。(另一个隐藏参数是_cmd,代表当前类方法的selector) -
[super init]做了什么
在一个类的方法reposition中,编译如下调用方法时,
// Class A
- (void)reposition {
...
[self setOrigin:someX];
...
}
编译器会将调用过程转换为:
objc_msgSend(id self,SEL _cmd, ...); //self ->实例对象
此时 self 指代实例对象,方法从对象对应 类结构的方法列表 中开始寻找,如果找不到,延继承链往 父类中寻找 。同样如果 reposition 是类方法, self 指代 A 类对象。
// Class A
- (void)reposition {
...
[super setOrigin:someX];
...
}
编译器会将调用过程转换为:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
其中,第一个参数是个objc_super的结构体,
struct objc_super {
id receiver;
Class superClass;
};
objc_super中的成员receiver类似objc_msgSend的第一个参数 receiver;成员superClass则记录这个类的父类是什么。
当开始编译代码 [super setOrigin:someX :someY]; 时,则需要做这几个事:
- 构建 objc_super 的结构体,其成员变量 receiver 就是当前实例对象(self) 。而成员变量 superClass 就是指类A的 superClass。
- 调用 objc_msgSendSuper 的方法,将这个结构体和 setOrigin: 的 sel 传递过去。
函数里面在做的事情类似这样:从 objc_super 结构体指向的 superClass 的方法列表开始找 setOrigin 的 selector,找到后再以 objc_super->receiver 去调用这个 selector,可能也会使用 objc_msgSend 这个函数,不过此时的第一个参数 theReceiver 就是 objc_super->receiver,第二个参数是从 objc_super->superClass 中找到的 selector。
这也解释了文章,开头为什么[super class]和[self class]输出一致。
当 发送 class 消息 时不管是 self 还是 super 其消息主体依然是 self ,也就是说 self 和 super 指向的 是同一个对象。只是 查找方法的位置 区别,一个从本类,一个从本类的超类。一般情况下class方法 只有在根类 NSObject 中定义,极少情况有子类重写 class 方法,如果重写可能会不一样。
super只是个编译器符号,它可以替换成 [self class],只不过它方法是从 self 的超类开始查找。
3. 为什么要 if (self = [super init])
这样符合OC继承类初始化规范流程,[super init]递归的去self的super中调用init,直到调用根类 NSObject 中的init。即,根据各级super的init来初始化内存区域的属性,再逐级返回内存指针,直到A类中得到内存指针,赋值给self 参数。若中途初始化参数失败,则if中self为nil,可免接下来无用的初始化。
有些特殊情况是不能保证所 alloc 和 init 出来的内存是同一块内存;也有可能在父类方法初始化过程中对象空间被释放为nil,加上这样的判断是为了提高容错性,如果init成功就返回对象,否则返回nil。
4. 总结
- 面向对象过程中子类继承父类,就拥有了父类所有的属性和方法,一个完整的对象的初始化包括子类和父类初始化。
- 子类 [alloc init] 后,首先这里只有一个实例对象实体self,没有所谓的父类对象实体super。初始化过程中,子类、父类属性和方法初始化都属于子类对象的一部分,super的指针赋给self这一说法是错的,其实全部指的是该对象的初始位置。
参考文章:
https://www.cnblogs.com/damontang/p/4034890.html
https://blog.csdn.net/lin1109221208/article/details/108724965
https://opensource.apple.com/source/objc4/objc4-750.1/runtime/objc-runtime-new.h.auto.html
https://opensource.apple.com/source/objc4/objc4-750.1/runtime/objc-private.h.auto.html
网友评论