iOS进阶-03isa & 对象本质
isa验证:对象的第一个属性必然是isa
先上代码
######### Person 类定义###########
@interface Person : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int height;
@end
####### 调试代码 ######
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
Class cls = object_getClass(p);//获取当前对象的类
NSLog(@"hello world!");
}
return 0;
}
########## object_getClass()方法源码 ###########
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();//getIsa()方法里面返回了Class
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();////ISA()方法里面返回了Class
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);//Class是isa.bits & ISA_MASK得来的,ISA_MASK是一个宏定义的常数,在不同的架构中值会不同
#endif
}
######## ISA_MASK 定义 ######
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
在NSLog
处打断点,进行LLDB调试
(lldb) x/4gx p
0x101e2f490: 0x001d8001000015fd 0x0000000000000000
0x101e2f4a0: 0x0000000000000000 0x0000000000000000
在之前的章节中我都是直接标写 0x001d8001000015f5
就是isa
,如何证明?继续LLDB调试
(lldb) x/4gx p //打印p对象内存情况
0x101e2f490: 0x001d8001000015fd 0x0000000000000000
0x101e2f4a0: 0x0000000000000000 0x0000000000000000
(lldb) p/x Person.class//先获取Person类的内存地址 ,用于下面比对
(Class) $1 = 0x00000001000015f8 Person
(lldb) p/x 0x001d8001000015fd & 0x00007ffffffffff8//用isa&ISA_MASK ,得到$2
(long) $2 = 0x00000001000015f8//和$1地址是一样的,不就是person类吗
(lldb) po 0x00000001000015f8//我们继续打印$2 ,可以看到就是Person
Person
细心的你应该已经明白,在object_getClass()方法源码
中class是通过isa.bits & ISA_MASK
得来的,所以上面的LLDB调试其实就是反推证明x/4gx p
第一个内存地址就是isa
ISA_MASK 是一个常数宏,在不同的架构中值是不一样的,在x86_64架构中是 # define ISA_MASK 0x00007ffffffffff8ULL;在arm64架构中是 # define ISA_MASK 0x0000000ffffffff8ULL;详见上面的ISA_MASK 定义源码
类的创建的个数
我们知道对象可以创建多个,那类是否也会创建多个?,直接上代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
p.age = 5;
p.name = @"lee";
p.height = 185;
Person *p1 = [Person alloc];
p1.age = 23;
p1.name = @"Bao";
p1.height = 199;
NSLog(@"hello world!");
NSLog(@"hello world!");
}
return 0;
}
还是在NSLog
处打断点,进行LLDB调试
(lldb) po p
<Person: 0x101f5e440>
(lldb) po p1
<Person: 0x101f5f570>
(lldb) x/4gx p
0x101f5e440: 0x001d800100001655 0x000000b900000005
0x101f5e450: 0x0000000100001050 0x0000000000000000
(lldb) x/4gx p1
0x101f5f570: 0x001d800100001655 0x000000c700000017
0x101f5f580: 0x0000000100001070 0x0000000000000000
上面的LLDB调试可以看到,对象p和p1的地址是不同的,但是其对象内存地址打印的第一段都是0x001d800100001655
,说明isa指向同一个类;
再看下面的代码
void testClassNum() {
Class cls1 = [Person class];
Class cls2 = [Person alloc].class;
Class cls3 = object_getClass([Person alloc]);
NSLog(@"\n%p-\n%p-\n%p-",cls1,cls2,cls3);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testClassNum();
}
return 0;
}
//testClassNum()方法打印结果
2020-02-04 18:17:16.811327+0800 Test[44422:1154780]
0x100002668-
0x100002668-
0x100002668-
可以看到,三个地址都是一样的;
结论:
- 对象可以创建多个,但类只创建一个
isa走位
调试代码一
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
p.age = 5;
p.name = @"lee";
p.height = 185;
NSLog(@"hello world!");
}
return 0;
}
还是在NSLog
处打断点,进行LLDB调试
(lldb) x/4gx p//打印对象内存地址
0x101ebac30: 0x001d80010000163d 0x000000b900000005
0x101ebac40: 0x0000000100001058 0x0000000000000000
(lldb) p/x 0x001d80010000163d & 0x00007ffffffffff8
(long) $9 = 0x0000000100001638
(lldb) po $9//$9是类
Person
(lldb) x/4gx 0x0000000100001638//打印类对象内存地址
0x100001638: 0x001d800100001611 0x0000000100b36140
0x100001648: 0x0000000101ebf250 0x0000000300000003
(lldb) p/x 0x001d800100001611 & 0x00007ffffffffff8
(long) $10 = 0x0000000100001610
(lldb) po $10//$10是Person元类
Person
(lldb) x/4gx 0x0000000100001610//打印Person元类对象内存地址
0x100001610: 0x001d800100b360f1 0x0000000100b360f0
0x100001620: 0x0000000100f3f0b0 0x0000000300000007
(lldb) p/x 0x001d800100b360f1 & 0x00007ffffffffff8
(long) $11 = 0x0000000100b360f0
(lldb) po $11//$11是NSObject根元类
NSObject
(lldb) x/4gx 0x0000000100b360f0//打印NSObject根元类对象内存地址
0x100b360f0: 0x001d800100b360f1 0x0000000100b36140
0x100b36100: 0x0000000100f3cfc0 0x0000000400000007
(lldb) p/x 0x001d800100b360f1 & 0x00007ffffffffff8
(long) $12 = 0x0000000100b360f0
(lldb) po $12//$12是NSObject根元类
NSObject
调试代码二:
void testisa() {
//NSObject 实例对象
NSObject *object1 = [NSObject alloc];
//NSObject 类
Class class = object_getClass(object1);
//NSObject 元类
Class metaClass = object_getClass(class);
//NSObject 根元类
Class rootMetaClass = object_getClass(metaClass);
//NSObject 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p- 实例对象\n%p- 类\n%p- 元类\n%p- 根元类\n%p- 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testisa();
}
return 0;
}
//打印结果
2020-02-04 18:30:42.651273+0800 Test[44597:1164375]
0x1035012b0- 实例对象
0x100b38140- 类
0x100b380f0- 元类
0x100b380f0- 根元类
0x100b380f0- 根根元类
这时你再看这张apple的图,是不是清楚很多了,注意看标红的部分
isa走位&继承关系图
结论
1.isa走位:
- 对象--> 类 --> 元类--> 根元类 --> 根元类
- 根元类 isa还是指向根元类
2.继承关系:
- 根元类的父类是NSObject
- NSObject的父类是nil
对象的本质
我们先写两个类Animal和Person
############# Animal ###############
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Animal : NSObject
@end
NS_ASSUME_NONNULL_END
############# Person ###############
#import <Foundation/Foundation.h>
#import "Animal.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
NSString *nickName;//成员变量 -- 基本类型
Animal *cat;//实例变量 -- 由其他类声明而来
}
@property (nonatomic,copy) NSString *tureName;//属性
@end
NS_ASSUME_NONNULL_END
然后将使用终端将Person.m文件编译成Person.cpp文件,具体终端命令如下:
//cd到包含person.m文件的目录下
$ cd /Users/baofan/Desktop/RFDemo/RFMVC_MVP_MVVM/Object
//通过clang命令将person.m文件编译成person.cpp文件
$ clang -rewrite-objc Person.m -o Person.cpp
在原有包含person.m文件的目录下会新增一个Person.cpp文件,双击打开
截屏2020-02-0419.24.20.png
你会发现这是个拥有10000多行代码的文件,头痛!不要急,直接拉倒文件最底部,然后慢慢往上看,我这里挑选了部分代码进行展示
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *nickName;
Animal *cat;
NSString * _Nonnull _tureName;
};
// @property (nonatomic,copy) NSString *tureName;
/* @end */
#pragma clang assume_nonnull end
// @implementation Person
static NSString * _Nonnull _I_Person_tureName(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_tureName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setTureName_(Person * self, SEL _cmd, NSString * _Nonnull tureName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _tureName), (id)tureName, 0, 1); }
// @end
struct _prop_t {
const char *name;
const char *attributes;
};
struct _protocol_t;
struct _objc_method {
struct objc_selector * _cmd;
const char *method_type;
void *_imp;
};
struct _protocol_t {
void * isa; // NULL
const char *protocol_name;
const struct _protocol_list_t * protocol_list; // super protocols
const struct method_list_t *instance_methods;
const struct method_list_t *class_methods;
const struct method_list_t *optionalInstanceMethods;
const struct method_list_t *optionalClassMethods;
const struct _prop_list_t * properties;
const unsigned int size; // sizeof(struct _protocol_t)
const unsigned int flags; // = 0
const char ** extendedMethodTypes;
};
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name;
const char *type;
unsigned int alignment;
unsigned int size;
};
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name;
const struct _method_list_t *baseMethods;
const struct _objc_protocol_list *baseProtocols;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
上面代码显示:Person.cpp中有Person_IMPL
结构体,并且还有_objc_method、 _protocol_t、 _class_ro_t
等结构;
Person_IMPL 及 属性
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *nickName;
Animal *cat;
NSString * _Nonnull _tureName;
};
// @property (nonatomic,copy) NSString *tureName;
/* @end */
#pragma clang assume_nonnull end
// @implementation Person
static NSString * _Nonnull _I_Person_tureName(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_tureName)); }
static void _I_Person_setTureName_(Person * self, SEL _cmd, NSString * _Nonnull tureName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _tureName), (id)tureName, 0, 1); }
// @end
可以看到之前在Person.h中的属性和成员变量都被编译在了Person_IMPL结构体中;细心的你肯定发现了,只有属性tureName
自动生成了_I_Person_tureName _I_Person_setTureName_
方法,而实例方法并没有;
结论:
- 对象在编译之后是一个结构体
- 其属性会生成对应的带下划线的_成员变量,set get方法
- 成员变量不会自动生成get、set方法
方法名简析
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
{{(struct objc_selector *)"tureName", "@16@0:8", (void *)_I_Person_tureName},
{(struct objc_selector *)"setTureName:", "v24@0:8@16", (void *)_I_Person_setTureName_}}
};
_I_Person_tureName : 函数名(函数指针)用于找到函数的具体实现
tureName: 方法名
@16@0:8 方法签名:
- @:返回值类型 - id类型
- 16:总共的量(偏移量)
- @:参数一类型 - id类型 参数偏移范围0-7
- : 参数二类型 - sel类型 参数偏移范围8-15
为什么@ 代表id类型,: 代表sel类型?apple就是这么规定的# Type Encodings
数据类型占用字节大小
类和元类的创建时期
我们知道load
方法早main
方法加载,所以我做一个测试,新建一个Person
和Person+createTime
分类,图片如下:
然后在分类的load方法中打断点
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person alloc];
}
return 0;
}
运行上述代码,程序会断在Person分类的load方法中;此时进行LLDB调试:
(lldb) x/4gx Person.class//能打印出Person类的内存信息,说明类在这之前已经创建
0x100002648: 0x001d800100002621 0x0000000100b37140
0x100002658: 0x00000001003da270 0x0000000000000000
(lldb) p/x 0x001d800100002621 & 0x00007ffffffffff8
(long) $1 = 0x0000000100002620
(lldb) po $1//$1 是Person元类
Person
(lldb) x/4gx 0x0000000100002620
0x100002620: 0x001d800100b370f1 0x0000000100b370f0
0x100002630: 0x0000000100f3ce80 0x0000000200000003
(lldb) p/x 0x001d800100b370f1 & 0x00007ffffffffff8
(long) $2 = 0x0000000100b370f0
(lldb) po $2//$2是NSObject根元类
NSObject
在上述LLDB调试中我们发现,在load的方法之前,内存中就已经含有类、元类、根元类的内存信息,说明类、元类、根元类在编译时期就已经创建。
还有一种方法也能证明:
- command + b 只进行编译
- 将product中的可执行文件拖入MachOView(坏苹果)中查看,可以发现Person类已经存在于可执行文件中 MachOView 可执行文件查看
isa在不同架构中的结构
isa结构注意看 shiftcls占的内存大小在不同架构中是不同的,所以上文中 ISA_MASK 是个根据架构变化的常数值。