讲交换方法时,我们说过下节课讲给分类加属性,我猜你一点都不期待,因为你肯定会。
引言
关于分类加属性我们尝试这种方式:
- 创建分类,直接添加,引入文件,直接调用,一步到位,双击666!!!
创建分类:
@interface Person (Character)
@property(nonatomic,strong)NSString* name;
@end
@implementation Person (Character)
@end
潇洒调用:
Person* person = [[Person alloc] init];
person.name = @"伍丽娟";
结果爆炸:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x60000000c6f0'
原理分析
其实作为一个饱受套路的👨💻来说,肯定不会像上面这样天真烂漫的写代码。可能很多人也都知道用 objc_setAssociatedObject
,objc_getAssociatedObject
进行添加,我知道你不关心其中的原理,但是我偏要说!
我们作如下操作:
- 我们创建一个分类,并在分类中声明一个实例变量,如图:
你会发现报错了,系统告诉你在分类中不能添加实例变量,也就是说,Person
类的结构体中的实例变量链表(ivars
)不可扩展,Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。有人说我这是扯淡,系统明明有class_addIvar
这个方法啊!
这个方法的用处是:我们通过运行时来创建一个类时,我们才可以使用class_addIvar
函数添加实例变量。但是这个方法也只能在objc_allocateClassPair
函数与objc_registerClassPair
之间调用。另外,这个类也不能是元类。
现在我们知道分类中添加实例变量是不可能的了,于是我们还是沿着属性这条路发展才有一线生机,按照引言中的方法直接声明属性,到.m
中查看,你就会发现Xcode的良心所在:
哎呦喂,它告诉你你要实现
setter
和getter
方法,虽然ivars
链表不能扩展,但是methodLists
可以啊。但是之前的setter
和getter
都是结合实例变量实现的,现在该怎么办呢?现在我们的主角才是时候闪亮登场,下面两个方法用来把一个对象与另一个对象进行关联,并不会像属性自动生成的实例变量一样在当前类开辟空间。
/*
* object 源对象
* key 关键字
* value 被关联的对象
* policy 关联策略
*/
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
于是,我们的setter
和getter
方法可以这么写:
-(NSString *)name {
return objc_getAssociatedObject(self, @"name");
}
-(void)setName:(NSString *)name {
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
你会发现,完美解决,但是这样的属性不会生成实例变量,不要总是妄想用_name
取到对应实例变量,它只是有对应的setter
和getter
方法而已。
伍丽娟是这样理解的:
前几回我们说到,你找到了伍丽娟
,并且顺利搞走了你们之间的绊脚石无敌三道杠
,你觉得你的幸福生活即将开始,但是,不幸的事又发生了:
当初上帝在造人时,都有特定的特性,比如你的性别
(readonly),你的朋友
(readwrite),你的事业
(readwrite),你的老师同学
(readwrite),等等... ... ,只要属于你的,你都可以随心所欲尝试你可以自主改变的部分。但是,上帝并没有给你可以拥有女票
的特性,也就说你一辈子都是单身狗
,你和吴丽娟
根本就不可能,因为你根本就没这个功能!
你颓废,你沮丧,你的生活没有了方向,你没有办法和她有半点联系,也没有办法让别人通过你的女票
就想到她,在被人眼里,你的生命中就没有女票
这个概念。但是,你发现你的思想,你的行动是独立的,你可以做自己想做的任何事,但这并不能改变你是单身狗
的事实。于是你决定,哪怕不做情侣,能默默关注也好,能让别人看到你们之间还是有关系你也就心满意足了,于是你主动出击搭建你们之间的某种联系,但是她永远都不属于你,她永远无法走入你的生命!你只能单纯的关注她,你的心永远无法为他开辟出一席之地。人们也只能间接的通过你侧面的了解伍丽娟
,但是在伍丽娟
的角度,你啥么都不是。
代码封装
感谢上一位选手的情感真挚的发言!如果我们每一个都去像上面这样写,你自己都会觉得自己是个逗逼吧(但很多人乐此不疲)。封装一下,内附用法:
//
// YSAssociated.h
// RuntimeSkill
//
// Created by ys on 2016/5/11.
// Copyright © 2016年 ys. All rights reserved.
//
#import <objc/runtime.h>
// 添加id类型属性
#define ASSOCIATED(propertyName, setter, type, objc_AssociationPolicy)\
- (type)propertyName {\
return objc_getAssociatedObject(self, _cmd);\
}\
\
- (void)setter:(type)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), object, objc_AssociationPolicy);\
}
// 添加BOOL类型属性
#define ASSOCIATED_BOOL(propertyName, setter)\
- (BOOL)propertyName {\
NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.boolValue;\
}\
\
- (void)setter:(BOOL)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
}
// 添加NSInteger类型属性
#define ASSOCIATED_NSInteger(propertyName, setter)\
- (NSInteger)propertyName {\
NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.integerValue;\
}\
\
- (void)setter:(NSInteger)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
}
// 添加float类型属性
#define ASSOCIATED_float(propertyName, setter)\
- (float)propertyName {\
NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.floatValue;\
}\
\
- (void)setter:(float)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
}
// 添加double类型属性
#define ASSOCIATED_double(propertyName, setter)\
- (double)propertyName {\
NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.doubleValue;\
}\
\
- (void)setter:(double)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
}
// 添加long long类型属性
#define ASSOCIATED_longlong(propertyName, setter)\
- (long long)propertyName {\
NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.longLongValue;\
}\
\
- (void)setter:(long long)object\
{\
objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
}
//在类别中添加属性
//使用方法如下:
/**
.h
#import <Foundation/Foundation.h>
@interface Person (AssociatedTest)
@property (nonatomic, strong) NSString *name;
@property (nonatomic, weak) id delegate;
@property (nonatomic, assign) BOOL isOK;
@end
.m
#import "Person + AssociatedTest.h"
@implementation NSObject (AssociatedTest)
ASSOCIATED(name, setName, NSString *, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
ASSOCIATED(delegate, setDelegate, id, OBJC_ASSOCIATION_ASSIGN)
ASSOCIATED_BOOL(isOK, setIsOK)
@end
*/
根据我上面的使用方法,你应该能够更加快速稳定的为扩展类添加属性了(提供一种思路:很多时候,不要只局限于你理解中的对象,你可以给系统类添加Block
类型属性,把代码块作为参数进行操作),但是在有些情况下,我们并不需要如此复杂的添加一个属性,临时链接一下就可以,因为我发现这样的死心眼还挺多。理解原理,理性封装之后你用起来是不是更放得开手脚了,如果你有感到一点点爽,请给伍丽娟
点个赞!!!