问题:Object-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方法用继承好还是分类好?为什么?
答案:OC不可以多重继承,但可以用protocol的形式实现多个接口。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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
可见category中有实例方法列表、类方法列表、属性列表、还有类属性列表(没见过),甚至可以有协议列表,就是没有成员变量,说明是不能给分类加成员变量的。这点也是与extension不同的一点,还有就是extension是在编译期就把代码与主类合并在一起的,而category中的方法是在运行时才“合并”的。
看下编译期
clang一个片段分析
static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClass",
0, // &OBJC_CLASS_$_MyClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
可见category的方法和类,都被转化成为static的函数和struct,这就说明了另一个问题:同一编译环境下,分类是不能重名的,同一分类中的方法也是不能重名的。
通过这个片段也能看到另一个点:编译后,分类是会被放在__DATA段,__objc_const.
运行时
分类在运行时的加载,是在runtime库一启动就做了的。
void _objc_init(void)
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
category的加载是在map_images中做的,map_images最后走到objc-runtime-new.mm里面的_read_images,_read_images从方法名就可以看出来是在加载各种镜像文件,什么是镜像文件呢?
1、Executable: 应用的主要二进制(比如.o文件)
2、Dylib:dynamic library,又称 DSO 或 DLL)
3、Bundle: 资源文件,不能被链接的 Dylib,只能在运行时使用 dlopen() 加载
category明显属于executeble中的.o文件。
找到相应代码
/ Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
class_t *cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = NULL;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
BOOL classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (isRealized(cls)) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
getName(cls), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->isa, hi);
if (isRealized(cls->isa)) {
remethodizeClass(cls->isa);
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
getName(cls), cat->name);
}
}
}
}
这段代码的主干逻辑就是
- 把category的实例方法、协议以及属性添加到类上
- 把category的类方法和协议添加到类的metaclass上
通过阅读后续方法栈,可以自己去追,就不贴代码了。
addUnattachedCategoryForClass
remethodizeClass
attachCategoryMethods
attachMethodLists
可以得知:
-
category的方法没有替换原来类已经有的方法,也就是说如果category和原类都有methodA,attach之后,类的方法列表里会有两个methodA
-
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就返回了,其实后面可能还有一样名字的方法。
方法的列表决定于类的列表。类的加载列表是由文件顺序装载的。也就是我们最说的build phases -> compile sources中的文件顺序。所以,如果同一项目中,同一个类存在同名分类函数,是可能会出现意料不到效果的,你可能调用了别人的函数。所以,起名字前要注意命名空间。
关联对象
最后说下关联对象
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
"name",
name,
OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
关联对象有两个个点:1、如何存储 2、怎么销毁
1、存储于全局AssociationsManager中的一个静态的AssociationsHashMap中,结构为(示意图):
{
'key' : 对象地址
'value': {
'name1' : 'value1',
'name2' : 'value2',
...
}
}
也就是说每一个对象的关联属性都是独立存在的,颗粒度是object,不是class.
2、销毁
在objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
// Read all of the flags at once for performance.
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}













网友评论