在iOS开发中为了方便开发,通常我们会通过Category对系统的类进行扩展,给系统类添加方法,例如
@interface UIImage (Image)
/** 根据颜色生成一张1*1的纯色图片*/
+ (UIImage *)imageWithColor:(UIColor *)color;
@end
#import "UIImage+Image.h"
@implementation UIImage (Image)
+ (UIImage *)imageWithColor:(UIColor *)color
{
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}
@end
给UIImage添加一个分类,可通过传入颜色直接生成一张颜色对应的纯色图片
这样的功能主要就是OC的runtime机制实现的.
但是在category中是不能够直接去添加属性的,因为系统是不会为我们自动的去生成属性对应的setter/getter方法
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIImageView setTestStr:]: unrecognized selector sent to instance 0x7fa32e509310'
直接添加之后赋值的时候报错
category为什么会有这样的限制呢?我们需要从@property关键字声明属性默认做的事情来思考
@interface UIImageView (rumtime)
@property (nonatomic, strong) NSString *testStr;
@end
我们在UIImageView的category中声明了一个NSString属性
正常情况下,在.h中声明属性后,我们就可在类的实例中使用这些属性了.为了能够正确使用属性,OC会默认为我们完成以下工作
在.m中,编译器通过@synthesize关键字,将我们声明的属性转换为了对应的实例变量,并默认在属性名称前添加'_'来在类中标识对应的实例变量. 根据我们在@property中指定的访问限制(readwrite/readonly),编译器会自动生成默认的setter/getter方法(其本质仍是对带下划线的实例变量的操作).(如果是readonly,则只会生成getter方法).由@property生成的实例变量,会在类实例创建时(alloc)被分配内存,类实例销毁时释放内存.
@property关键之相关内容请查看
https://www.jianshu.com/p/e1c4c45ebd96
@synthesize与生成setter/getter方法,均是在编译期完成的,而category则属于runtime时期加载,所以编译器就不会为我们做@synthesize与生成对应setter/getter方法的工作了,因此我们也就不能够在category中添加属性,直接添加之后实例对象访问时直接报错.
所以我们需要使用runtime动态的去关联属性
话不多数,直接上代码
@interface UIImageView (rumtime)
@property (nonatomic, strong) NSString *testStr;
@end
#import "UIImageView+rumtime.h"
#import <objc/runtime.h>
static char *imageViewClickKey;
@interface UIImageView ()
@end
@implementation UIImageView (rumtime)
- (void)setTestStr:(NSString *)testStr {
objc_setAssociatedObject(self, @selector(testStr), testStr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)testStr {
return objc_getAssociatedObject(self, @selector(testStr));
}
@end
需要注意的是,建立关联的key可以为任意值,但是必须保证其唯一性,并且在setter和getter中均能访问,而@selector正好满足,故我们可以使用它作为key.添加的属性应为NSObject的子类,一般属性如int等不能设置关联.对于OBJC_ASSOCIATION_ASSIGN,虽然是weak引用,但其并不会像property的weak那样释放后自动为nil,而是一个野指针,这里要注意不要引发BAD ACCESS异常。