深入理解Objective-C:Category(上)

来源:互联网 发布:淘宝小号信誉查询 编辑:程序博客网 时间:2024/04/30 09:10

摘要


无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在Objective-C 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落,从Apple官方的framework到各个开源框架,从功能繁复的大型APP到简单的应用,catagory无处不在。本文对category做了比较全面的整理,希望对读者有所裨益。


简介


本文作者来自美团酒店旅游事业群iOS研发组。我们致力于创造价值、提升效率、追求卓越。欢迎大家加入我们(简历请发送到邮箱majia03@meituan.com)。

本文系学习Objective-C的runtime源码时整理所成,主要剖析了category在runtime层的实现原理以及和category相关的方方面面,内容包括:


  • 初入宝地-category简介

  • 连类比事-category和extension

  • 挑灯细览-category真面目

  • 追本溯源-category如何加载

  • 旁枝末叶-category和+load方法

  • 触类旁通-category和方法覆盖

  • 更上一层-category和关联对象


1、初入宝地-category简介


category是Objective-C 2.0之后添加的语言特性,category的主要作用是为已经存在的类添加方法。除此之外,apple还推荐了category的另外两个使用场景1


  • 可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处,a)可以减少单个文件的体积 b)可以把不同的功能组织到不同的category里 c)可以由多个开发者共同完成一个类 d)可以按需加载想要的category 等等。

  • 声明私有方法


不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景:


  • 模拟多继承

  • 把framework的私有方法公开


Objective-C的这个语言特性对于纯动态语言来说可能不算什么,比如javascript,你可以随时为一个“类”或者对象添加任意方法和实例变量。但是对于不是那么“动态”的语言而言,这确实是一个了不起的特性。


2、连类比事-category和extension


extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。(详见2)


但是category则完全不一样,它是在运行期决议的。

就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。


3、挑灯细览-category真面目


我们知道,所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了


1)、类的名字(name)

2)、类(cls)

3)、category中所有给类添加的实例方法的列表(instanceMethods)

4)、category中所有添加的类方法的列表(classMethods)

5)、category实现的所有协议的列表(protocols)

6)、category中添加的所有属性(instanceProperties)


typedef struct category_t {

    const char *name;

    classref_t cls;

    struct method_list_t *instanceMethods;

    struct method_list_t *classMethods;

    struct protocol_list_t *protocols;

    struct property_list_t *instanceProperties;

} category_t;


从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。

ok,我们先去写一个category看一下category到底为何物:


MyClass.h:


#import <Foundation/Foundation.h>

 

@interface MyClass : NSObject

 

(void)printName;

 

@end

 

@interface MyClass(MyAddition)

 

@property(nonatomic, copy) NSString *name;

 

(void)printName;

 

@end


MyClass.m:


#import "MyClass.h"

 

@implementation MyClass

 

(void)printName

{

    NSLog(@"%@",@"MyClass");

}

 

@end

 

@implementation MyClass(MyAddition)

 

(void)printName

{

    NSLog(@"%@",@"MyAddition");

}

 

@end



我们使用clang的命令去看看category到底会变成什么:


clang -rewrite-objc MyClass.m


好吧,我们得到了一个3M大小,10w多行的.cpp文件(这绝对是Apple值得吐槽的一点),我们忽略掉所有和我们无关的东西,在文件的最后,我们找到了如下代码片段:


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_MyClass_$_MyAddition __attribute__ ((used, section("__DATA,__objc_const"))) = {

sizeof(_objc_method),

1,

{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}}

};

 

static struct /*_prop_list_t*/ {

unsigned int entsize;  // sizeof(struct _prop_t)

unsigned int count_of_properties;

struct _prop_t prop_list[1];

} _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const")))

0 0
原创粉丝点击