NSObject 中 isa 指针源码解读

oc runtime源码 版本 objc4-723
先来一段大家都知道的知识
这里写图片描述
NSObject类在程序运行会有多个实例对象,一个类对象,一个元类对象。
其中所有实例对象中的isa指针指向类对象,类对象中的isa指针指向元类对象。NSObject 的元类对象指向自己,其他元类对象指向 NSObject 的元类对象

OC 对象都是 C 语言的结构体,所有的对象都包含一个类型为 isa_t 的 isa 指针.

上面是结论,咱证明一下,新建Xcode 工程后 修改 mai.m 文件

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface Person : NSObject{
    NSString * _name;
    int         age;
}
@end
@implementation Person
@end
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject * obj = [Person new];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

创建了一个新类Person,父类为 NSObject,然后在命令行执行如下命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp

然后就会在生成的 main-arm64.cpp 文件中找到如下结构体

struct NSObject_IMPL {
    __unsafe_unretained Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *__strong _name;
    int age;
};

猜测 Person_IMPL 应该是 Persion Implementation 的简写,Implementation 在计算机语义中是实现的意思,Person_IMPL 就是 Persion 底层实现的意思。
从这可以证明 OC 对象都是 C 语言的结构体

在 OC 中所有的类都是继承自NSObject,来看一下 NSObject 的结构

@interface NSObject <NSObject> { 
   Class isa  OBJC_ISA_AVAILABILITY;
 }
typedef struct objc_class *Class;

objc_class 结构体的源码 ,下面的代码就是从 oc runtime 源码搜到的

struct objc_class : objc_object {
    // 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

    class_rw_t *data() { 
        return bits.data();
    }
    ...
}

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    ...
}

从上面代码可以看到,NSObject 类有一个类型为 Class 的成员变量isa , Class 是结构体 objc_class 的别名,结构体 objc_class 继承自 结构体objc_object, 结构体 objc_object 中第一个成员变量就是 isa_t 类型的 isa,还是私有变量。
到这里就证明了 所有的对象都包含一个类型为 isa_t 的 isa 指针 这句话。
下面来看一下 isa_t 到底是个啥

typedef unsigned long       uintptr_t;
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        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)
    };
};

上面的代码我去掉了 __x86_64__ iMac、 __ARM_ARCH_7K__ iWatch 相关代码,只保留了与__arm64__iPhone 有关的代码。

union结构是一种特殊的类,union 里的成员内存是共享的,以size最大的结构作为自己的大小。
union可以用来测试CPU是大端模式还是小端模式

void checkCPU()
{
    union MyUnion{
        int i;
        char c;
    }test;
    test.i = 1;
    if (test.c == 1)
        printf("little endian \n");
    else
        printf("big endian \n");
}
int main(int argc, char * argv[]) {
        checkCPU();
}

union(联合体) isa_t 中有构造函数isa_t(uintptr_t value)和 isa_t(), 成员变量 Class 类型的 cls,unsigned long 类型的 bits以及一个结构体,cls、bits、结构体 共享一块长8字节64位的内存,结构体使用位域技术存储更多的信息。

isa 指针在 arm64 架构之前就是一普通指针,存储着Class、Meta-Class 对象的内存地址,在 arm64 之后,才变成现在这个样子的。
解释一下 结构体中 变量是啥意思

变量位(bit)
nonpointer1是否开启指针优化
has_assoc1是否设置过关联对象,如果没有释放更快
has_cxx_dtor1表示对象是否有C++的析构函数(.cxx_destruct),如果没有释放更快
shiftcls33类指针,存储着 Class、Meta-Class 对象的内存地址
magic6固定值1a,用于调试时分辨对象是否未完成初始化
weakly_referenced1是否有被弱引用指向过,如果没有释放更快
deallocating1对象是否正在释放
has_sidetable_rc1该对象的引用计数值是否过大而导致无法存储在extra_rc 中
extra_rc19存储引用计数值减一后的结果

isa 初始化

当调用 [NSObject alloc] 创建一个新的实例的时候,会调用到 initInstanceIsa 函数

define ISA_MAGIC_VALUE 0x000001a000000001ULL
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
        isa = newisa;
    }
}

hasCxxDtor 值是从 类对象的isa中取出的。上面的代码可以精简为

  newisa.bits = ISA_MAGIC_VALUE;
  newisa.has_cxx_dtor = hasCxxDtor;
  newisa.shiftcls = (uintptr_t)cls >> 3;
