Objective-C类结构分析(class_rw_t)

Objective-C类结构分析(class_rw_t)
Photo by Joshua Sortino / Unsplash

上一篇分析了cache_t的结构和怎么缓存方法的,这里继续分析objc_class中的class_data_bits_t,搜索可以找到主要结构如下

struct class_data_bits_t {
    friend objc_class;
    uintptr_t bits;

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

    bool isAnySwift() {
        return isSwiftStable() || isSwiftLegacy();
    }
};

可以看出,结构体里面只有一个bits,然后主要方法就是获取class_rw_t,和判断是不是Swift的类。

看标题就知道,这篇文章的重点就是class_rw_t,源码中搜索找到结构体定义

struct class_rw_t {
    uint32_t flags; //标记位
    uint16_t witness; //存放cls地址 缓存range的索引
    
    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass; //第一个子类
    Class nextSiblingClass; //兄弟类
}

这里主要关注ro_or_rw_ext,使用的时候,会通过联合指针转换成ro_or_rw_ext_t

using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

const ro_or_rw_ext_t get_ro_or_rwe() const {
    return ro_or_rw_ext_t{ro_or_rw_ext};
}

void set_ro_or_rwe(const class_ro_t *ro) {
    ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}

void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
    // the release barrier is so that the class_rw_ext_t::ro initialization
    // is visible to lockless readers
    rwe->ro = ro;
    ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
}

这个里面可能存储着const class_ro_t或者class_rw_ext_t,但只有class_ro_t的时候,就存储class_ro_t,当两者同时存在的时候,就存储class_rw_ext_t。也就是需要的时候才会加载class_rw_ext_t进行分配内存。

class_rw_ext_t *extAllocIfNeeded() {
    auto v = get_ro_or_rwe();
    if (fastpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
    } else {
        return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
    }
}

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        rwe->methods.attachLists(&list, 1);
    }

    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    set_ro_or_rwe(rwe, ro);
    return rwe;
}

搜索extAllocIfNeeded,会发现在以下方法中调用

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)

BOOL class_addProtocol(Class cls, Protocol *protocol_gen)

static bool 
_class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attrs, unsigned int count, bool replace)

可以看出,当类的方法、协议、属性,除类本身外有额外添加的时候就会加载class_rw_ext_t分配内存。

class_rw_ext_t 结构如下

struct class_rw_ext_t {
    DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
    class_ro_t_authed_ptr<const class_ro_t> ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};

这里面存储着,*_array_t类型的方法、属性、协议。

char *demangledName;,可以找到如下方法,应该是Swift类OC化的类名。

const char *
protocol_t::demangledName() 
{
    if (!hasDemangledNameField())
        return mangledName;
    
    if (! _demangledName) {
        char *de = copySwiftV1DemangledName(mangledName, true/*isProtocol*/);
        if (! OSAtomicCompareAndSwapPtrBarrier(nil, (void*)(de ?: mangledName), 
                                               (void**)&_demangledName)) 
        {
            if (de) free(de);
        }
    }
    return _demangledName;
}

version应该是版本,这里如果是7的话当前类为元类,0的话为普通类

rwe->version = (ro->flags & RO_META) ? 7 : 0;

class_ro_t结构如下

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

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
#if __has_feature(ptrauth_calls)
        method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
        if (ptr == nullptr)
            return nullptr;

        // Don't auth if the class_ro and the method list are both in the shared cache.
        // This is secure since they'll be read-only, and this allows the shared cache
        // to cut down on the number of signed pointers it has.
        bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
        bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
        if (roInSharedCache && listInSharedCache)
            return ptr;

        // Auth all other small lists.
        if (ptr->isSmallList())
            ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
                                    ptrauth_key_method_list_pointer,
                                    ptrauth_blend_discriminator(&baseMethodList,
                                                                methodListPointerDiscriminator));
        return ptr;
#else
        return (method_list_t *)baseMethodList;
#endif
    }
};

这里面主要存储着类的信息,包含大小、类名、成员变量、属性列表、方法列表、协议等信息。还有就是class_ro_t是只读的,存储的信息在编译时期就已经确定了,不能再更改,并且必要的时候可以清除,需要用的时候重磁盘中加载就好了。

class_rw_t 是可读可写的,它信息的存储是在运行的时候,需要用到的时候才会写入,并且一直存在于内存中。因为class_rw_ext_t没有成员变量列表,这就是为什么分类中不能添加成员变量的原因。

继续看下 *_array_t 和 *_list_t,查找代码可以找到如下代码

class *_array_t : public list_array_tt<*_t, *_list_t, *_list_t_authed_ptr>

*_array_t 继承于list_array_tt,里面存放着*_list_t。相当于一个二维数组,结构如下

class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
}

*_list_t里面存储着各自的*_t的对象,分别包含各自的信息,列如method_t(简化)

struct method_t {
    //…
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    //…
}

其他更多请查看源码,这里就不贴代码了。

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