iOS相关iOS点点滴滴

关于iOS Class Category的整理

2017-12-12  本文已影响8人  上发条的树

参考

作用

分类中的非私有方法,也是属于原来那个类的方法,调用的时候,也是用原来的类去调用,而不是新的其它的类。
例如: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中,是没有私有方法的

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文件中,就会出现如下的警告:


分类中声明一个属性的警告.png

以上警告,告诉我们:需要手动添加方法,或者使用@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 编程中的“宏”,它有元编程的思想。上面这段代码,实际上会做三件事:

@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,本文应该是具有一定的参考作用的。如果您觉得本文对您有一定的帮助,请随手点个喜欢,十分感谢!🌹🌹🌹

上一篇 下一篇

猜你喜欢

热点阅读