iOS Category的正确理解

来源:互联网 发布:750x254淘宝店招素材 编辑:程序博客网 时间:2024/06/04 19:55

Category的作用

(1)可以将类的实现分散到多个不同文件或多个不同框架中,方便代码管理。也可以对框架提供类的扩展(因为框架类没有源码,不能修改)。

(2)创建对私有方法的前向引用:如果其他类中的方法未实现,在你访问其他类的私有方法时编译器报错这时使用类别,在类别中声明这些方法(不必提供方法实现),编译器就不会再产生警告

(3)向对象添加非正式协议:创建一个NSObject的类别称为“创建一个非正式协议”,因为可以作为任何类的委托对象使用。有两个方面的局限性: (1)无法向类中添加新的实例变量,类别没有位置容纳实例变量。(2)名称冲突,即当类别中的方法与原始类方法名称冲突时,类别具有更高的优先级。类别方法将完全取代初始方法从而无法再使用初始方法。这个类似于方法的重载,但是这里是直接覆盖了原方法。

其实有很多面试的都遇到过为什么Category不能添加属性?是不是感觉问法很有问题!!!应该是为什么Category只能通过Runtime动态关联属性?为什么Category不能添加成员变量?
这里首先搞清楚属性和成员变量的关系,这个网上很多,还有就是关键字的理解,可以看这个
@dynamic 与 @synthesize 关键词个人理解

举个列子

@interface UIView (Extention)@property (nonatomic,assign) CGFloat x;@end@implementation UIView (Extention)- (void)setX:(CGFloat)x{    CGRect rec = self.frame;    rec.origin.x = x;    self.frame = rec;}- (CGFloat)x{    return self.frame.origin.x;}@end

网上很多例子都是根据Runtime添加的,这个等下再给出,先看看基
本数据类型,很明显,非常简单就能添加属性进去了。非常巧合,其实Catogory的属性是不会自动执行@synthesize 关键词下的getter和setter方法,因此如果我们手动实现,就能实现类似常规的点语法访问和赋值,其实Category只是是无法添加成员变量。

原理:为什么可以动态添加方法,不能添加成员变量

为什么Category下的属性不做任何处理,直接进行getter和setter访问会崩溃?
既然允许用Category给类增加方法和属性,那为什么不允许增加成员变量?
1.首先,看到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.

意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair之后才可以被使用,同样没有机会再添加成员变量。
举个例子,如何让NSObject避免出现unrecognized selected导致崩溃,里面就涉及到runtime以及一些元类的创建,就可以添加Method,Ivar等

@implementation NSObject (HookUnrecognizedSel)// A Selector for a method that the receiver does not implement.// 当category重写类已有的方法时会出现此警告。// Category is implementing a method which will also be implemented by its primary class#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"- (id)forwardingTargetForSelector:(SEL)aSelector{    NSLog(@"unrecognized selector : classe:%@ sel:%@",NSStringFromClass([self class]),NSStringFromSelector(aSelector));    // 元类 meta class 创建 重新指定Selector 防止崩溃  http://ios.jobbole.com/81657///    1、为”class pair”分配内存 (使用objc_allocateClassPair).//    2、添加方法或成员变量到有需要的类里 (我已经使用class_addMethod添加了一个方法).//    3、创建出来    // 用objc_allocateClassPair创建一个自定义名字的元类    Class class = objc_allocateClassPair(NSClassFromString(@"NSObject"), "UnrecognizedSel", 0);    // 类添加方法 Sel 和 Imp    class_addMethod(class, aSelector, class_getMethodImplementation([self class], @selector(customMethod)), "v@:");//    class_addIvar(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)//    objc_registerClassPair(class)    // 创建    id tempObject = [[class alloc] init];    return tempObject;}#pragma clang diagnostic pop- (void)customMethod{    NSLog(@"呵呵");}@end

那么创建完的类在进行方法,属性,实例变量的操作为何有的可以有的不可以?
先看看类的结构

struct objc_class {    Class isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__    Class super_class                       OBJC2_UNAVAILABLE;  // 父类    const char *name                        OBJC2_UNAVAILABLE;  // 类名    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0    long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识    long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表#endif} OBJC2_UNAVAILABLE;

在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。

动态添加属性

其实@property关键字在Category下,是不会自动生成getter和setter以及实例变量的,Category是动态加载注入的,那么这些生成是静态编译的时候类已经创建好了,那么既然是动态无法注入,我们就需要自己通过Runtime进行动态关联

objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects

为什么我说这个很有用呢?因为这允许开发者对已经存在的类在扩展中添加自定义的属性,这几乎弥补了OC的缺点。

@interface NSObject (AssociatedObject)@property (nonatomic, strong) id associatedObject;@end@implementation NSObject (AssociatedObject)@dynamic associatedObject;- (void)setAssociatedObject:(id)object {     objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (id)associatedObject {    return objc_getAssociatedObject(self, @selector(associatedObject));}

通常推荐的做法是添加的属性最好是 static char 类型的,当然更推荐是指针型的。通常来说该属性应该是常量、唯一的、在适用范围内用getter和setter访问到:

static char kAssociatedObjectKey;objc_getAssociatedObject(self, &kAssociatedObjectKey);

Extension与Category区别

Extension

在编译器决议,是类的一部分,在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。
伴随着类的产生而产生,也随着类的消失而消失。

Category

是运行期决议的
类扩展可以添加实例变量,分类不能添加实例变量
原因:因为在运行期,对象的内存布局已经确定,如果添加实例变量会破坏类的内部布局,这对编译性语言是灾难性的。

参考资料如下,个人用来记录加深印象
http://www.jianshu.com/p/535d1574cb86
Objective-C类成员变量深度剖析
Associated Objects

原创粉丝点击