一、[self class] 和 [super class]区别
在 MyPerson
类中添加如下代码:
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@~%p - %@~%p",[self class],[self class],[super class],[super class]);
}
return self;
}
运行工程,输出结果:
MyPerson~0x1000035f8 - MyPerson~0x1000035f8
可看到[self class]
和[super class]
均是 MyPerson
.下面对其进行原理分析。
1、clang 编译 .cpp ,
clang -rewrite-objc MyPerson.m -o MyPerson.cpp
编译后内容如下:
static instancetype _I_MyPerson_init(MyPerson * self, SEL _cmd) {
self = ((MyPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)(
(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyPerson"))},
sel_registerName("init")
);
if (self) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hv_q0n645rs10j0n5hv1x4ds3vc0000gn_T_MyPerson_123f05_mi_1,
// [self class] --> objc_msgSend()
((Class (*)(id, SEL))(void *)objc_msgSend)(
(id)self, // 消息接收者
sel_registerName("class") // cmd
),
// [super class] -->objc_msgSendSuper()
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper(
// 参数1 - 消息接收者
(__rw_objc_super){
(id)self, // object
(id)class_getSuperclass(objc_getClass("MyPerson")) // superClass
},
// 参数2 - SEL cmd
sel_registerName("class")
)
);
}
return self;
}
结构体 __rw_objc_super
:
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
1、消息 - cmd
getClass()
其实就是是 getIsa()
,代码如下,这里不再多做赘述。
- (Class)class {
return object_getClass(self);
}
/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
2、消息接收者分析 - self
// 实现有汇编编写,源码在下面
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
[self class]
: objc_msgSend()
的隐藏参数self
即消息接受者
,这里没什么可说的;
下面对[super class]
本质进行具体分析。
2.1、super
本质
super
是一个关键字,是寄存器下的一个指令,它的本质是什么呢?
--> 结构体 objc_super
:
#ifndef OBJC_SUPER
#define OBJC_SUPER
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;// 消息接收者
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
// super_class 是第一个要搜索的类
/* super_class is the first class to search */
};
#endif
结合上面编译后代码,可知,取的是super_class
. 那么[super class]
:
- 消息接收者
receiver
-->self
即MyPerson
- 方法查找时,搜索的第一个类是
super_class
*
通过断点调试也可得知,self
均为MyPerson
:
可以总结了吗?:NO!
[self class]
的本质是objc_msgSend()
. 我们都已清楚;
[super class]
的本质是objc_msgSendSuper()
吗?
不!它只是在编译时被编译成了objc_msgSendSuper()
,运行时调用的是objc_msgSendSuper2()
.
2.2、验证运行时 objc_msgSendSuper2()
打开汇编,control + step into
一步步调试,见下图,运行时调的方法是class_getSuperclass2()
:
3、objc_msgSend
/objc_msgSendSuper
/objc_msgSendSuper2
汇编源码
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in r11
* Forwarding returned in Z flag
* r10 reserved for our use but not used
*
********************************************************************/
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
GetIsaCheckNil NORMAL // r10 = self->isa, or return zero
CacheLookup NORMAL, CALL // calls IMP on success
GetIsaSupport NORMAL
NilTestReturnZero NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
GetIsaCheckNil NORMAL // r10 = self->isa, or return zero IMP
CacheLookup NORMAL, LOOKUP // returns IMP on success
GetIsaSupport NORMAL
NilTestReturnIMP NORMAL
// cache miss: go search the method lists
LCacheMiss:
// isa still in r10
jmp __objc_msgLookup_uncached
END_ENTRY _objc_msgLookup
ENTRY _objc_msgSend_fixup
int3
END_ENTRY _objc_msgSend_fixup
STATIC_ENTRY _objc_msgSend_fixedup
// Load _cmd from the message_ref
movq 8(%a2), %a2
jmp _objc_msgSend
END_ENTRY _objc_msgSend_fixedup
/********************************************************************
*
* id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...);
*
* struct objc_super {
* id receiver;
* Class class;
* };
********************************************************************/
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
// search the cache (objc_super in %a1)
movq class(%a1), %r10 // class = objc_super->class
movq receiver(%a1), %a1 // load real receiver
CacheLookup NORMAL, CALL // calls IMP on success
// cache miss: go search the method lists
LCacheMiss:
// class still in r10
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
/********************************************************************
* id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
*
* struct objc_super {
* id receiver;
* Class cls; // SUBCLASS of the class to search
* }
********************************************************************/
ENTRY _objc_msgSendSuper2
ldr r9, [r0, #CLASS] // class = struct super->class
ldr r9, [r9, #SUPERCLASS] // class = class->superclass
CacheLookup NORMAL, _objc_msgSendSuper2
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr r0, [r0, #RECEIVER] // load real receiver
bx r12 // call imp
CacheLookup2 NORMAL, _objc_msgSendSuper2
// cache miss
ldr r9, [r0, #CLASS] // class = struct super->class
ldr r9, [r9, #SUPERCLASS] // class = class->superclass
ldr r0, [r0, #RECEIVER] // load real receiver
b __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper2
*总结:
-
[self class]
的本质是objc_msgSend
(self, obj->getIsa()
); -
[super class]
的本质是class_getSuperclass2()
,消息接受者是self
,它的方法查找会从super_class
开始,省一步self
类自己的查找,会更快速。
二、内存平移问题
1、首先,准备如下代码,运行工程:
MyPerson
文件代码:
// .h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyPerson : NSObject
@property (nonatomic, assign) int y_age;
@property (nonatomic, copy) NSString *y_name;
- (void)doSomething;
@end
NS_ASSUME_NONNULL_END
// .m
#import "MyPerson.h"
@implementation MyPerson
- (void)doSomething { // self 消息接受者 - MyPerson: 0x7ffee8a7c0e8
// person -> y_name -> 8字节
NSLog(@"函数 %s - %@",__func__,self.y_name);
}
@end
ViewController.m
代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// ViewController 当前的类
// self cmd (id)class_getSuperclass(objc_getClass("LGTeacher")) self cls kc person
Class cls = [MyPerson class];
void *personAddr = &cls; //
MyPerson *person = [MyPerson alloc];
NSLog(@"%p - %p",&person,personAddr);
// 0x7ffeeb154178 - 0x7ffeeb154188
[(__bridge id)personAddr doSomething];
// 函数 -[MyPerson doSomething] - ViewController
[person doSomething]; // self.y_name = nil - (null)
// 函数 -[MyPerson doSomething] - (null)
通过上面执行结果,person
对象的结果自不必提,但是personAddr
可以正常执行doSomeThing
且在y_name
并未赋值的情况下有值ViewController
,这是为何?
通过lldb
调试,打印个变量地址如下:
(lldb) p &personAddr
(void **) $9 = 0x00007ffee9b51180
(lldb) p personAddr
(MyPerson *) $10 = 0x00007ffee9b51188
(lldb) p &person
(MyPerson **) $11 = 0x00007ffee9b51178
(lldb) p &cls
(Class *) $12 = 0x00007ffee9b51188
指针personAddr
的地址即[MyPerson class]
类的地址,person
对象的地址小8字节。
通过 OC 底层探索04 中已知类的结构,OC 底层探索 03 中已知对象的本质是个结构体。如下图:
上图信息:person
对象的isa
和personAddr
指针都指向MyPerson
类执行方法时所有流程一致,so,personAddr
也可正常执行方法;当查找对象y_name
时,根据person
对象的结构,内存平移8字节找到属性变量y_name
。
但是personAddr
只是一个 8字节的指针变量
,地址指向MyPerson
类而已并无实质关联,personAddr
指针变量是没有y_name
等属性内容的,指针personAddr
平移 8 字节找到的自然不会是y_name
--> 那么它拿到的是什么呢?
2、栈
的信息情况
压栈情况大概图示(后面详细):
image.png2.1、函数的隐藏参数压栈问题
示例代码:
// 定义一个函数
void my_func (id person, id mySel){
NSLog(@"person == %p - mySel == %p",&person,&mySel);
}
my_func(@(10),@(20));
// 调用结果输出:
// person == 0x7ffeecba3138 - mySel == 0x7ffeecba3130
已知栈
是个先进后出 地址连续且由高到低
的结构。
由上面代码也可知,函数my_func()
的2个参数压入栈里面,压栈顺序从前往后,地址连续。
2.2、结构体压栈
代码:
// 定义一个 struct
struct my_struct {
int num1;
int num2;
};
MyPerson *personTest = [MyPerson alloc];
struct my_struct struct1 = {111, 222};
lldb
调试如下:
(lldb) p &personTest // personTest变量 的地址
(MyPerson **) $0 = 0x00007ffee3196188 // 地址 -8
(lldb) p *(int *)0x00007ffee3196180
(int) $1 = 111
(lldb) p *(int *)0x00007ffee3196184 // 一个 int 4字节
(int) $3 = 222
(lldb)
由上:结构体成员压栈顺序由后往前
。 --> so 压站信息图中所示(super本质在文章上面已有探究),结构体中两个成员,(id)class_getSuperclass(objc_getClass("MyPerson"))
先入站,self
后入栈。
2.3、打印从 self
到 person
对象 之间的压栈信息
void *sp = (void *)&self;
void *end = (void *)&person;
long count = (sp - end) / 0x8;
for (long i = 0; i <= count; i++) {
void *address = sp - 0x8 * i;
if ( i == 1) {// _cmd 是 串类型
NSLog(@"%p : %s",address, *(char **)address);
}else{
NSLog(@"%p : %@",address, *(void **)address);
}
}
/* 输出信息如下
0x7ffee65911a8 : <ViewController: 0x7f8c6a702c30> // self
0x7ffee65911a0 : viewDidLoad // cmd
0x7ffee6591198 : ViewController // (id)class_getSuperclass(objc_getClass("MyPerson"))
0x7ffee6591190 : <ViewController: 0x7f8c6a702c30> // self
0x7ffee6591188 : MyPerson
0x7ffee6591180 : <MyPerson: 0x7ffee6591188> // 指针变量的地址 &personAddr
0x7ffee5171178 : <MyPerson: 0x600000934980> // person 对象的地址
*/
由上,MyPerson
地址平移8 恰好是<ViewController: 0x7f8c6a702c30>
。
总结:
- 函数形参压栈,顺序是从前往后;
- 结构体成员压栈,顺序从后向前;
2.1 结构体中成员,int
类型成原会直接分配占内存;-->基本数据类型(值类型) 在栈上分配存储空间
2.2 需开辟空间的MyPerson
类型变量的地址会压栈(person 变量所在的空间是栈
),但变量内存分配在堆上(person 指向的空间在堆
)。-->需开辟空间的数据类型,栈中只存其地址,存储空间在堆上分配
以上。
网友评论