深入理解isa指针

深入理解isa指针

实例对象的isa指针指对象的类,类对象的isa指针指向对象的元类,这种说法对吗?

对,但也不完全对。因为在arm64架构之前,isa确实只存储着Class、Meta-Class对象的内存地址,但是在之后苹果对isa进行了优化,变成了一个共用体结构,使其能用位域存储更多的信息。而指向Class的指针需要按位与个ISA_MASK才可以获取到。

上图是一张很经典的指针指向图,虽然已经不完全正确,但是还是可以作为参考的。

上一篇文章提到,isa指针被优化成union isa_t类型,源码中搜索可以在objc-private.h文件中找到定义,去除方法简化后的代码如下

union isa_t {
    uintptr_t bits;
private:
    Class cls;
public:
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
};

然后再isa.h文件里面找到ISA_BITFIELD的定义,忽略x86和模拟器,把定义替换到以上代码中,可以变成如下

union isa_t {
    uintptr_t bits;
private:
    Class cls;
public:
    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 unused            : 1;
        uintptr_t has_sidetable_rc  : 1; 
        uintptr_t extra_rc          : 19
    };
};

那这些结构体里面的东西都表示什么意思呢?可以在源码里面搜索找下答案,我的源码版本是objc4-818.2,不同版本可能不太一样。

搜索isa.nonpointer; 会找到一个 hasNonpointerIsa()方法里面使用了,然后继续搜索hasNonpointerIsa(),会发现在objc-object.h文件里面有两个地方定义了这个函数,为什么会有两个呢,不会冲突吗?所以一定有条件编译指令区分编译。继续查找代码,会发现以下内容(简化代码)

#if SUPPORT_NONPOINTER_ISA  //line 172
inline bool 
objc_object::hasNonpointerIsa()
{
    return isa.nonpointer;
}
#else //line 952
inline bool 
objc_object::hasNonpointerIsa()
{
    return false;
}
#endif //1217

对比下很明显可以看出,在SUPPORT_NONPOINTER_ISA部分,返回值应该为true,也就是当nonpointer为1时,表示优化过的指针。

搜索isa.has_assoc; 找到个hasAssociatedObjects()方法被调用,然后搜索has_assoc = 找到setHasAssociatedObjects()方法中被设置为true,但是并没有设置为false的地方。所以当这个值为1的时候,表示被设置过关联对象。

搜索isa.has_cxx_dtor; 找到hasCxxDtor(),然后搜索hasCxxDtor()方法在objc-private.h文件中找到以下注释

// object may have -.cxx_destruct implementation?
bool hasCxxDtor();

可以看出,这个值表示对象是否有C++的析构函数。苹果标识这些的作用是什么呢?

搜索hasCxxDtor();方法,发现objc-runtime-new.mm文件中如下代码

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, /*deallocating*/true);
        obj->clearDeallocating();
    }

    return obj;
}

可以看出,在对象释放的时候,用于判断是否有关联对象和C++的析构函数,如果没有的话,可以更快的释放对象。

搜索shiftcls,并没有找到有用的信息。不过看名字猜测一定和类有关系,如果获取类指针的话,需要在现有的isa指针上按位与ISA_MASK,查看下ISA_MASK的值为0x0000000ffffffff8ULL,复制粘贴到计算器如图

看到值为1的位的长度正好和shiftcls的长度对应上,按照按位与的逻辑,保留值为1对应部分的值。可以确定shiftcls 存储的Class、Meta-Class对象的内存地址信息。

源码中搜索magic,并没有找到太多有用的信息,通过查询资料,得知这个值是为了在调试的时候分辨对象是否未完成初始化。

weakly_referenced 从字面意思就能看出,这个标识是否有弱引用。相同方法搜索得如下代码

// object may be weakly referenced?
bool isWeaklyReferenced();

搜索extra_rc; 会找到如下代码

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

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

可以看到,extra_rc存储值为对象的引用计数,同时可以看到当has_sidetable_rc为1的时候,还需要加上另一个值才能得到准确的引用计数。

