iOS对象本质及isa

文章目录

前言

通过底层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字节(每个BOOL1字节)来存储属性,使用位域之后,只需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的nonpointer0时,直接绑定类和地址的对应关系,即TaggedPoint。像NSDateNSNumber等这样的小对象存储的值,绝大多数情况并不会大于20亿这个量级。如果采用指针内存的方式,那势必会造成内存的浪费和性能损耗。苹果采用将value值直接存储在isa_t中的uintptr_t bits上,并且用一些特殊标识来标明此isaTaggedPoint类型的。这样用isa就存储了值,而不需要在上分配内存再去存储值。要知道内存的分配、释放及访问,要比内存慢很多的。

猜你喜欢

转载自blog.csdn.net/weixin_38016552/article/details/125619343
isa