分类(Category)不能为类添加成员变量,因为类的空间大小在编译期就已经确定了,而分类属于运行时技术,但是在需要的时候可以增加存储属性来实现属性与对象的绑定.
常用的方法
使用分类(Category)为类增加属性大致有以下方法:
存储方法(相当于setter方法)
/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
调用该方法可以保存,更新或者删除属性.需要四个参数:
- object:需要绑定属性的源对象(需要把属性绑定到该对象上);
- key:绑定关系的key值,用以标记绑定的属性,所以对同一个对象绑定属性时需要确保不同属性对应的key值唯一;
- value:需要绑定的属性值,如果该值有效则会保存或者更新key对应的值;如果该值为nil,则会删除key对应的保存值;
- policy:跟property的语义属性相似,主要是对象处理属性保存的相关细节,稍后做详细说明.
获取方法(相当于getter方法)
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
调用该方法可以获取保存的属性值,需要两个参数:
- object:需要获取绑定属性的源对象(也就是属性被绑定在哪个对象上);
- key:被绑定属性的key值,要和保存时使用的key值保持一致才能正确取出存储的值.
删除绑定属性
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
调用该方法会删除对象上绑定的所有属性,需要参数:
- object: 属性绑定的对象.
应用场景
在一些情况下,希望对象可以携带一些额外的属性,但是该对象偏偏没有这样的属性.对于自定义的类,可以通过该修源代码增加属性,但是对于没有源代码的系统类,如果单单为了增加一个属性就创建一个子类,有些得不偿失而且也没有必要,这时候就可以通过分类(Category)来实现.
例如在控制器中需要为UIButton添加一个identifier的属性:
@interface UIImageView (AddProperty)
@property (nonatomic, copy) NSString *sourceURL;
@end
@implementation UIImageView (AddProperty)
- (NSString *) sourceURL {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setSourceURL:(NSString *) sourceURL {
objc_setAssociatedObject(self, @selector(sourceURL), identifier, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
@implementation CustomerViewController
- (void)viewDidload {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect){.origin=CGPointZero, .size=(CGSize){60, 60}}];
//加载网络图片
[imageView setImageWithURL:url];
//存储图片源
[imageView setIdentifier:url];
[imageView setUserInteractionEnabled:true];
[imageView addGestureRecognizer:({
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click:)];
tap;
})];
[self.view addSubview:imageView];
}
- (void) click:(UITapGestureRecognizer *)tap {
/*
只是做个演示,提供一种思路,或许有人会说用tag也可以区分,勿喷
*/
NSString *identifier = [(UIImageView *)tap.view identifier];
NSLog(@"identifier == %@", identifier);
if ([identifier isEqualToString:@"identifier_login"]) {
//do some operations for special identifier
}
}
@end
其中关于key值的取用方法,大概有以下两种:
- 使用静态字符,例如SDWebImage,AFN以及MJRefresh中就大量使用了这种方式
static char AFBackgroundImageDownloadReceiptNormal;
static char AFBackgroundImageDownloadReceiptHighlighted;
static char AFBackgroundImageDownloadReceiptSelected;
static char AFBackgroundImageDownloadReceiptDisabled;
static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFBackgroundImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFBackgroundImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFBackgroundImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFBackgroundImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state));
}
- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
...
- 使用@selector,腾讯云作者就比较倾向于这一种方式(如果是使用当前方法的sel,也可以使用_cmd代替):
...
- (BOOL)enableMD5Verification {
NSNumber* number = objc_getAssociatedObject(self, @selector(enableMD5Verification));
return [number boolValue];
}
- (void)setEnableMD5Verification:(BOOL)enableMD5Verification {
NSNumber* number = [NSNumber numberWithBool:enableMD5Verification];
objc_setAssociatedObject(self, @selector(enableMD5Verification), number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
...
总而言之,key就是一个指针,保持存取一致就好了.