iOS-Category简单使用

Category是指Objective-C中的类别也叫分类,是一种不需要继承即可给类添加方法的语法技术。

Category利用Objective-C的动态运行时分配机制(Runtime),主要作用是为现有的类(自己的或系统的或三方库的)添加新方法。

Category可以为任何已有的类添加新的方法,包括那些没有源代码的类。

Category的设计原则

Category 的实现可以依赖主类,但主类一定不依赖 Category,也就是说移除任何一个 Category 的代码不会对主类产生任何影响。

Category 可以直接使用主类已有的私有成员变量,但不应该为实现 Category 而往主类中添加成员变量。

所以 Category的设计一定是简单的插拔使用的,就像买个机械键盘来扩展在 MacBook 上的键盘,但拔了机械键盘之后,MacBook 的运行不会受到任何影响。 

Category添加方法

Category添加方法是最常用也是最主要、最重要的使用方式。

第一步:我们要新建一个文件。

命名时应该加上大写前缀。 

第二步:为这个Category添加方法。

注意:Category方法最好不要和类原有的方法重名,运行时会优先调用Category中的同名方法,也就是Category方法会覆盖掉类中原有的方法,这是不好的,因为Category本意为添加新的方法而不是重写。

所以Category方法在命名时,例如:小写前缀_方法名称 - zoc_viewLog。

如果一个类有多个Category,那么把小写前缀改为小写Category名避免错误,例如:小写Category名_方法名称 -zoconelog_viewLog。

#import <UIKit/UIKit.h>

@interface UIView (ZOCOneLog)

+(void) zoc_viewLog;

@end
#import "UIView+ZOCOneLog.h"

@implementation UIView (ZOCOneLog)

+(void) zoc_viewLog {
    NSLog(@"我是ZOCOneLog中的zoc_viewLog方法!");
}

@end

第三步: 调用Category方法。

在需要使用的地方导入头文件。

#import "UIView+ZOCOneLog.h"

然后调用该方法即可。

[UIView zoc_viewLog];

 调用结果:

2019-01-15 23:52:08.854 demo[1622:97931] 我是 ZOCOneLog 中的 zoc_viewLog 方法!

Category添加属性

网上很多人、很多博客都说Category不能添加属性呢?

那么Category到底能不能添加属性呢?

这个我们要从Category的结构体开始分析:

typedef struct category_t {
    const char *name;  //类的名字
    classref_t cls;  //类
    struct method_list_t *instanceMethods;  //category中所有给类添加的实例方法的列表
    struct method_list_t *classMethods;  //category中所有添加的类方法的列表
    struct protocol_list_t *protocols;  //category实现的所有协议的列表
    struct property_list_t *instanceProperties;  //category中添加的所有属性
} category_t;

Category的结构体中定义了类名、扩展的类,四个列表分别是实例方法、类方法、协议、属性

从Category的结构体可以看出这个四个列表就是我们可以添加、扩展的东西。

而没有定义在Category的结构体中的成员变量等就无法添加。

所以答案是Category可以为类添加属性。

但是为什么那么多人、博客都说Category不能添加属性呢?

因为在Category中添加@property,是不会自动生成_成员变量(带下划线的成员变量)和该@property的setter和getter方法。

所以尽管添加了@property,但也因为没有该@property的setter、getter方法和_成员变量而无法使用该@property。

但其实我们可以利用Runtime手动为该@property实现setter、getter方法,从而达到Category可以为类添加属性的效果。

//添加属性
@property (nonatomic, strong) NSString *name;
//以下是添加属性之后的步骤

// 定义属性关联的key:属性名+Key = "属性名"
static const char *nameKey = "name";

// setter方法
-(void)setName:(NSString *)name {
    /*
     objc_setAssociatedObject:设置关联对象,看样式就很像setter方法
     self:给哪个对象添加关联,一般为self
     nameKey:属性关联的key,通过这个key来获取该属性
     name:赋给属性的值,就是传入的参数
     OBJC_ASSOCIATION_RETAIN_NONATOMIC:关联策略
     */
    objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// getter方法
-(NSString *)name {
    /*
     objc_getAssociatedObject:获得关联对象,看样式就很像getter方法
     self:从哪个对象获得关联,一般为self
     nameKey:属性关联的key,通过这个key来获取该属性
     */
    return objc_getAssociatedObject(self, nameKey);
}
//测试代码,导入头文件等步骤省略
UIView *view = [[UIView alloc]init];
view.name = @"SunSatan";
NSLog(@"UIView.name = %@",view.name);
//打印结果
2019-01-15 23:34:30.168 demo[1550:90531] UIView.name = SunSatan

 通过以上几个步骤我们就可以实现Category为类添加属性。

调用优先级

如果你遵循Category中不重写类原有方法,同一类中的不同分类(Category) 也不实现重名方法,那么本节内容可以忽略。

调用优先级:类别(Category) > 本类 > 父类。

也就是说会优先调用cateory中的方法,然后调用本类方法,最后调用父类方法,这也是重写类原有方法会覆盖的原因。

一个类的两个分类(Category) 如果实现了同名方法,那么它们之间也有优先级。

我们需要来验证:

首先在已有ZOCOneLog的基础上,我们在再创建一个Category为ZOCTwoLog。

ZOCTwoLog中实现与ZOCOneLog中同名的方法:zoc_viewLog,但是打印信息以此来区分。

#import <UIKit/UIKit.h>

@interface UIView (ZOCTwoLog)

+ (void) zoc_viewLog;

@end
#import "UIView+ZOCTwoLog.h"

@implementation UIView (ZOCTwoLog)

+(void) zoc_viewLog {
    NSLog(@"我是 ZOCTwoLog 中的 zoc_viewLog 方法!");
}

@end

 第一次运行结果:此时调用的是ZOCOneLog中的zoc_viewLog方法。

2019-01-15 23:47:22.877 demo[1597:95854] 我是 ZOCOneLog 中的 zoc_viewLog 方法!

此时我们来看,文件的编译顺序,文件从上往下编译。

所以ZOCTwoLog比ZOCOneLog先编译。

然后我们将ZOCOneLog拖到ZOCTwoLog文件上面,再运行。

第二次运行结果:此时调用的是ZOCTwoLog中的zoc_viewLog方法。

2019-01-15 23:52:08.854 代理demo[1622:97931] 我是 ZOCTwoLog 中的 zoc_viewLog 方法!

结论:一个类的两个分类(Category)的调用优先级取决于哪个文件后编译,后编译先调用。

注意:Category是在运行时加载的,不是在编译时,因为Category使用的是Runtime。

猜你喜欢

转载自blog.csdn.net/qq_36557133/article/details/86495467