这是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
复制代码