iOS学习篇-谈谈个人对Category的理解

废话不多说,直接进入主题。。。
下面我们由几个问题展开:
1、什么是Category?
2、Category有什么特点?
3、Category中能添加哪些内容?

什么是Category?

Category(分类)是在Objective-C 2.0之后出现的,主要用于给现有类添加方法,以便对原有类的一个补充和完善,因为我们任何类都不是天生完美的。我们看下苹果的官方文档是如何解释的:

You use categories to define additional methods of an existing class—even one whose source code is unavailable to you—without subclassing. You typically use a category to add methods to an existing class, such as one defined in the Cocoa frameworks. The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class. You can also use categories of your own classes to:

  • Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.
  • Declare private methods.

总结一下就是:
1、分解体积庞大的自定义类文件
2、声明私有方法

Category有什么特点?

我们先看这句解释

The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class.

可知:
1、分类是在运行时期决议的。
2、分类中添加的方法子类是可继承的。

那么除此之外分类还可以添加什么呢?

Category中可以添加哪些内容?

我们先看下苹果源码的实现:

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;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) return nil; // classProperties;
        else return instanceProperties;
    }
};

在category_t的结构体中我们可以看到如下:
name:类名
cls:类
instanceMethods:实例方法列表
classMethods:类方法列表
protocols:协议列表
instanceProperties:属性列表

现在就很明确了:

分类中可以添加:实例方法、类方法、协议、属性

所以也由此可知:category是不能添加成员变量的,从源代码层面上解释就是category_t的结构体里面没有ivars成员变量列表。

那么,问题又来了。。。。。有人该会问啦,上面不是说可以添加属性么,按照正常情况来说,我在类文件里面使用@property声明一个属性,编译器就会自动生成一个带下划线的成员变量呀,这样不就是能添加成员变量嘛,NONONO,在category里面这样的想法是不对的,下面我解释一下原因:

第一、在分类里使用@property声明属性,只是将该属性添加到该类的属性列表(instanceProperties),声明的getter、setter方法添加到实例方法列表,但是不会生成相应的成员变量,也没有实现setter、getter方法。
第二、前面有讲到category是在运行时期决议的,因为在运行期也就是编译完成之后,对象的内存布局已经确定,如果添加实例变量就需要重新内存对齐,就会破坏类的内部布局,这对编译型语言来说是不可取的。

这里说明一点,其实有很多开发者包括我自己以前都会误认为category是不能添加属性的,其实这种理解是错误的,属性是可以添加的,在category中@property声明一个属性编译器并不会报错,但是在运行时期调用setter、getter方法的时候就会报错,错误原因是找不到该方法,所以也证明了category能添加属性,但是不能添加成员变量。

但是我们在开发过程中需要往分类中添加成员变量怎么办呢?
这个问题其实大家都知道改如何解决,那就是runtime的关联对象,说到这,可能有人又会有疑问啦,既然说category中添加成员变量会遇到内存对齐问题,为什么通过runtime就可以添加呢,所以请带着疑问继续往下继续看:

先看一下关联对象的实现源码

void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

通过源代码我们可以看见,关联对象是由AssociationsManager进行管理,AssociationsManager结构体里面是由一个静态AssociationsHashMap来存储所有的关联对象的,
disguised_ptr_t disguised_object = DISGUISE(object);这里是获取要关联属性的对象的指针地址,作为map的key,value对应的是ObjectAssociationMap,看下ObjectAssociationMap里面都有什么

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> 

其中void * 就是我们在objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) 中传入的key,
然后看下ObjcAssociation

 class ObjcAssociation {
        uintptr_t _policy;
        id _value;
};

这里面的value就是我们关联的对象成员变量的值。

到这里,关联对象的解释基本就结束了,所以看得出来,通过关联对象给category添加成员变量由于是存储在一个全局的AssociationsManager里面,所以并不会对现有类的内存造成影响。

objc_getAssociatedObject_gc就不做详细介绍了,大家可以静下心来阅读苹果的源代码。

注: 本文章解释的比较浅显,也易懂,如有解释不当的地方欢迎留言交流,谢谢!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容