代理模式的两种细分
代理模式也叫委托模式,我们在这统一一下称呼。A类要委托B类来做某事,则A叫“委托者”,因为A委托、托付B帮它做事;把B叫“代理者”,因为B帮助、代理A完成该事。
在代理模式里,代理者需要实现定义有关该“委托之事”规范的协议,说起协议。可以细分为“普通代理协议”和“数据源协议”。这个在UITableVIew中体现得很明显。
** UITableViewDelegate
协议就是普通的代理协议。**
总得来说,在下面这个方法中,** 数据是从“委托者”(UITableView)通过参数流向“代理者”(一般为ViewController)的。**
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
** 而UITableViewDataSource
协议就是所谓的“数据源协议”。**
顾名思义,它为“委托者”(UITableView)提供数据。总得来说,** 它的数据是从“代理者”(当前ViewController)以返回值的形式流向“委托者”(UITableView)的。**
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
代理模式里“普通代理协议”和“数据源协议”有以上数据流向不同的区别,望细辨之。
委托者的代理属性
// YWAlertView.h
#import <UIKit/UIKit.h>
@class YWAlertView;
@protocol YWAlertViewDelegate <NSObject>
- (void)ywalertView:(YWAlertView *)alertView clickBtnIndex:(NSInteger)index;
@end
@interface YWAlertView : UIAlertView
@property (nonatomic, weak)id<YWAlertViewDelegate> delegate;
@end
我们在自定义的YWAlertView中定义了它的代理者的属性delegate来保存、持有代理者。看上面的代码,定义委托者的代理者属性要注意两点:
- 内存管理语义要使用weak。因为此时委托者YWAlertView持有了代理者delegate,而在代理者类ViewController中,一般也要持有YWAlertView对象,这样的话它们两者互相持有,互相保留,则会形成保留坏,互不相让,都不释放。这样就造成了内存泄露。
- 委托者的代理属性的类型是
id<YWAlertViewDelegate>
这在OC中叫 ** 匿名对象 ** ,没有指定它的具体得是什么类,一定得是什么类,它的语义是,遵从YWAlertViewDelegate协议的任何对象。
** 注意:** OC中的匿名对象和概念和其他编程语言中的有所不同,其他编程的匿名对象一般指以内联方式所创建出来的无名类。要注意区分,以免混淆。
细节优化
若协议中的代理是可选实现的,则我们在委托类中调用代理方法时,则需要判断代理者是否已经实现该协议里方法,判断代理者能否响应此选择子。
我们知道有些方法在执行过程中多次调用的,比如NSURLConnection网络下载返回数据的代理方法是多次调用,网络数据是一段一段下载而来的。又比如下面UITextFieldDelegate里的方法,也是多次调用的。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
像下面我们自定义的YWTextfield,它是基于UITextfield而建的,在YWTextField中UITextfieldDelegate的值发生改变时就调用我们协议定义的方法,所以该协议方法会被调用多次。
// YWTextfield.h
#import <UIKit/UIKit.h>
@class YWTextfield;
@protocol YWTextfieldDelegate <NSObject>
- (void)ywtextfield:(YWTextfield *)textfield textValueIsChanging:(NSString *)text;
@end
@interface YWTextfield : UITextField
@property (nonatomic, weak)id<YWTextfieldDelegate> delegate;
@end
// YWTextfield.m
#import "YWTextfield.h"
@interface YWTextfield ()<UITextFieldDelegate>
@end
@implementation YWTextfield
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if([self.delegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)])
{
[self.delegate ywtextfield:self textValueIsChanging:string];
return YES;
}
return NO;
}
@end
其实每次调用前通过respondsToSelector
来检测代理者能否响应,是完全没必要的,因为若代理者本身没变,它不太可能突然不能响应原本可以响应的选择子。所以,这儿有可优化的可能性。比较好的方案是 ** 把代理者能否响应该协议方法这一信息缓存起来,以后每次只通过该缓存判断该协议方法是否被实现。**
像下面的例子,我们重写代理者属性ywDelegate的setter方法,在setter方法中通过``respondsToSelector```判断一次代理者是否实现该协议方法,然后把结果缓存在“段位”变量_ywdelegateFlags中。这样当我们设置某类为委托类的代理时,就已经开始判断它是否实现协议方法,且把结果缓存下来了。
// YWTextfield.m
#import "YWTextfield.h"
// C中的“段位”
struct
{
unsigned int ywtextfieldStartEdit :1;
unsigned int valueIsChanging :1;
// 表示占用一个字节,因此可以代表1 or 0 两个值
}_ywdelegateFlags;
@interface YWTextfield ()<YWTextfieldDelegate>
@end
@implementation YWTextfield
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(_ywdelegateFlags.valueIsChanging)
{
[self.ywDelegate ywtextfield:self textValueIsChanging:string];
return YES;
}
return NO;
}
#pragma mark ---- setter
// 重写ywDelegate属性的setter方法,在内把代理者是否实现该协议方法这一信息缓存下来
- (void)setYwDelegate:(id<YWTextfieldDelegate>)ywDelegate
{
_ywDelegate = ywDelegate;
_ywdelegateFlags.valueIsChanging = [ywDelegate respondsToSelector:@selector(ywtextfield:textValueIsChanging:)];
}
@end
** 注意:** 此处用来缓存代理者能否响应该选择子这一信息的数据结构叫“段位”。
关于分类的一些细节
OC有分类,当我们想给某类添加新方法时,我们可以为该类定义分类,将新方法定义在里面,而不需要定义一个继承于该类的子类。
** 这么说来,OC中为某类添加新方法有两种方案,其一为继承该类定义子类,在子类添加;其二则为添加分类,在分类中定义新方法。**
当一个类很庞大时,我们可以根据功能模块的不同,而将方法分散到几个分类中,这样便于管理。比如,我们把可以像下面这样拆分Person类
// Person主类
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy)NSString *personId;
@property (nonatomic, copy)NSString *name;
@property (nonatomic, strong)NSArray *friends;
@end
// "工作"某块
@interface Person (Work)
- (void)goToCode;
- (void)writeBlog;
@end
// "好友"模块
@interface Person (FriendShip)
- (void)addFriend:(Person *)person;
- (void)removeFriend:(Person *)person;
@end
** 注意:我们把“好友”这一模块拆分出来建立了FriendShip分类,指的是把有关好友的“方法们”拆分出来了,你可别把friends这个属性也拆分进FriendShip分类中。若把属性声明放在了分类中,编译时会给出警告。
要记住: 把所有的成员变量好属性声明都应写在主类中。**
网友评论