Category为何不能添加成员变量?

关于Category的原理,参考这篇文章:http://tech.meituan.com/DiveIntoCategory.html

Category的使用场景

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

Category到底是不能添加成员变量,还是属性?还是都不能添加?

属性和成员变量的区别,就不介绍了。
首先,Category在代码里添加成员变量是根本编译不过去的,而添加属性是可以编译通过的,如图:


FFA05734-5479-4D2F-AC71-78C86C681EC7.png

但是,在Category添加的属性后,只会声明setter和getter方法,.m文件并未实现setter和getter方法,会有黄色预警,如图:


4F9D5BD7-FB97-49BE-9B35-7FC274A4D062.png

而且,如果调用的话,也不会被赋值,如图:


035F002C-E200-4F7D-BDF1-EFB56C117583.png

最常见的解决方案就是用runtime手动实现setter和getter方法,这里就不介绍了。
所以,刚才那个问题,Category不能添加成员变量和属性?就可以有答案了,不能添加成员变量,可以添加属性,但是属性要手动实现setter和getter方法。

Category为何不能添加成员变量,而只能添加方法?

这要从Category的原理说起,简单地说就是通过runtime动态地把Category中的方法等添加到类中(苹果在实现的过程中并未将属性添加到类中,所以属性仅仅是声明了setter和getter方法,而并未实现),具体请参考http://tech.meituan.com/DiveIntoCategory.html
Objective-C提供的runtime函数中,确实有一个lass_addIvar()
函数用于给类添加成员变量,但是文档中特别说明:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair
之后才可以被使用,同样没有机会再添加成员变量。
我们设想一下如果Objective-C允许动态增加成员变量,会发生什么事情。假设如下代码可以执行。

E6AD8A2E-77C0-4186-AFC0-26C8451F6571.png
MyObject *obj = [[MyObject alloc] init];
// 基类增加一个4字节的成员变量someVar
class_addIvar([NSObject class], "someVar", 4, ...);
// 基类增加方法someMethod,用到了someVar
class_addMethod([NSObject class], @selector(someMethod), ...);
// 调用someMethod,修改了someVar
[obj someMethod];
// 访问子类成员变量,会发生什么?
[obj->students length];

显然,这样做会带来严重问题,为基类动态增加成员变量会导致所有已创建出的子类实例都无法使用,比如上线后的app,如果用户手机系统升级iOS新版本后,必须重新编译提交才能在新版系统上运行。那为什么runtime允许动态添加方法和属性,而不会引发问题呢?

因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。

需要注意的有两点:

  • 1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
  • 2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

参考:http://quotation.github.io/objc/2015/05/21/objc-runtime-ivar-access.html

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    萌萌的小伟哥阅读 922评论 0 9
  • 今天朋友问我:我真佩服你,在一家公司这么长时间。 “你过来你也会这样的,因为我们都有好奇心” 我13年毕业后进公司...
    呀呀丙阅读 127评论 0 1
  • 夸人,讲故事,提问都要注意细节,具体到某个部分,不要太笼统。
    kwfs笔记阅读 43评论 0 0