四.使用关联引用为分类添加数据
虽然不能在分类中创建实例变量,但是可以创建关联引用(associative reference)。通过关联引用,你可以向任何对象中添加键-值(key-value)数据。
假如有一个Person类,这个person类可能会被用在其他程序中,有些电子邮件地址(emailAddress)这个字段是有意义的,有些时候这个字段是没有用的。一个比较好的解决方案就是使用分类为Person类添加一个名为emailAddress的属性,这样可以避免不需要emailAddress时的开销。或许Person类并不是你写的,改类的维护者也不会为你添加这个属性。这种情况下,要怎么解决问题看?首先看一下Person类的定义:
@interface Person:NSObject
@property (nonatomic, readwrite, copy) NSString *name;
@end;
@implementation Person
@end
现在使用关联引用在分类中添加一个emailAddress属性:
#import<objc/runtime.h>
@interface Person(EmailAddress)
@property (nonatomic, readwrite, copy) NSString *emailAddress;
@end
@implementation Person (EmailAddress)
static char emailAddressKey;
- (NSString *)emailAddress{
return objc_getAssociatedObject(self, &emailAddressKey);
}
- (void)setEmailAddress:(NSString *)emailAddress{
objc_setAssociatedObject(self, &emailAddressKey,emailAddress,OBJC_ASSOCIATION_COPY);
}
@end
可以看出,关联引用是基于键(key)的内存地址的,而不是键的值。在emailAddressKey中储存的内容并不重要,但是它需要一个唯一地址,所以通常使用一个未赋值的static char 作为键。
如果要在警告面板中关联一个对象,使用关联引用是一个非常好的方式。例:
#import "ViewController.h"
#import <objc/runtime.h>
@implementation ViewController
static const char kRepresentedObject;
- (IBAction)doSomething:(id)sender {
UIAlertView *alert = [[UIAlertViewalloc]
initWithTitle:@"Alert" message:nil
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
objc_setAssociatedObject(alert, &kRepresentedObject,
sender,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[alert show];
}
- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex {
UIButton *sender = objc_getAssociatedObject(alertView,
&kRepresentedObject);
self.buttonLabel.text = [[sender titleLabel] text];
}
@end
五.类扩展(class extension)
类扩展的声明方式跟分类很像,只不过括号内的名字是空的:
@interface MyObject()
- (void)doSomething;
@end
类扩展是在.m文件中声明私有方法的一个很棒的方式。不同于分类,在类扩展中声明的方法与在类中声明的方法是完全一致的,这些方法必须全部实现(分类的方法不必全都实现),且在编译时被添加到类中,而分类是在运行时添加。在类扩展中也可以声明合成属性。
六.协议
协议跟类一样可以继承。协议应该总是继承<NSObject>,就像类总是继承NSObject一样。委托协议(delegate protocol)的第一个参数总是委托对象。正是因此,一个委托才能管理多个委托对象。比如,一个控制器就可以作为多个UIAlertView实例的委托来使用。注意在委托对象之外是否还有其他参数,这对命名约定有一定影响。如果没有参数,类名应该最后出现(numberOfSectionsInTableView:);如果有其他参数,类名要首先出现,作为它自身的参数(tableView:numberOfRowsInSection:)。
创建协议之后,通常还需要一个属性以便于操作它,通常使用id<Protocol>的方式来声明:
@property(nonatomic, readwrite, weak) id<MyDelegate> delegate;
这表示“与MyDelegate协议相容的任何对象”。同事使用类和协议声明一个属性也是可以的,而且可以使用多个协议:
@property(nonatomic, readwrite, weak) MyClass *<MyDelegate,UITableViewDelegate> delegate;
这个属性声明是说delegate必须是MyClass或其子类的对象,而且必须同时与<MyDelegate>和<UITableViewDelegate>协议相容。
六.单例
建议使用Grand Center Dispatch(CCD):
+ (MYSingleton *)sharedSingleton{
static dispatch_once_t pred;
static MYSingleton *instance = nil;
dispatch_once(&pred, ^{instance = [[self alloc] init];});
return instance;
}
这样编写方便、快速,而且线程安全。其他方法要在+sharedSingleton中添加一个@synchronize以达到线程安全的目的,但是这种做法在每次调用+sharedSingleton时都会导致性能显著下降。另外,还可以使用+initialize,但使用GCD的方法最简单。
强制执行单例:
+ (MYSingleton *)sharedSingleton{
static dispatch_once_t pred;
static MYSingleton *instance = nil;
dispatch_once(&pred, ^{instance = [[self alloc] init];});
return instance;
}
- (id)init{
//禁用调用-init或new
NSAssert(NO, @"Cannot create instance of Singleton");
//在这里,你可以返回nil或[self initSingleton],由你来决定是返回nil还是返回[self...]
return nil;
}
//真正的(私有)init方法
- (id)initSingleton{
self = [super init];
if(self){
//初始化代码
}
return self;
}