什么时候才会把引用计数写到sidetable中呢。搜索has_sidetable_rc = true 得到如下代码

    if (slowpath(carry)) {
        // newisa.extra_rc++ overflowed
        if (variant != RRVariant::Full) {
            ClearExclusive(&isa.bits);
            return rootRetain_overflow(tryRetain);
        }
        // Leave half of the retain counts inline and
        // prepare to copy the other half to the side table.
        if (!tryRetain && !sideTableLocked) sidetable_lock();
        sideTableLocked = true;
        transcribeToSideTable = true;
        newisa.extra_rc = RC_HALF;
        newisa.has_sidetable_rc = true;
    }

发现newisa.extra_rc++ overflowed,当引用计数过大无法存入到extra_rc,会把引用计数写入到sidetable里面,并把has_sidetable_rc设置为true,extra_rc的值设置为RC_HALF,查看宏定义的值define RC_HALF  (1ULL<<18),可得extra_rc最大存储引用计数为2^18 - 1 = 262143。

通过以上内容可以看出,优化后的指针可以存储很多内容,并且可以更有效率的获取存储的内容。

Read more

Category和关联对象

Category和关联对象

Category也就是分类,可以在没有类定义的情况下为现有类添加方法、属性、协议,众所周知是不能增加成员变量的,但是是可以通过关联对象给类增加的。 struct category_t { const char *name; classref_t cls; WrappedPtr instanceMethods; WrappedPtr classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsF

By LEMON
内存管理-AutoreleasePool

内存管理-AutoreleasePool

AutoreleasePool(自动释放池)是Objective-C中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。 @autoreleasepool { ... }可以通过 clang -rewrite-objc 命令转成C++代码如下 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; } 然后可以从objc源码中找到__AtAutoreleasePool的定义 struct __AtAutoreleasePool { __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; }; 这个结构体先

By LEMON
如何为macOS应用开发插件

如何为macOS应用开发插件

本文章只用于学习和交流,转载请注明出处。 玩过越狱手机的应该都了解,iOS的越狱插件是可以为系统或者应用添加一些特色的功能的。越狱插件的开发,已经有完整的工具链,并且插件的加载运行Substrate框架已经为我们做好。 更改应用的功能实现有两种方法,一种是通过反编译工具更改汇编代码后生成新的可执行文件。另一种是写一个动态库,然后注入到可执行文件。第一种适合简单的逻辑修改,毕竟用汇编实现功能是需要很大的工作量的。 第二种要怎么注入呢?了解Mach-O 二进制文件的应该知道,Mach-O 二进制文件Load Commands中的 LC_LOAD_DYLIB 标头告诉 macOS在执行期间要加载哪些动态库 (dylib)。所以我们只需要在二进制文件中添加一条LC_LOAD_DYLIB就可以。而insert_dylib工具已经为我们实现了添加的功能。所以现在唯一需要考虑的就是插件如何开发。 废话不多说,现在就以mac版的迅雷为例开发一款属于我们自己的插件。首先下载最新版本的迅雷(版本5.0.2)。然后用Xcode新建一个macOS下的Framework,如下 新建完目录结构如下,如果

By LEMON
深入分析objc_msgSend尾调用优化

深入分析objc_msgSend尾调用优化

无意间看到了这篇文章 iOS objc_msgSend尾调用优化详解,讲的是在Release模式下,当某函数的最后一项操作是调用另外一个函数,编译器会生成调转至另一函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”(frame stack),以实现栈帧复用。 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。所以在每一次函数调用之前,需要保存下执行的环境,以确保正常返回到调用位置。下面是- (void)viewDidLoad方法的汇编代码 可以看出在调用[super viewDidLoad]之前,先申请栈空间,然后保存x29和x30寄存器的值,调用结束后恢复寄存器的值,然后平栈。x29寄存器也就是fp寄存器,保存栈底的地址。x30寄存器也就是lr寄存器,保存返回地址。那栈帧复用又是怎么实现的呢?本着知其然知其所以然的精神,深入研究下。用下文章中的例子 - (NSInteger)func1:(NSInteger)num{ if (num >= 2000000) { return num; } return

By LEMON
京ICP备15024336号-4