一、类的内存的ro数据
上篇文章,我们可以通过lldb看到类结构里的属性和实例方法,但是没看实例变量和类方法,接下来继续探究
@interface ApplePerson : NSObject{
NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;
- (void)sayHenXi;
+ (void)sayNiuBi;
@end
我们翻阅源码查找实例变量
ivars(instance variables),在结构体class_ro_t里面
struct class_ro_t {
…
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
…
}
我们翻阅苹果开发者视频,也验证了这个想法
再次查看源码,发现有返回
class_ro_t结构的方法
const class_ro_t *ro() const {
...
}
二、实例变量和属性以及编码
接下来测试一下,打印内存
(lldb) x/6gx ApplePerson.class
0x100008498: 0x0000000100008470 0x0000000100379140
0x1000084a8: 0x000000010036d0e0 0x0000803000000000
0x1000084b8: 0x00000001007a3024 0x00000001000084e8
平移32个字节
(lldb) p/x 0x100008498 + 0x20
(long) $1 = 0x00000001000084b8
强转打印
(lldb) p (class_data_bits_t *)0x00000001000084b8
(class_data_bits_t *) $2 = 0x00000001000084b8
获取数据
p $2->data()
(class_rw_t *) $3 = 0x00000001007a3020
获取
ro
(lldb) p *$3.ro()
(const class_ro_t) $5 = {
...
ivars = 0x00000001000080d0
...
}
可以看到有
ivars,继续获取
(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000080d0
打印
$6
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
}
可以看到
$7是一个list类型,所以用get获取数据
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x0000000100008450
name = 0x0000000100003e90 "salary"
type = 0x0000000100003f4c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x0000000100008454
name = 0x0000000100003e97 "age"
type = 0x0000000100003f58 "q"
alignment_raw = 3
size = 8
}
(lldb) p $7.get(2)
(ivar_t) $10 = {
offset = 0x0000000100008458
name = 0x0000000100003e9b "_name"
type = 0x0000000100003f4c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(3)
(ivar_t) $11 = {
offset = 0x000000010000845c
name = 0x0000000100003ea1 "_workNumber"
type = 0x0000000100003f4c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(4)
Assertion failed: (i < count), function get, file /Users/.../runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
可以获取到
salary、age、_name、_workNumber四个ivars,对比类的结构
@interface ApplePerson : NSObject{
NSString *salary;
NSInteger age;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;
可以发现实例变量在
ivars里面是原封不动的存在,而属性也存在ivars里面,但是加了下划线_。同时,可以ivars里面看到NSString是@\"NSString\"类型size=8,是8字节,而NSInteger是"q"类型,size=8,说明NSInteger在64位系统下也是8字节
三、查在C++代码底层的实现
由于源码代码太多,查看不方便,新建一个空的命令行工程
#import <Foundation/Foundation.h>
@interface ApplePerson : NSObject{
NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;
- (void)sayHenXi;
+ (void)sayNiuBi;
@end
@implementation ApplePerson
- (void)sayHenXi{
}
+ (void)sayNiuBi{
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
clang一下clang -rewrite-objc main.m -o niubi.cpp,会得到cpp文件,搜索ApplePerson
发现自己定义的属性和方法在底层以及被屏蔽,属性
name和workNumber在结构体ApplePerson_IMPL里面生成带下划线_的实例变量,并且带有set/get方法还可以看到方法列表
里面有
v16@0:8和v24@0:8@16属于OC的类型编码
Objective-C Runtime Programming Guide
| Code | Meaning |
|---|---|
c | A char |
i | An int |
s | A short |
l | A long``l is treated as a 32-bit quantity on 64-bit programs. |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
| [array type] | An array |
| {name=type... } | A structure |
| (name=type... ) | A union |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
v16@0:8的v代表void;16代表参数占用内存大小;@代表一个对象(无论是静态类型id还是类型化id)其实就是id self;0代表从0号位置开始;:方法选择器(SEL);8代表从8号位置开始,id self是结构体指针类型8字节,SEL是8字节,刚好16字节
四、setter方法的底层实现
接下来我们研究属性对于底层代码的影响
@interface ApplePerson : NSObject{
NSString *salary;
}
//@property (nonatomic, copy) NSString *name;
//@property (nonatomic, copy) NSString *workNumber;
@property (nonatomic, copy) NSString *nc_job_title;
@property (atomic, copy) NSString *ac_job_title;
@property (nonatomic) NSString *n_job_title;
@property (atomic) NSString *a_job_title;
clang一下
static void _I_ApplePerson_setNc_job_title_(ApplePerson * self, SEL _cmd, NSString *nc_job_title) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _nc_job_title), (id)nc_job_title, 0, 1); }
static void _I_ApplePerson_setAc_job_title_(ApplePerson * self, SEL _cmd, NSString *ac_job_title) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _ac_job_title), (id)ac_job_title, 1, 1); }
static void _I_ApplePerson_setN_job_title_(ApplePerson * self, SEL _cmd, NSString *n_job_title) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_n_job_title)) = n_job_title; }
static void _I_ApplePerson_setA_job_title_(ApplePerson * self, SEL _cmd, NSString *a_job_title) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_a_job_title)) = a_job_title; }
可以看到,属性带
copy的在底层会有objc_setProperty方法,不带copy的则没有,源码翻阅objc_setProperty
无论哪个方法,都会调用
reallySetProperty(self, _cmd, newValue, offset, true, false, false);查看
reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
...
}
可以看到
copy、mutableCopy的底层深浅拷贝操作的不同,接着我们再定义strong和copy两个属性进行对比
@interface ApplePerson : NSObject{
NSString *salary;
}
@property (nonatomic, copy) NSString *nc_job_title;
@property (nonatomic, strong) NSString *ns_job_title;
clang一下
static void _I_ApplePerson_setNc_job_title_(...) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ApplePerson, _nc_job_title), (id)nc_job_title, 0, 1); }
static void _I_ApplePerson_setNs_job_title_(...) { (*(NSString **)((char *)self + OBJC_IVAR_$_ApplePerson$_ns_job_title)) = ns_job_title; }
可以看到
copy走的是objc_setProperty,而strong走的是内存平移的方法,这里可以看到它们之间的区别
五、类方法的存储
上篇文章,我们只在底层看到实例方法,没有看到类方法,把执行文件拉进烂苹果,可以看到
+号方法sayNiuBi
查看clang后的代码
static void _I_ApplePerson_sayHenXi(ApplePerson * self, SEL _cmd) {
}
static void _C_ApplePerson_sayNiuBi(Class self, SEL _cmd) {
}
I应该是Instance method,C应该是代表Class methods,可以看到其传入参数,一个是ApplePerson *,另一个是Class,也就是ApplePerson的上一层,就是ApplePerson的元类,接下来在源码做验证
//打印内存地址
(lldb) x/4gx ApplePerson.class
0x100008470: 0x0000000100008448 0x0000000100379140
0x100008480: 0x000000010036d0e0 0x0000802800000000
//拿到isa
(lldb) p/x 0x0000000100008448 & 0x007ffffffffffff8
(long) $1 = 0x0000000100008448
//拿到元类地址
(lldb) po 0x0000000100008448
ApplePerson
平移32位
(lldb) p/x 0x0000000100008448 + 0x20
(long) $3 = 0x0000000100008468
还原
(lldb) p/x (class_data_bits_t *)$3
(class_data_bits_t *) $4 = 0x0000000100008468
拿到
data
(lldb) p $4->data()
(class_rw_t *) $5 = 0x00000001011a5600
查看
(lldb) p *$5
(class_rw_t) $6 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4302404417
}
}
firstSubclass = nil
nextSiblingClass = 0x00000001fe029dc0
}
获取方法
(lldb) p $6.methods()
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000100003d20
}
arrayAndFlag = 4294982944
}
}
}
获取方法列表
(lldb) p $7.list
(const method_list_t_authed_ptr<method_list_t>) $8 = {
ptr = 0x0000000100003d20
}
获取
ptr
(lldb) p $8.ptr
(method_list_t *const) $9 = 0x0000000100003d20
查看
$9
(lldb) p *$9
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 1)
}
获取方法名称
(lldb) p $10.get(0).name
(SEL) $11 = "sayNiuBi"
Fix-it applied, fixed expression was:
$10.get(0).name()
最终在元类找到类方法
sayNiuBi
六、类方法存储的API方式解析
通过API验证类和元类的方法,新工程先定义一个类
@interface ApplePerson : NSObject{
NSString *salary;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *workNumber;
- (void)sayHenXi;
+ (void)sayNiuBi;
定义获取方法名的方法
void AppleObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
通过类和元类分别调用方法
ApplePerson *person = [ApplePerson alloc];
Class pClass = object_getClass(person);
AppleObjc_copyMethodList(pClass);
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
NSLog(@"------------------------------");
AppleObjc_copyMethodList(metaClass);
打印输出
Method, name: sayHenXi
Method, name: name
Method, name: setName:
Method, name: workNumber
Method, name: setWorkNumber:
Method, name: .cxx_destruct
------------------------------
Method, name: sayNiuBi
上面类打印属性的
set/get方法,c++析构函数方法和实例方法;元类打印了类方法,
接下来再通过实例方法验证
void AppleInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method instance_Method1 = class_getInstanceMethod(pClass, @selector(sayHenXi));
Method instance_Method2 = class_getInstanceMethod(metaClass, @selector(sayHenXi));
Method instance_Method3 = class_getInstanceMethod(pClass, @selector(sayNiuBi));
Method instance_Method4 = class_getInstanceMethod(metaClass, @selector(sayNiuBi));
NSLog(@"\nfun : %s \ninstance_Method1 : %p \ninstance_Method1 : %p \ninstance_Method1 : %p\ninstance_Method1 : %p",__func__,instance_Method1,instance_Method2,instance_Method3,instance_Method4);
}
调用
ApplePerson *person = [ApplePerson alloc];
Class pClass = object_getClass(person);
AppleInstanceMethod_classToMetaclass(pClass);
打印输出
fun : AppleInstanceMethod_classToMetaclass
instance_Method1 : 0x100003df1
instance_Method1 : 0x0
instance_Method1 : 0x0
instance_Method1 : 0x100003dd9
类的实例方法输出地址
0x100003df1,元类实例方法输出0x100003dd9
再用类方法验证
void AppleClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method class_method1 = class_getClassMethod(pClass, @selector(sayHenXi));
Method class_method2 = class_getClassMethod(metaClass, @selector(sayHenXi));
Method class_method3 = class_getClassMethod(pClass, @selector(sayNiuBi));
Method class_method4 = class_getClassMethod(metaClass, @selector(sayNiuBi));
NSLog(@"\nfun : %s \nclass_method1 : %p \nclass_method2 : %p \nclass_method3 : %p\nclass_method4 : %p",__func__,class_method1,class_method2,class_method3,class_method4);
}
调用
ApplePerson *person = [ApplePerson alloc];
Class pClass = object_getClass(person);
AppleClassMethod_classToMetaclass(pClass);
打印输出
fun : AppleClassMethod_classToMetaclass
class_method1 : 0x0
class_method2 : 0x0
class_method3 : 0x100003d61
class_method4 : 0x100003d61
class_method4是元类的类方法,class_method4是元类的对象方法,为何可以拿到类方法,并且可以看类的类方法是
0x100003d61,元类的类方法是0x100003d61返回地址相同,查看源码
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
当传入是一个元类的时候,返回元类的实例方法,所以一致 通过获取imp查找方法的实现
void AppleIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHenXi));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHenXi));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayNiuBi));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayNiuBi));
NSLog(@"\n %s \n imp1:%p\n imp2:%p\n imp3:%p\n imp4:%p",__func__,imp1,imp2,imp3,imp4);
}
调用
ApplePerson *person = [ApplePerson alloc];
Class pClass = object_getClass(person);
AppleIMP_classToMetaclass(pClass);
打印输出
AppleIMP_classToMetaclass
imp1:0x100003a00
imp2:0x19305f6c0
imp3:0x19305f6c0
imp4:0x100003a14
imp1和imp4有时正确的,imp2和imp3理论上应该是nil,为和会有地址,查看源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
…
if (!imp) {
return _objc_msgForward;
}
return imp;
}
原来imp不存的的时候,返回
_objc_msgForward
最终结论:所以OC其实没有所谓的类方法,实际都是实例方法