老青菜

iOS Extension 底层分析

2017-08-12

Class Extension 类扩展,又叫匿名分类。主要作用就是给类添加变量、属性、方法。

简单使用

假如我们有一个 KDClangGrandsonTest 类,需要公开某个只读属性,而且可以正常读写,或者内部需要申明一个属性。我们通常会这样写:
1.KDClangGrandsonTest.h 文件

@interface KDClangGrandsonTest : NSObject
/** 年龄,只读 */
@property (nonatomic, assign, readonly) NSInteger   age;
@end

2.KDClangGrandsonTest.m 文件

@interface KDClangGrandsonTest()
/** 昵称,内部使用 */
@property (nonatomic, strong) NSString *nickname;
/** 年龄 */
@property (nonatomic, assign, readwrite) NSInteger   age;
@end

@implementation KDClangGrandsonTest
{    
    NSString    *_sex;
}
@end

底层结构

我们知道 Category 有以下特点:

  1. 有自己的名字,有单独的类文件。
  2. 可以声明属性,可以添加方法(以及方法的实现),不能添加变量(ivar)。
  3. 在程序启动,runtime loading(_read_images)的时候,才会把属性和方法添加到主类里。

Extension 不一样:

  1. 没有名字,没有单独的类文件。
  2. 可以增加变量、属性,也可以声明方法。
  3. 编译期间就会把这些变量、属性、方法添加到主类里。

为什么说是在编译期间呢?我们在 terminal 里执行命令:

#普通文件
clang -rewrite-objc KDClangGrandsonTest.m

#针对包含UIKit的文件
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m

打开编译后的 KDClangGrandsonTest.cpp 文件,找到几行关键代码:

1.class_ro_t 结构体,存储编译期就已经确定的属性、方法以及遵循的协议。

struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};

2._ivar_list_t 结构体,存储变量,这里存储了_sex_age_nickname

static struct /*_ivar_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[3];
} _OBJC_$_INSTANCE_VARIABLES_KDClangGrandsonTest __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),
    3,
    {{(unsigned long int *)&OBJC_IVAR_$_KDClangGrandsonTest$_sex, "_sex", "@\"NSString\"", 3, 8},
	 {(unsigned long int *)&OBJC_IVAR_$_KDClangGrandsonTest$_age, "_age", "q", 3, 8},
	 {(unsigned long int *)&OBJC_IVAR_$_KDClangGrandsonTest$_nickname, "_nickname", "@\"NSString\"", 3, 8}}
};

3._method_list_t 结构体,存储编译期间需要确定的方法列表。

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[6];
} _OBJC_$_INSTANCE_METHODS_KDClangGrandsonTest __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    6,
    {{(struct objc_selector *)"init", "@16@0:8", (void *)_I_KDClangGrandsonTest_init},
	{(struct objc_selector *)"instanceMethodGrandson1", "v16@0:8", (void *)_I_KDClangGrandsonTest_instanceMethodGrandson1},
	{(struct objc_selector *)"age", "q16@0:8", (void *)_I_KDClangGrandsonTest_age},
	{(struct objc_selector *)"setAge:", "v24@0:8q16", (void *)_I_KDClangGrandsonTest_setAge_},
	{(struct objc_selector *)"nickname", "@16@0:8", (void *)_I_KDClangGrandsonTest_nickname},
	{(struct objc_selector *)"setNickname:", "v24@0:8@16", (void *)_I_KDClangGrandsonTest_setNickname_}}
};

4.初始化 _class_ro_t 结构体,传入了 INSTANCE_VARIABLES_XXXINSTANCE_METHODS_XXX,确定了编译期间的 ivarsmethod,当然还有 PROP_LIST,这里就不列出来了。

static struct _class_ro_t _OBJC_CLASS_RO_$_KDClangGrandsonTest __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, __OFFSETOFIVAR__(struct KDClangGrandsonTest, _sex), sizeof(struct KDClangGrandsonTest_IMPL), 
    (unsigned int)0, 
    0, 
    "KDClangGrandsonTest",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_KDClangGrandsonTest,
    0, 
    (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_KDClangGrandsonTest,
    0, 
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_KDClangGrandsonTest,
};

最后我们得出结论,Class Extension 虽然叫做匿名分类,但是是在编译期间就确定了成员变量、属性、方法、协议,并存储在 _class_ro_t 里。所以不用担心给 runtime loading 造成负担。

参考链接

巧用 Class Extension 分离接口依赖

Tags: objc
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章