1.Category是什么?重写一个类的方法用继承好还是分类好?
Category是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
category好处:
可以减少单个文件的体积
可以把不同的功能组织到不同的category里
可以由多个开发者共同完成一个类
可以按需加载想要的category
声明私有方法
category特点
category只能给某个已有的类扩充方法,不能扩充成员变量。
category中也可以添加属性,只不过@property只会生成setter和getter的声明,不会生成setter和getter的实现以及成员变量。
如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_。
如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。
2.Category的实现原理或者本质 ?
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据合并到类信息中(类对象、元类对象中)
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
3.Category的加载处理过程是什么?
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
可以理解为:
把 category 的实例方法、协议以及属性添加到类上。
把 category 的类方法和协议添加到类的 metaclass 上。
其中需要注意的是:
category 的方法没有「完全替换掉」原来类已经有的方法,也就是说如果 category 和原来类都有 methodA,那么 category 附加完成之后,类的方法列表里会有两个 methodA。
category 的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category 的方法会「覆盖」掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会返回,不会管后面可能还有一样名字的方法。
4.Category为什么不能添加成员变量?
Category 可以给类增加方法和属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
在 Objective-C 提供的 runtime 函数中,确实有一个 class_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.
大概的意思说,这个函数只能在“构建一个类的过程中”调用。当编译类的时候,编译器生成了一个实例变量内存布局 ivar layout,来告诉运行时去那里访问类的实例变量们,一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被 runtime 加载,没有机会调用 addIvar。程序在运行时动态构建的类需要在调用 objc_registerClassPair 之后才可以被使用,同样没有机会再添加成员变量。
不能为一个类动态的添加成员变量,可以给类动态增加方法和属性。因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。
5.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
在类和 category 中都可以有 +load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方法.
6.在类的 +load方法调用的时候,我们可以调用 category 中声明的方法么?
可以调用,因为附加 category 到类的工作会先于 +load方法的执行。
7.这么些个+load方法,调用顺序是咋样的呢?
+load的执行顺序是先类,后 category,而 category 的+load 执行顺序是根据编译顺序决定的。虽然对于 +load的执行顺序是这样,但是对于「覆盖」掉的方法,则会先找到最后一个编译的 category 里的对应方法。
子类的+load方法会在它的所有父类的+load方法之后执行,而分类的+load方法会在它的主类的+load方法之后执行,但是不同的类之间的+load方法的调用顺序是不确定的,在Compile Sources中,文件的引入的先后顺序决定不同的类之间的+load方法的调用顺序。
注意:+load方法调用时,如果调用别的类的方法,且该方法依赖于那个类的load方法进行初始化设置,那么必须确保那个类的+load方法已经调用了,比如在Other类中调用Child类里面的方法,则打印出的字符串就为null。
8.怎么调用到原来类中被 category 覆盖掉的方法?
对于这个问题,我们已经知道 category 其实并不是完全替换掉原来类的同名方法,只是 category 在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法
9.load、initialize的区别,以及它们在category重写的时候的调用的次序。
区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:
先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。
initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。
参考:http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/
10.category是如何为一个类添加属性的
利用关联对象
#import
"MyClass.h"
@interface MyClass (Category1)
@property(nonatomic,copy)NSString*name;
@end
#import "MyClass+Category1.h"
#import <objc/runtime.h>
@implementation MyClass (Category1)
+ (void)load
{
NSLog(@"%@",@"load in Category1");
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
"name",
name,
OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
@end
11.Category(类别)、 Extension(扩展)和继承的区别?分别用来做什么?
区别:
1. 分类有名字,类扩展没有分类名字,是一种特殊的分类。
2. 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法。
3. 继承可以增加,修改或者删除方法,并且可以增加属性。
就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
extension在编译期决议,它就是类的一部分,但是category则完全不一样,它是在运行期决议的。extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它、extension伴随类的产生而产生,亦随之一起消亡。
extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。而category不需要有类的源码,我们可以给系统提供的类添加category。
extension可以添加实例变量,而category不可以。
extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现。
(二)Extension
1、 什么是extension
extension被开发者称之为扩展、延展、匿名分类。extension看起来很像一个匿名的category,但是extension和category几乎完全是两个东西。和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。extension一般用于声明私有方法,私有属性,私有成员变量。
2、 extension的存在形式
category是拥有.h文件和.m文件的东西。但是extension不然。extension只存在于一个.h文件中,或者extension只能寄生于一个类的.m文件中。比如,viewController.m文件中通常寄生这么个东西,其实这就是一个extension:
@interface ViewController ()
@end