前言
通过底层c/c++源码,我们可以来探究iOS对象的本质和isa。
提示:以下是本篇文章正文内容,下面案例可供参考
一、对象的本质是什么?
1.对象的本质
创建类SLPerson
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface SLPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation SLPerson
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在终端通过以下命令将main.m编译生成main.cpp。
xcrun -sdk iphonesimulator clang -rewrite-objc main.m -o main.cpp
SLPerson类被编译成一个结构体SLPerson_IMPL 。
struct SLPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
SLPerson_IMPL中的 NSObject_IVARS中包含isa,OC的继承,在底层中是使用结构体嵌套来实现的。
2.name的set与get方法
static NSString * _I_SLPerson_name(SLPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_SLPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_SLPerson_setName_(SLPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct SLPerson, _name), (id)name, 0, 1); }
在源码中查询objc_setProperty。
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
进入reallySetProperty,该方法中,主要通过将newValue进行retain,将oldValue进行release。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
这里采用了适配器的设计模式 ,开发者通过应用层接口来使用set方法,中间隔离层适配一个统一入口,通过cmd区分不同的set,对内调用底层reallySetProperty,这样无论应用层或者底层的变化,都互不影响,达到上下层隔离接口的目的。
二、isa分析
NSObject中包含一个Class类型的isa,Class实际是一个struct objc_class*,我们来查看源码
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
......此处省略很多行
}
struct objc_object {
private:
isa_t isa;
.....此处省略很多行
}
所以我们发现,其实真正的isa类型是 isa_t,是一个联合体,isa_t的定义如下:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
......此处省略很多行代码
};
1.联合体
联合体是由不同的数据类型组成
,但其变量是互斥
的,所有成员共用一块内存
。同一时刻只能保存一个成员的值
,如果对新成员赋值
,就会将原来成员的值覆盖掉,
其占用的内存
等于最大的成员
占用的内存。
由以上代码可以看出,结构体是共存的,联合体是互斥的。所以联合体使用内存更灵活,节省空间。
2.位域
位域同样也是一种节省空间的手段,下面我们来分析代码
@interface SLDirection : NSObject
@property (nonatomic, assign) BOOL top;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL bottom;
@property (nonatomic, assign) BOOL right;
@end
@implementation SLDirection
{
struct{
char top: 1;
char left: 1;
char bottom: 1;
char right: 1;
}mydirection;
}
- (void)setTop:(BOOL)top{
mydirection.top = top;
}
- (void)setLeft:(BOOL)left{
mydirection.left = left;
}
- (void)setRight:(BOOL)right{
mydirection.right = right;
}
-(void)setBottom:(BOOL)bottom{
mydirection.bottom = bottom;
}
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
SLDirection * d = [[SLDirection alloc]init];
d.top = YES;
d.left = YES;
d.bottom = YES;
d.right = NO;
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过断点分析,结果如下:
原本需要4字节(每个BOOL占1字节)来存储属性,使用位域之后,只需1个字节(8位)来存就很足够。
在isa_t中有个结构体,里边就有个成员ISA_BITFIELD使用了位域,ISA_BITFIELD是一个宏定义,有两个版本 __arm64__
(对应ios 移动端) 和 __x86_64__
(对应macOS)下边以arm64为例。
# if __arm64__
。。。。
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
其中
- nonpointer:0——纯isa指针;1——包括类对象地址、类信息、引用计数等。
- has_assoc:0——
没有关联
对象;1——存在关联
对象 - shiftcls:这里用来存储类的地址
3.isa关联指针与类
对于NonpointerIsa,isa并不只是单纯的一个指针,实际上只有33位(arm64)用于存储类对象的地址。
a.通过掩码来还原类信息
b.通过位运算还原类信息
4.补充:TaggedPointer
当isa的nonpointer为0时,直接绑定类和地址的对应关系,即TaggedPoint。像NSDate和
NSNumber等
这样的小对象存储的值,绝大多数情况并不会大于20亿这个量级。如果采用指针、堆内存的方式,那势必会造成内存的浪费和性能损耗。苹果采用将value
值直接存储在isa_t
中的uintptr_t bits
上,并且用一些特殊标识来标明此isa
是TaggedPoint
类型的。这样用isa
就存储了值,而不需要在堆上分配内存再去存储值。要知道堆内存的分配、释放及访问,要比栈内存慢很多的。