关于iOS Class Category的整理
参考
作用
- 把一个类中的方法实现分成几个文件,这样做的好处:
a)可以减小单个文件的体积。
b)把不同功能的方法,分类在不同的文件中。
c)可由不同的开发者负责不同的分类。
d)按需加载不同的分类。 - 声明私有方法(创建私有方法的前向引用)。
分类中的非私有方法,也是属于原来那个类的方法,调用的时候,也是用原来的类去调用,而不是新的其它的类。
例如:Person
@interface Person : NSObject
@property(retain,nonatomic)NSString* name;
-(void)printName;
@end
@implementation Person
-(void)printName{
NSLog(@"%@",self.name);
}
@end
以及它的分类Person+Skill
@interface Person (Skill)
-(void)eatSkill;
@end
@implementation Person (Skill)
-(void)eatSkill{
NSLog(@"人的技能之一:吃饭");
}
@end
那么调用分类的方法,其实就是Person
调用自己的方法一样:
#import "Person.h"
#import "Person+Skill.h"
Person *person = [Person alloc]init];
person.name = @"wxx";
[person printName];
[person eatSkill];
声明私有方法(创建私有方法的前向引用)
我们都知道,在Objective-C
中,创建一个方法,如果在.h文件中声明了(.m中实现),那么这个方法属于公有方法,也就是public。如果只是在Extension
(类扩展)中声明(关于Extension点这里),或者不声明,那么就是属于私有方法,也就是private。在Extension
中声明方法如下:
@interface Person(){
}
-(void)privateMothod;
@end
默认情况下,私有方法是不能在其他类中使用的,强制使用,Xcode编译器会报错。如果非要访问私有方法,可以新建该方法所属类的分类,在分类的.h中声明该方法,.m中不用实现。严格意义上来说,Objective-C中,是没有私有方法的。后面会说到为什么
下面是一个例子:
在类中创建一个私有方法,扩展类可声明可不声明
#import "Person.h"
@interface Person(){
}
-(void)privateMethod;
@end
@implementation Person
-(void)privateMethod{
NSLog(@"this is class private method");
}
@end
分类中.h声明该私有方法:
#import "Person.h"
@interface Person (PrivateMethod)
//只声明,不实现
-(void)privateMethod;
@end
#import "Person+PrivateMethod.h"
@implementation Person (PrivateMethod)
@end
最终运行:
#import "Person.h"
#import "Person+PrivateMethod.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];
[person privateMethod];
}
@end
运行结果:
this is class private method
通过上面的方法,实现了调用类中的私有方法,由此得到以下结论
Objective-C中,是没有私有方法的
-
Q:为什么说Objective-C中,是没有私有方法的?
A:因为通过运行时,我们可以获取到一个类中的所有方法名;而有了方法名,可以如上添加分类的方式,调用私有方法! -
Q:如何获取一个类中的所有方法名?
A:如下所示:
unsigned int count;
Method *methods = class_copyMethodList([Person class], &count);
for (int i = 0; i < count; i++) {
Method method = methods[i];
SEL selector = method_getName(method)
NSString *name = NSStringFromSelector(selector);
NSLog(@"%@",name);
}
打印结果:
2017-12-11 17:19:41.386635+0800 WxxDemo[2068:829821] eatSkill
2017-12-11 17:19:41.386780+0800 WxxDemo[2068:829821] haha
2017-12-11 17:19:41.386912+0800 WxxDemo[2068:829821] setHaha:
2017-12-11 17:19:41.387020+0800 WxxDemo[2068:829821] printName
2017-12-11 17:19:41.387116+0800 WxxDemo[2068:829821] privateMethod
2017-12-11 17:19:41.387252+0800 WxxDemo[2068:829821] .cxx_destruct
2017-12-11 17:19:41.387404+0800 WxxDemo[2068:829821] name
2017-12-11 17:19:41.387495+0800 WxxDemo[2068:829821] setName:
可以看到,私有方法名称(例如:privateMethod),是可以获取到的!如何调用私有方法,相信大家已经很清楚。
分类中添加属性
直接在分类.h中添加属性(严格意义上来讲,不是添加,而是声明。):
@interface Person (Skill)
@property (nonatomic,copy)NSString *haha;
@end
在分类的.m文件中,就会出现如下的警告:

