开发过程中遇到一个问题.
问题简化描述如下:
有一个UIView的子类(CTestLevel),实现了init方法和initWithFrame方法,只调用了CTestLevel的init,init中只调用了[super init] , 可为什么当前类的initWithFrame被调用了?不应该是调用父类吗?怎么调用了子类的initWithFrame?
如下:
- 初始化一个对象
CTestLevel *testObj = [CTestLevel alloc] init];
@implementation CTestLevel
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSLog(@"initWithFrame");
}
return self;
}
-(instancetype)init
{
self = [super init];
if (self)
{
NSLog(@"init");
}
return self;
}
@end
2.设置断点,查看调用关系
3 查看堆栈
查看调用关系和堆栈,
testObjc调用了init方法,然后调用了UIView的init的方法,这没有问题,
因为CTestLevel的init方法调用了[super init];
- 堆栈中最上层为什么又调用了CTestLevel 的initWithFrame方法呢?
- initWithFrame是UIView的指定初始化方法,按道理应该调用UIView的initWithFrame方法啊,为什么调用了CTestLevel的initWithFrame的初始化方法?
- 因为子类重写了initWithFrame,所以调用子类的initWithFrame?如果不重写就调用UIView的initWithFrame?
- super self 都是什么意思?
这都是怎么一个逻辑,具体是怎么实现的?
- 先说明Objective-C的类图结构:
其中实心箭头表示继承关系,CTestLevel继承自UIView,UIView继承自UIResponder, …一直继承到NSObject.NSObject的父类为nil, 所有类变量到最终的父类都是nil.
虚线箭头表示所属关系:testObj属于CTestLevel类的对象,CTestLevel类属于CTestLevel元类的对象。所有的元类对象属于NSObject元类对象,NSObject元类对象属于自身.
- 代码说明Objective-C类的结构
Objective-C中所有的东西都是对象,即使是类,它也是一种对象。
testObj对象的如下表示:
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
objc_object 表示一个对象,其中的isa指向CTestLevel类
Class的结构体定义如下:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
其中isa指向CTestLevel元类,super_class指向其父类UIView,ivars存放变量,methodLists方法列表,cache缓存方法,protocols类所遵循的协议。
self和super的含义,在Objective-C中self表示当前对象(就是这个testObj对象),类似于C++里面的this指针;
super其实是一个编译器指示符,在Runtime运行期这个关键词是不存在的。只是辅助编译器做了一些处理罢了,类似语法糖的东西。如何向对象发消息
testObj对象可以调用(init,initWithFrame, setHidden等方法),具体是怎么做到的?
例如:
当[testObj init]这样发送消息的时候,会转化为 id objc_msgSend(id theReceiver, SEL theSelector, …)来调用。其中theReceiver表示testObj对象,theSelector表示init方法. …表示参数,这里参数为空。
简写成如下这样:obj_msgSend(testObj,init,”“)
如果是调用哪个initWithFrame的话,大体如下:
obj_msg_Send(testObj,@Selector(initWithFrame),frame)
那调用[super init]的时候,没有testObj这个对象了?怎么办?
[super init]会转化成如下的方法
id objc_msgSendSuper(struct objc_super *super, SEL op, …)
一个objc_super,它具体是什么呢?结构体定义如下:
struct objc_super {
id receiver;
Class superClass;
};
里面也有一个receiver,这个receiver就是消息的接受者,这里是self,就是这个testObj对象!
superClass: self的父类,也就是UIView.
op:表示方法;这里就是init.
一句话:去父类里面找init方法,然后给testObj发消息。对,用当前对象,调用父类的方法。
self和super的区别,就是去哪里找方法的问题,接受消息的对象还是那个对象。
举个例子说明:
类的继承关系:
CTestLevel_3继承自CTestLevel_2,CTestLevel_2继承自CTestLevel_1,CTestLevel_1继承自UIView.
testObj是一个CTestLevel_3类型的对象。
CTestLevel_3实现了一个testLevel3方法,方法直接调用 : [super testLevel2];
CTestLevel_2实现了一个testLevel2方法,方法打印输出:NSStringFromClass([self class]);
因为testObj是一个CTestLevel_3的对象类型,所以输出结果[self class]是一个CTestLevel_3类型。等价于给CTestLevel_3类型的对象,发送父类的testLevel2消息。
经常遇到下面的问题:
NSLog(@"%@\n",self); //CTestLevel_3
NSLog(@"%@\n",[self class]);//CTestLevel_3
NSLog(@"%@\n",[self superclass]);//CTestLevel_2
NSLog(@"%@\n",[[self class] superclass]);//CTestLevel2 class
NSLog(@"%@\n",[super class]);//CTestLevel3
NSLog(@"%@\n",[super superclass]);//CTestLevel2
完。