這是Objective-C系列的第4篇。緩存
將類的實現代碼分散到便於管理的數個分類之中框架
Private
的分類中,以隱藏實現細節。老是爲第三方類的分類名稱添加前綴ide
勿在分類中聲明屬性post
把封裝數據所用的所有屬性都定義在主接口裏;fetch
在「class-continuation」分類以外的其餘分類中,能夠定義存取方法,但儘可能不要定義屬性。優化
編者按:在不少第三方開源庫中,使用「關聯對象」來在分類中定義屬性是很常見的手段。ui
使用「class-continuation」分類隱藏實現細節this
經過協議提供匿名對象atom
在委託代理中,若是要頻繁檢查該代理是否響應某個方法,那麼將代理相應能力緩存起來達到優化。而優化的最佳途徑就使用「位段」。「位段」是一個C語言數據類型。url
關於位段,簡要作個說明:
struct bs{
int a:1;
int :2; //無位段名,它只用來做填充或調整位置
int b:3;
int :0; //空域
int c:5; //從下一單元開始存放
};
struct bs data;
複製代碼
或者:
struct bs{
int a:1;
int :2;
int b:3;
int :0;
int c:5;
} data;
複製代碼
#include <stdio.h>
int main(){
struct{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit, *pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d, %d, %d\n", bit.a, bit.b, bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d, %d, %d\n", pbit->a, pbit->b, pbit->c);
return 0;
}
複製代碼
@class HONetworkFetcher;
@protocol NetworkFetcherDelegate <NSObject>
@optional
- (void)networkFetcher:(HONetworkFetcher*)fetcher didReceiveData:(NSData*)data;
- (void)networkFetcher:(HONetworkFetcher*)fetcher didFailerWithError:(NSError *)error;
- (void)networkFetcher:(HONetworkFetcher*)fetcher didUpdateProgerssTo:(float)progress;
@end
@interface HONetworkFetcher : NSObject
@property (nonatomic ,weak) id<HONetworkFetcherDelegate> delegate;
@end
複製代碼
#import "HONetworkFetcher.h"
@interface HONetworkFetcher()
{
struct {
unsigned int didReceiveData :1;
unsigned int didFailedWithError :1;
unsigned int didUpdateProgressTo :1;
} _delegateFlags;
}
@end
@implementation HONetworkFetcher
/** * 在設置代理的時候檢查方法可達性,並緩存起來 */
- (void)setDelegate:(id<HONetworkFetcherDelegate>)delegate
{
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didFailedWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailerWithError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgerssTo:)];
}
@end
複製代碼
Private
的分類中,以隱藏實現細節。分類爲現有類添加新功能,假如多個分類都爲該類添加了同一個方法名的某一個方法,那麼在運行時,會形成該方法名屢次覆蓋,以最後一次覆蓋爲主。假如遇到這種狀況的bug,很難追溯源頭,由於你不知道,其餘人也重寫了該方法。因此爲了不這種狀況的發生,就須要爲分類加上前綴,做爲一個「命名空間」,好比:
@interface NSString (HOG_HTTP)
- (NSString*)hog_urlEncodedString;
@end
複製代碼
即使加了前綴,也難保其餘分類不會覆蓋你所寫的放方法。可是下降了機率。
屬性是封裝數據的方式。在技術上,分類也能夠聲明屬性,可是要避免這種作法。
聲明文件:
@interface HOPerson (Friends)
@property (nonatomic ,strong)NSSet *friends;
@end
複製代碼
實現文件:
@implementation HOPerson(Friends)
@end
複製代碼
這時會發出警告:
HOPerson+Friends.m:11:17: Property 'friends' requires method 'friends' to be defined - use @dynamic or provide a method implementation in this category
HOPerson+Friends.m:11:17: Property 'friends' requires method 'setFriends:' to be defined - use @dynamic or provide a method implementation in this category
複製代碼
要消除警告,要麼添加@dynamic
,要麼添加對應的setter/getter方法。
下面是在實現文件裏添加setter/getter方法:
#import "HOPerson+Friends.h"
#import <objc/runtime.h>
static const char *kFriendPropertyKey = "kFriendPropertyKey";
@implementation HOPerson(Friends)
//@dynamic friends;
- (NSSet *)friends
{
return objc_getAssociatedObject(self, kFriendPropertyKey);
}
- (void)setFriends:(NSSet *)friends
{
objc_setAssociatedObject(self,
kFriendPropertyKey,
friends,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
複製代碼
在本例中,正確的作法是將全部的屬性都定義在主接口裏。主接口是惟一能定義成員變量(數據)的地方。而屬性只是定義實例變量及相關存取方法所用的「語法糖」,因此也應遵循同實例變量同樣的規則。至於分類機制應將其理解爲一種手段,目標在於擴展類的功能,而非封裝數據。
class-continuation和其餘分類不一樣,它必須定義在其所接續的那個類的實現文件裏。其重要之處在於,這是惟一能聲明實例變量的分類,並且此分類沒有特定的實現文件,其中的方法都應定義在類的主實現文件裏。
可參考本文上段中「位段在委託代理模式中的應用」中定義的位段,即實例變量。
在class-continuation中定義實例變量,主要是爲了將細節隱藏起來。
另外,在class-continuation中聲明只有在類的實現代碼中的私有方法也是較爲可取的。在編寫類的實現代碼以前,先在class-continuation中將須要實現的方法原型聲明,而後逐一實現。好比:
@interface HOPerson()
{
NSMutableSet *_internalFriends;
}
- (void)hog_findFriends;
@end
@implementation HOPerson
@end
複製代碼
最後,還有一種狀況,就是對象所遵循的協議只應視爲私有的話,那麼最好也在class-continuation中聲明。好比:
@interface HOPerson()<NSCopying,NSCoding>
{
NSMutableSet *_internalFriends;
}
- (void)hog_findFriends;
@end
@implementation HOPerson
@end
複製代碼