以上警告,告诉我们:需要手动添加方法,或者使用@dynamic在运行时动态实现关联属性
如果没有按照警告的做法来实现,直接调用该属性:
person.haha = @"哈哈";
就会报出如下的crach,果然上面的警告不能忽略啊:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setHaha:]: unrecognized selector sent to instance 0x60000001f7a0'
@property分析
@interface Person : NSObject
@property (nonatomic,copy)NSString *haha;
@end
实际上,@property可以看成是Objective-C 编程中的“宏”,它有元编程的思想。上面这段代码,实际上会做三件事:
- 生成实例变量 _haha
- 生成 getter 方法 - haha
- 生成 setter 方法 - setHaha:
@implementation DKObject {
NSString * _haha;
}
- (NSString *) haha {
return _haha;
}
- (void)setHaha:(NSString *) haha {
_haha = haha;
}
@end
Objective-C的.语法,是一种语法糖,.haha用于=左边是赋值,也就是set;.haha用于=右边,是取值,也就是get。
当然,这三件事都是编译器帮你生成代码,虽然没有看到,但是确实是存在的。而分类中,就不会自动生成实例变量,所以需要手动关联属性。
而通过查看category的定义,我们也可以看出一些端倪:
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
我们可以看到,category中,有实例方法列表instance_methods,类方法列表class_methods,属性列表protocols,唯独没有实例变量列表:ivars。因此,我们只能通过其他的途径去实现。
使用关联对象
既然分类中,编译器无法自动为属性生成实例变量,也无法自动生成get/set方法,那么我们只能在运行时动态的关联属性
如下:
#import "Person+Skill.h"
#import <objc/runtime.h>
static void *hahaKey = &hahaKey;
@implementation Person (Skill)
-(NSString*)haha{
return objc_getAssociatedObject(self, hahaKey);
}
-(void)setHaha:(NSString *)haha{
objc_setAssociatedObject(self, hahaKey, haha, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
如此一来,不会再有警告,也能够正常的使用该属性的实例变量,而不会crach:
person.haha = @"哈哈";
NSLog(@"%@",person.haha);
动态关联的相关方法中,key值的几种写法,利用静态变量地址唯一不变的特性
- 1、static const void *hahaKey = & hahaKey;
- 2、static const NSString * hahaKey = @"hahaKey";
- 3、static const char hahaKey;
或者这样:
- 4、@selector(haha)
关联策略是个枚举:
- OBJC_ASSOCIATION_ASSIGN = 0, //关联对象的属性是弱引用
- OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //关联对象的属性是强引用并且关联对象不使用原子性
- OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //关联对象的属性是copy并且关联对象不使用原子性
- OBJC_ASSOCIATION_RETAIN = 01401, //关联对象的属性是copy并且关联对象使用原子性
- OBJC_ASSOCIATION_COPY = 01403 //关联对象的属性是copy并且关联对象使用原子性
总结
- Category可以为主类添加方法,可以按照功能划分不同的分类。
- Category不能为主类添加实例变量,但是可以通过
objc association
的方式来动态关联属性,达到同样的效果。 - Category可以用来创建私有方法的前向引用,使得主类中的私有方法暴露无遗。
- Category可以依赖于主类,但是主类一定不依赖于Category,也就是说,移除Category,对主类没有任何影响。就像为MacBook买了一个键盘,即插即用。
本文属用法整理,而关于Category的底层实现没有进行分析,暂时没那个大神的能力(可通过文章开头的参考进行查看,里面有关于底层的实现分析)。但是对于使用Category,本文应该是具有一定的参考作用的。如果您觉得本文对您有一定的帮助,请随手点个喜欢,十分感谢!🌹🌹🌹