天天看點

避免在類别(category)中定義屬性(@property)

property 是包裝資料的一種辦法.盡管技術上可以實作在category裡面聲明一個property,但是應該盡量避免這樣做.理由是,除了class延續類别外,是不可能用一個category對class添加一個執行個體變量.是以對于category同樣也不可能合成一個執行個體變量去支援property.我們來切割下本來是實作person的class.你可能需要一個關于友誼的category聲明方法,來操作關于這個person朋友的清單.在不知道描述的問題前,你也可以把property的friends清單放到友誼category裡面.想這樣:

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString*)lastName;
@end
           
@implementation EOCPerson
// Methods
@end

@interface EOCPerson (Friendship)
@property (nonatomic, strong) NSArray *friends;
- (BOOL)isFriendsWith:(EOCPerson*)person;
@end

@implementation EOCPerson (Friendship)
// Methods
@end
           

假如你進行編譯,可是你将會收到編譯器警告後結束

warning: property 'friends' requires method 'friends' to bedefined - use @dynamic or provide a method implementation inthis category [-Wobjc-property-implementation]warning: property 'friends' requires method 'setFriends:' to bedefined - use @dynamic or provide a method implementation inthis category [-Wobjc-property-implementation]

這個稍微有點含糊的警告意思是說不能用category合成一個執行個體變量,并且是以property需要有一個accessor方法來實作在category中.或者,可以聲明accessor方法@dynamic,意味着你聲明的變量将在運作時有效,但是編譯時是看不到的.這可能發生的情況是如果你使用消息轉發機制來攔截這個方法并且在運作時提供實作.

為了避免category不能合成執行個體變量的問題,你可以使用關聯的對象.例如,你可能需要去實作下面category内的關聯:

#import <objc/runtime.h>
static const char *kFriendsPropertyKey = "kFriendsPropertyKey";
@implementation EOCPerson (Friendship)

- (NSArray*)friends {
 return objc_getAssociatedObject(self, kFriendsPropertyKey);
}

- (void)setFriends:(NSArray*)friends {
 objc_setAssociatedObject(self,kFriendsPropertyKey, friends,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
           

這樣不是很完美的解決辦法.是有大量的引用和容易出現記憶體管理錯誤.因為很容易就會忘記這個property是像這樣實作的.例如,你可以用改變property的屬性來修改記憶體管理語義.但是你同樣需要去記得改變記憶體管理語義在setter内關聯的對象.盡管這是一個不錯的解決方案,但是我不推薦.

同樣,你可能希望這個執行個體變量可以支援這個friends數組是一個可變數組. 你可以帶一個可變拷貝,但是這樣又會是另外一種無盡的混亂開始進入你的代碼基礎.是以property在主要接口定義比在category裡面定義要幹淨的多.

在這個示例中,正确的解決辦法就是把所有的property定義放到主接口聲明.所有的資料封裝在一個主接口定義的類中,主接口是唯一可以聲明執行個體變量(資料)的地方.因為他們僅僅是定義一個執行個體變量和相關文法糖通路器方法,property受到相同的規則.category應該被想到用做一個類的方法擴充來擴充功能,而不是封裝資料.也就是說,有些時候隻讀屬性可以被成功的用在category中.例如,你可能想建立一個NSCalendar的category來傳回一個包含月份字元串的數組.因為這個方法通路任何資料,并且這個property不支援一個執行個體變量,你可以實作這個category像這樣:

@interface NSCalendar (EOC_Additions)
@property (nonatomic, strong, readonly) NSArray *eoc_allMonths;
@end
@implementation NSCalendar (EOC_Additions)
- (NSArray*)eoc_allMonths {
 if ([self.calendarIdentifier
 isEqualToString:NSGregorianCalendar])
 {
 return @[@"January", @"February",
 @"March", @"April",
 @"May", @"June",
 @"July", @"August",
 @"September", @"October",
 @"November", @"December"];
 } else if ( /* other calendar identifiers */ ) {
 /* return months for other calendars */
 }}
@end
           

property所支援的執行個體變量将不會自動合成,因為所有所需的方法(這裡隻有一個隻讀方法)需要被實作.是以,編譯器會發出警告.然而,即使在這種情況下,一般最好避免使用property. property的用意就在于依賴class對資料的支援.property是封裝資料,在本例中,你在category内将替換聲明方法為檢索清單中的月份.

@interface NSCalendar (EOC_Additions)
- (NSArray*)eoc_allMonths;
@end
           

需要記住的事情1.把所有封裝資料的property聲明都在住接口中定義.2.在category中希望使用通路器方法來聲明property,除非他是一個類延續(class-continuation)category

繼續閱讀