bits

使用 ISA_MAGIC_VALUE 设置了 isa_t 联合体的 nonpointer 和 magic,nonpointer为1,magic为1a ,其他变量为零。
nonpointer为1 代表 isa_t 开启了指针优化,isa不再是一个内存指针,里面包含了类信息、对象的引用计数信息等。
nonpointer为0 代表 没有开启指针优化,当访问isa时,直接返回 cls 变量

has_cxx_dtor

hasCxxDtor 值是从 类对象的isa中来,那 类对象的 hasCxxDtor 值是从哪里来的呢.

// class has .cxx_construct/destruct implementations
#define RO_HAS_CXX_STRUCTORS  (1<<2)

static Class realizeClass(Class cls){

    const class_ro_t *ro;
    ...

    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

    ...

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    ...
}

realizeClass 函数会给类创建 class_rw_t * 数据, class_rw_t 结构里面包含了类的函数数组,属性数组,协议数组等,这些数组是可读写的,数组里也包含分类里的内容.
class_ro_t *ro 是只读的,里面存储的函数、属性等是不包含分类里的.
类对象的 hasCxxDtor 值来自 ro 的 flags,

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
}

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;

    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

下面来看一下获取类的方法

// Look for a __DATA or __DATA_CONST or __DATA_DIRTY section 
// with the given name that stores an array of T.
template <typename T>
T* getDataSection(const headerType *mhdr, const char *sectname, 
                  size_t *outBytes, size_t *outCount)
{
    unsigned long byteCount = 0;
    T* data = (T*)getsectiondata(mhdr, "__DATA", sectname, &byteCount);
    if (!data) {
        data = (T*)getsectiondata(mhdr, "__DATA_CONST", sectname, &byteCount);
    }
    if (!data) {
        data = (T*)getsectiondata(mhdr, "__DATA_DIRTY", sectname, &byteCount);
    }
    if (outBytes) *outBytes = byteCount;
    if (outCount) *outCount = byteCount / sizeof(T);
    return data;
}

#define GETSECT(name, type, sectname)                                   \
    type *name(const headerType *mhdr, size_t *outCount) {              \
        return getDataSection<type>(mhdr, sectname, nil, outCount);     \
    }                                                                   \
    type *name(const header_info *hi, size_t *outCount) {               \
        return getDataSection<type>(hi->mhdr(), sectname, nil, outCount); \
    }

GETSECT(_getObjc2ClassList,           classref_t,      "__objc_classlist"); //获取当前前注册的所有类
GETSECT(_getObjc2NonlazyClassList,    classref_t,      "__objc_nlclslist");//获取所有非懒加载的类(实现了+load)

_getObjc2ClassList 获取所有类, 通过 getDataSection 函数看,Class cls 信息是从 Mach-O 中的 __DATA 数据段获取的,也就是说 ro 中的 flags 应该是编译期写在Mach-O文件中的.

shiftcls

shiftcls存储的是类指针, 将位移(shift)后的 cls 存入 shiftcls(类指针按照 8 字节对齐,所以最后三位一定是 0,所以可以右移三位来节省位的使用)。

has_assoc

has_assoc是否设置过关联对象,如果没有释放更快
runtime提供給我们关联对象的方法

//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)
//移除关联的对象
void objc_removeAssociatedObjects(id object)

变量说明:

id object:被关联的对象(如xiaoming)
const void *key:关联的key,要求唯一
id value:关联的对象(如dog)
objc_AssociationPolicy policy:内存管理的策略

objc_AssociationPolicy policy的enum值有:

OBJC_ASSOCIATION_ASSIGN = 0,          
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
OBJC_ASSOCIATION_RETAIN = 01401,       
OBJC_ASSOCIATION_COPY = 01403

现在看一下关联对象函数

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        ...
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
               ...
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
           ...
        }
    }
   ...
}
inline void
objc_object::setHasAssociatedObjects()
{
    if (isTaggedPointer()) return;

 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (!newisa.nonpointer  ||  newisa.has_assoc) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.has_assoc = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

看到了吧, 如果有没有在 associations 找到对象,那就调用对象object 的 setHasAssociatedObjects() 函数设置has_assoc 变量为 true.
“如果没有释放更快” 这是因为在释放对象的时候,如果没有关联过,会少执行很多代码

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

看上面的代码,如果对象没有弱引用,没有关联对象,没有c++析构函数,引用计数没有曾经溢出过 ,那就直接释放对象,如果这些标记为 true,那还要执行下面的代码

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}
weakly_referenced

