因?yàn)镃ategory在iOS平時(shí)的開發(fā)中用的比較多,所以本文從各個(gè)方面介紹一下Category,很多地方都是自己的理解,歡迎各位看到的同學(xué)批評指正,多多交流。
一、Category作用
- 為已存在的類添加方法;對類進(jìn)行了很好的擴(kuò)展,應(yīng)對變化的需求。
- 可以把一個(gè)類的實(shí)現(xiàn)分散到多個(gè)文件中,使得每個(gè)文件不至于龐大,而且可以聚集同一邏輯的代碼在一個(gè)文件中。同時(shí),也可以按需加載方法。
- 同一個(gè)類可以由多個(gè)人共同完成。
二、為什么category不能添加屬性或者說是成員變量
這個(gè)問題從runtime的角度進(jìn)行分析。
下圖是objc_class結(jié)構(gòu)體:

不詳細(xì)介紹每一個(gè)字段的含義了,主要介紹與category相關(guān)的部分:
在上面的objc_class結(jié)構(gòu)體中,ivars是objc_ivar_list(成員變量列表)指針;methodLists是指向objc_method_list指針的指針。在Runtime中,objc_class結(jié)構(gòu)體大小是固定的,不可能往這個(gè)結(jié)構(gòu)體中添加數(shù)據(jù),只能修改。所以ivars指向的是一個(gè)固定區(qū)域,只能修改成員變量值,不能增加成員變量個(gè)數(shù)。methodList是是一個(gè)二維數(shù)組,所以可以修改*methodLists的值來增加成員方法,雖沒辦法擴(kuò)展methodLists指向的內(nèi)存區(qū)域,卻可以改變這個(gè)內(nèi)存區(qū)域的值(存儲(chǔ)的是指針)。因此,可以動(dòng)態(tài)添加方法,不能添加成員變量。類似的是protocol,因?yàn)樗蔷幾g器處理的,所以可以添加變量。category是在運(yùn)行時(shí)處理的。
三、怎么樣可以實(shí)現(xiàn)添加屬性或者說添加成員變量
上面說了category中不可能添加成員變量或?qū)傩?。因?yàn)橄到y(tǒng)無法合成實(shí)現(xiàn)屬性所需的實(shí)例變量,所以category中也無法添加@property。但有時(shí)候確實(shí)需要添加屬性,那有沒有其他辦法,可以不改變對象的內(nèi)存結(jié)構(gòu),變相的給對象增加成員變量。
我們可以Runtime的objc_getAssociatedObject和objc_setAssociatedObject方法來模擬屬性的get和set方法,用關(guān)聯(lián)對象來模擬實(shí)例變量,這樣就有了@property的三要素,只是跟@property的實(shí)現(xiàn)機(jī)制是完全不一樣的。
有兩種方式,第一種代碼更簡潔:
.h文件:
@property (nonatomic) NSString Id;
.m文件,要#import <objc/runtime.h>
- (NSString *)Id
{
return objc_getAssociatedObject(self, @selector(Id));
}
- (void)setId:(NSString *)Id
{
objc_setAssociatedObject(self, @selector(Id), Id, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
第二種:
static void *UIViewPoint =@"UIViewPoint";
@implementation UIView (Point)
@dynamic pointView;
- (void)setPointView:(UIView *)pointView {
objc_setAssociatedObject(self, UIViewPoint, pointView,OBJC_ASSOCIATION_RETAIN);
}
- (UIView *)pointView {
return objc_getAssociatedObject(self, UIViewPoint);
}
這兩個(gè)方法的原型如下:
id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
@selector(Id) 是參數(shù)中的 key,方法二中使用了靜態(tài)指針 static void * 類型的參數(shù)來代替,第一種方法因?yàn)槭÷粤寺暶鲄?shù)的代碼,并且能很好地保證 key 的唯一性,所以更簡潔。
OBJC_ASSOCIATION_RETAIN_NONATOMIC 從字面意思就能猜到它是和屬性的修飾符是對應(yīng)的。對應(yīng)關(guān)系見如下的定義:
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. */
};
當(dāng)屬性是基本數(shù)據(jù)類型的時(shí)候,可能只是需要添加get和set方法,不需要實(shí)例變量的時(shí)候,可以在category中這樣添加屬性,這種方式,同樣沒有改變對象的內(nèi)存結(jié)構(gòu)。
@property (nonatomic) CGFloat left;
@property (nonatomic) CGFloat right;
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)right {
return self.left + self.width;
}
- (void)setRight:(CGFloat)right {
if(right == self.right){
return;
}
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
我查了很多資料,很多人認(rèn)為category可以添加屬性,但是不可以添加成員變量,我覺得這種說法是不嚴(yán)謹(jǐn)?shù)?,所以寫了這篇博客,來總結(jié)一下。如果有理解的不對的地方,希望看到的朋友指正,大家多多交流。