iOS-类的加载(二)

201 阅读5分钟

类的加载(一)这里主要聊的是类的加载,这篇文章聊下分类的加载

问题先行

1、分类中为什么添加的属性不能自动生成setget方法?
2、类和分类的加载可以分为几种情况?

资源准备

1、objc源码下载opensource.apple.com/

分类

分类的本质

通过底层看本质,我们需要把源代码编程成C++代码来分析

@interface ASSon (ext)

@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)NSInteger age;
- (void)instanceMethod1;
+ (void)instanceMethod2;

@end

@implementation ASSon (ext)

- (void)instanceMethod1 {}
+ (void)instanceMethod2 {}

@end

兼容编译(代码少)clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp
完成编译(不报错)xcrun -sdk iphonesimulator clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp

C++分析

通过clang编译指令,会生成对应的cpp文件,我们从下向上分析app文件(可用信息主要在下方)

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_ASSon_$_ext,
};

可以看出分类是存储在MachO文件的__DATA段的__objc_catlist

static struct _category_t _OBJC_$_CATEGORY_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"ASSon",
	0, // &OBJC_CLASS_$_ASSon,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_ASSon_$_ext,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_ASSon_$_ext,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ASSon_$_ext,
};
static void OBJC_CATEGORY_SETUP_$_ASSon_$_ext(void ) {
	_OBJC_$_CATEGORY_ASSon_$_ext.cls = &OBJC_CLASS_$_ASSon;
}

可以看出分类的结构体类型为_category_t,进而搜索_category_t可以看出其是个结构体如下

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};
  • 这个结构体里面有类名字cls对象方法列表类方法列表协议列表属性列表,但是没有变量列表,这也是分类属性没有setget方法的原因。
  • 分类的实例方法和类方法是分开在两个_method_list_t中的,是因为分类是没有元分类的,分类的方法是在运行时通过attachToClass插入到class的。
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"instanceMethod1", "v16@0:8", (void *)_I_ASSon_ext_instanceMethod1}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"instanceMethod2", "v16@0:8", (void *)_C_ASSon_ext_instanceMethod2}}
};

static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_ASSon_$_ext __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	2,
	{{"name","T@\"NSString\",C,N"},
	{"age","Tq,N"}}
};

有一个对象方法和一个类方法,格式为:sel+签名+地址,和method_t结构体一样。我们发现存在属性的变量名,但是没有相应的setget方法。

总结

  • 分类底层的结构体类型是_category_t
  • 分类可以添加方法和属性,不能添加成员变量
  • 分类添加的属性没有setget方法实现
  • 分类有两个方法列表,表明分类是没有元分类来进行存储方法的

分类的加载

加载时机分析

类的加载(一)中提及过分类的加载路径realizeClassWithoutSwift->methodizeClass,继续进行路径追踪methodizeClass->attachToClass->attachCategories,发现attachCategories才是分类加载的核心方法。

其中attachToClass方法主要是将分类添加到主类中

void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        /// 找到一个分类进来一次,即一个个加载分类
        auto it = map.find(previously);
        /// 这里会走进来,当主类没有实现load方法,分类开始加载,迫使主类开始加载
        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

attachCategories方法为初始化rwerwe的初始化主要涉及:addMethodaddPropertyaddprotocol,即对原始类进行修改或者处理时,才会进行rwe的初始化

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    /// 代码省略......

    /// 分类的最大个数为64
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    /// rwe的初始化(为主类中添加属性,方法,协议等做准备)
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        /// 方法处理
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        /// 属性处理
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        /// 协议处理
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

attachLists分类数据添加到主类

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<List> oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

总结:分类的加载主要分为3步

  • 分类数据加载时机:根据类和分类是否实现load方法来区分不同的时机
  • attachCategories准备分类数据
  • attachLists分类数据添加到主类

加载流程分析

通过全局搜索attachCategories只有两个地方有调用,attachToClassload_categories_nolock
在全局搜索load_categories_nolock只有两个地方调用loadAllCategories_read_imagesloadAllCategories只有一个地方调用load_images
在全局搜索attachToClass只有一个地方调用methodizeClass
通过上面分析,也知道类和分类的加载时机都和load方法实现有关。因此可以进行类和分类的四种情况搭配分析。 image.png 非懒加载类与非懒加载分类
调用方法路径

  • 类的加载map_images->map_images_nolock->_read_images->readClass-> _getObjc2NonlazyClassList->realizeClassWithoutSwift->methodizeClass
  • 分类的加载load_images->loadAllCategories->load_categories_nolock->attachCategories->attachLists

非懒加载类与懒加载分类
调用方法路径

  • 类的加载map_images->map_images_nolock->_read_images->readClass-> _getObjc2NonlazyClassList->realizeClassWithoutSwift->methodizeClass
  • 分类的加载map_images->map_images_nolock->_read_images->readClass-> _getObjc2NonlazyClassList->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories->attachLists

懒加载类与懒加载分类
调用方法路径

  • 类的加载lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift->methodizeClass
  • 分类的加载lookUpImpOrForward->realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories->attachLists

懒加载类与非懒加载分类
调用方法路径

  • 类的加载map_images->map_images_nolock->_read_images->readClass-> _getObjc2NonlazyClassList->realizeClassWithoutSwift->methodizeClass
  • 分类的加载load_images->loadAllCategories->load_categories_nolock->attachCategories->attachLists

通过以上条用路径分析可以得出如下结论:

  • 非懒加载类+非懒加载分类:类的加载在_read_images处,分类的加载在load_images方法中,首先对类进行加载,然后把分类的信息加入到类中
  • 非懒加载类+懒加载分类:类的加载在_read_images处,分类的加载则在编译时
  • 懒加载类+懒加载分类:类的加载在第一次消息发送的时候,分类的加载则在编译时
  • 懒加载类+非懒加载分类:只要分类实现了load,会迫使主类提前加载,即和第一种情况一致

image.png

问题先行解答

1、分类中为什么添加的属性不能自动生成setget方法?
通过_category_t结构体可以发现,其结构体内并没有成员变量列表,这也就是为什么添加的属性不能自动生成setget方法。
2、类和分类的加载可以分为几种情况?
见如上分类的加载->加载流程分析