是否有被弱引用指向过,如果没有释放更快
先接着看释放更快这一点,接着上面的代码clearDeallocating()

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }
}

objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

weak_clear_no_lock 函数就是对象被 weak 引用过要执行的函数,将所有引用设置为 nil.

slowpath 和 fastpath 宏的定义如下

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

__builtin_expect起的是优化性能的作用,与 cpu 读取指令有关
__builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。

再说下 weakly_referenced 是在哪里设置的,当某个对象的 weak 属性调用 setter 时,会调用 objc_storeWeak(id *location, id newObj) 函数,然后调用 setWeaklyReferenced_nolock()函数

id objc_storeWeak(id *location, id newObj)
{
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>(location, (objc_object *)newObj);
}
static id storeWeak(id *location, objc_object *newObj){
...
    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
...
}

inline void
objc_object::setWeaklyReferenced_nolock()
{
 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (slowpath(!newisa.nonpointer)) {
        ClearExclusive(&isa.bits);
        sidetable_setWeaklyReferenced_nolock();
        return;
    }
    if (newisa.weakly_referenced) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.weakly_referenced = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}
deallocating

对象是否正在释放,该变量就是对象释放的一个标记,防止释放内存的时候其他地方强引用.
是在rootRelease函数设置的

ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow){
...
  if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
...
}

如果是重复释放,就会崩溃.

has_sidetable_rc

在 64 位环境下,优化的 isa 指针并不是就一定会存储引用计数,毕竟用 19bit (iOS 系统)保存引用计数不一定够。需要注意的是这 19 位保存的是引用计数的值减一。has_sidetable_rc 的值如果为 1,那么引用计数会存储在一个叫 SideTable 的类的属性中

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

rootRetainCount 方法对引用计数存储逻辑进行了判断,如果是 TaggedPointer ,就直接返回本身.TaggedPointer在最后会简单说.
如果has_sidetable_rc 为0 ,就直接返回 extra_rc+1 .
如果has_sidetable_rc 为1 ,就从SideTables 获取当前实例对象的 SideTable 对象,refcnts 属性就是存储引用计数的散列表,以自身为 key 找到 value ,然后右移2位返回.
为啥右移呢,通过 宏 看到,第一个 bit 表示该对象是否有过 weak 对象,如果没有,在析构释放内存时可以更快;第二个 bit 表示该对象是否正在析构。从第三个 bit 开始才是存储引用计数数值的地方.

// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE            (1UL<<2)  // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1))

#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    assert(isa.nonpointer);
    SideTable& table = SideTables()[this];
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}

最后说一下

Tagged Pointer

苹果不允许访问 isa 指针,与 Tagged Pointer 有关系.因为 Tagged Pointer 的情况下,isa 不在是指向一块内存,而是直接表示对象的值,64位其中高4位和低4位是保留位,最高位63位,是标记位,第60、61、62位是类型位.
从源码可以看到,_OBJC_TAG_MASK 就是标记位.

#if TARGET_OS_OSX && __x86_64__
    // 64-bit Mac - tag bit is LSB
#   define OBJC_MSB_TAGGED_POINTERS 0
#else
    // Everything else - tag bit is MSB
#   define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
#   define _OBJC_TAG_MASK (1UL<<63)
#else
#   define _OBJC_TAG_MASK 1UL
#endif
static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr) 
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
inline bool 
objc_object::isTaggedPointer() 
{
    return _objc_isTaggedPointer(this);
}

举个例子

 NSString *a = @"a";
 NSString *b = [[a mutableCopy] copy];
 NSLog(@"%p %p %@", a, b, object_getClass(b));
//0x10a2822a8 0xa000000000000611 NSTaggedPointerString

通过lldb 打印

(lldb) p @1
(__NSCFNumber *) $3 = 0xb000000000000012 (int)1
(lldb) p @2
(__NSCFNumber *) $4 = 0xb000000000000022 (int)2
(lldb) p @0xfffffffffffff
(__NSCFNumber *) $1 = 0xb0fffffffffffff3 
Typebit(60~62)int
NSString0102
NSNumber0113
NSIndexPath1004
NSManagedObjectID1015
NSDate1106
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值