在Objective-C代码中, 使用Category文件可以很好地优化代码结构, 给一个类提供一些扩展功能. 如果Category中的方法与原来类中的方法重名, 则会将原来的方法覆盖.
而Category添加属性, 只能添加对应的getter/setter方法, 而不会添加真正的实例变量(ivar). 因为实例变量存在于类的结构中, 是在编译期就确定好的.
利用Category优化代码结构
在Category中使用属性, 只能采用关联对象来实现.
Category中定义的属性, 其实只会有getter/setter方法, 而没有包含实例变量.
@interface MyViewController (Timer)
@property (nonatomic, strong) NSTimer *timer;
- (void)addTimer;
- (void)removeTimer;
@end
实现懒加载
在实现文件中的getter方法中, 添加类似懒加载的实现代码即可:
#import <objc/runtime.h>
static void *kContextTimer = &kContextTimer;
@implemetation MyViewController (Timer)
// 关联对象
- (NSTimer *)timer {
NSTimer *timer = objc_getAssociatedObject(self, kContextTimer);
if (!timer) {
timer = [NSTimer timerWithTimeInterval:1.f
target:self
selector:@selector(actionTimerOn:)
userInfo:nil
repeats:YES];
objc_setAssociatedObject(self, kContextTimer, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return timer;
}
- (void)setTimer:(NSTimer *)timer {
objc_setAssociatedObject(self, kContextTimer, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)actionTimerOn:(NSTimer *)sender {
NSLog(@"actionTimerOn : %@", sender);
}
- (void)addTimer {
[[NSRunLoop currentRunLoop] addTimer:self.timer
forMode:NSRunLoopCommonModes];
}
- (void)removeTimer {
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
@end
关联策略objc_AssociationPolicy
关联策略表明了关联的对象是通过值传递, 引用传递, 还是copy的方式进行关联.
/* Associative References */
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
采用的关联策略与属性内存管理关键字保持一致即可.
对于普通的OC对象, 通常采用OBJC_ASSOCIATION_RETAIN_NONATOMIC.
对于NSString等通常采用copy关键字的属性, 通常采用OBJC_ASSOCIATION_COPY_NONATOMIC.
对于枚举等通常采用assign关键字的属性, 通常采用OBJC_ASSOCIATION_ASSIGN.
关联键值key
参数key接收一个const void * _Nonnull的指针类型, 因此可以有多种传入方式:
static void *kContextTimer = &kContextTimer;
static void *kContextTimer = &kContextTimer;
objc_getAssociatedObject(self, kContextTimer);
objc_setAssociatedObject(self, kContextTimer, timer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
static char imageURLStorageKey;
在SDWebImage中,
static char imageURLStorageKey;
objc_getAssociatedObject(self, &imageURLStorageKey);
objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
直接使用@selector(af_activeImageDownloadReceipt)
在AFNetworking中, 有如下类似的写法:
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt;
@end
@implementation UIImageView (_AFNetworking)
- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}
- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
根据state来区分一些有联系的key值
对于UIButton的扩展, 采用如下方式与UIButton的状态联系起来, 是一种非常巧妙的处理方式.
static char AFImageDownloadReceiptNormal;
static char AFImageDownloadReceiptHighlighted;
static char AFImageDownloadReceiptSelected;
static char AFImageDownloadReceiptDisabled;
static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}
- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}