前言:什么是设计模式?
百度百科:设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化。
换句话说:设计模式其实就是一套被大家普遍接受了的开发规范,在特定的场景使用特定的设计模式,有助于提高代码的阅读性、健壮性、可维护性和可重用性,好处多多。
目录
1、什么是代理设计模式
2、怎么使用代理设计模式
3、代理设计模式的实现原理
4、使用代理设计模式需要注意的地方
5、代理设计模式的应用场景
1、什么是代理设计模式
首先要知道,代理设计模式只是协议的一种用途,协议有很多用途,代理设计模式只是其中的一种,不能将代理设计模式等同于协议。
所谓代理设计模式是指一个类的某些事情由别的类来实现,而不是它自己实现。代理设计模式是由委托方、代理方、协议、代理属性和代理对象五部分组成的,我们要想实现代理设计模式首先要做的就是要找到委托方和代理方,然后把委托方想要代理方做的事情放在协议里,而代理属性则是委托方中的一个变量,代理对象是代理方的一个实例。
2、怎么使用代理设计模式
可参看第5小节代理传值的例子。
3、代理设计模式的实现原理
代理设计模式实现的原理其实就是把代理对象的内存传递给代理属性 + 把协议方法转移给代理方。
这里需要牵涉一点儿runtime的知识,会有助于我们了解代理设计模式的实现原理,runtime库中对类的定义如下:
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
可见OC类的本质也是一个结构体,这个结构体里有两个成员变量
methodLists
和protocols
,前者用来存储该类所有的实例方法,后者用来存储该类所遵循的协议。
接下来我们通过一个简单的例子来理解一下代理设计模式的实现原理:比方说现在有一个码农类和一个外卖员类,码农想让外卖员帮忙买午餐。那么在这个应用场景中,我们能找到委托方为码农类,代理方为外卖员类,协议中的方法应该是买午餐,代理属性应该存在于码农类中,代理对象则是外卖员类的一个实例。
- 委托方:创建协议和代理属性
-----------Coder.h-----------
#import <Foundation/Foundation.h>
// 协议
@protocol CoderDelegate <NSObject>
// 买午餐
- (void)buyLunch;
@end
@interface Coder : NSObject
// 代理属性
@property (nonatomic, weak) id<CoderDelegate> delegate;
@end
-----------Coder.m-----------
#import "Coder.h"
@implementation Coder
@end
可见委托方码农类中创建了一个协议和一个代理属性,协议的目的就是在适当的时机把协议中的方法转移到代理方的methodLists
成员变量里,而代理属性的目的则是在适当的时机指向代理对象。
- 代理方:遵循协议,完成协议方法的转移
-----------Deliverer.h-----------
#import <Foundation/Foundation.h>
#import "Coder.h"
@interface Deliverer : NSObject <CoderDelegate>// 遵循协议
@end
-----------Deliverer.m-----------
#import "Deliverer.h"
@implementation Deliverer
// 实现代理方法
- (void)buyLunch {
NSLog(@"我是外卖员,我可以帮码农买午餐");
}
@end
当委托方外卖员类遵循了代理方的协议之后,外卖员类就会把协议里所有的方法都添加到自己的methodLists
里(方法实现与否没有关系,但是你代理的作用就是要帮人家做事情,所以光遵循不实现是没有意义的),以备将来代理对象的调用,这样就完成了协议方法转移给代理方。而委托方的methodLists
是没有这些方法的,因为它本身没有声明这些方法,也没有遵循协议。
- 代理对象的内存传递给代理属性
-----------ViewController.m-----------
- (void)viewDidLoad {
[super viewDidLoad];
Deliverer *deliverer = [Deliverer new];// 外卖员
Coder *coder = [Coder new];// 码农
coder.delegate = deliverer;// 码农设置外卖员为自己的代理
if ([coder.delegate respondsToSelector:@selector(buyLunch)]) {
[coder.delegate buyLunch];// 外卖员去买午餐
}
}
由代码可以看出deliverer变量指向代理对象外卖员,那当我们把deliverer变量赋值给coder的代理属性delegate之后,代理属性delegate也就指向了代理对象外卖员,这样接下来我们给代理属性delegate发送消息,其实就是给代理对象外卖员发送消息,而刚刚好前面代理方已经通过遵循协议把代理方法添加到了自己的methodLists
里,所以可以正常的调用方法。(但是我们依旧需要判断一下代理方是否实现了代理方法,否则给代理对象发送一个没实现方法的话会崩掉。)
4、使用代理设计模式需要注意的地方
使用代理设计模式的时候,用weak修饰代理属性,避免循环引用。
- 我们先来分析一下代理设计模式为什么会造成循环引用:
以tableView的使用为例:如果tableView的delegate属性用strong来修饰,那么delegate属性就会强引用当前控制器,而delegate属性仅仅是tableView对象的一个属性,它的释放依赖于tableView对象的释放,所以换句话说就是tableView对象强引用着当前控制器对象。同理当前控制器的tableView成员变量也是用strong修饰的,所以它也强引用着它所指向的tableView对象,而tableView成员变量也仅仅是当前控制器的一个成员变量而已,它的释放也依赖于当前控制器的释放,所以换句话说就是当前控制器强引用着tableView对象。这样tableView强引用着当前控制器,当前控制器又强引用着tableView,这就造成了对象的循环引用,最终双方都无法正常的释放内存,导致内存泄漏。
- 然后再看weak修饰代理属性是如何打破循环引用的:
了解了循环引用是如何造成的,weak修饰代理属性是如何打破循环引用的其实就很明显了,依旧以tableView的使用为例:用weak修饰了delegate属性之后,delegate属性仅仅是弱引用当前控制器,不会持有当前对象不会使对象的引用计数加1,所以最后的结果就是tableView弱引用当前控制器,当前控制器强引用tableView,所以就打破了循环引用。而之所以用weak而不用assign修饰代理属性,是因为weak在对象释放之后会自动将代理属性置为nil,避免野指针的出现,而assign不会,所以使用weak更安全一些。
5、代理设计模式的应用场景
代理设计模式的应用场景主要有两个:传值和回调。它们的实现方式是一样的,只不过一个目的侧重于传值,另一个目的侧重于回调,所以下面仅通过代理传值来简单演示下代理传值和代理回调的实现,通过block传值来简单演示下block传值和block回调的实现,而把重心留在分析代理和block的选型上。
(1)代理传值block传值
假设现在有一个应用场景是:点击后面一个控制器,前面一个界面需要刷新,不点则不刷新。
- 代理传值
前一个界面
-----------ViewController.h-----------
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
-----------ViewController.m-----------
#import "ViewController.h"
#import "ViewController1.h"
@interface ViewController () <ViewController1Delegate>
@property (nonatomic, assign) BOOL needRefresh;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"===========>需要刷新:%d", self.needRefresh);
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.needRefresh = NO;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ViewController1 *vc1 = [[ViewController1 alloc] init];
vc1.delegate = self;
[self.navigationController pushViewController:vc1 animated:YES];
}
- (void)didTouch:(BOOL)flag {
self.needRefresh = flag;
}
@end
后一个界面
-----------ViewController1.h-----------
#import <UIKit/UIKit.h>
@protocol ViewController1Delegate <NSObject>
- (void)didTouch:(BOOL)flag;
@end
@interface ViewController1 : UIViewController
@property (nonatomic, weak) id<ViewController1Delegate> delegate;
@end
-----------ViewController1.m-----------
#import "ViewController1.h"
@interface ViewController1 ()
@end
@implementation ViewController1
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:@selector(didTouch:)]) {
[self.delegate didTouch:YES];
}
}
@end
- block传值
前一个界面
-----------ViewController.h-----------
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
-----------ViewController.m-----------
#import "ViewController.h"
#import "ViewController1.h"
@interface ViewController ()
@property (nonatomic, assign) BOOL needRefresh;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"===========>需要刷新:%d", self.needRefresh);
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.needRefresh = NO;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
ViewController1 *vc1 = [[ViewController1 alloc] init];
vc1.didTouch = ^(BOOL flag) {
self.needRefresh = flag;
};
[self.navigationController pushViewController:vc1 animated:YES];
}
@end
后一个界面
-----------ViewController1.h-----------
#import <UIKit/UIKit.h>
@interface ViewController1 : UIViewController
@property (nonatomic, copy) void (^didTouch)(BOOL flag);
@end
-----------ViewController1.m-----------
#import "ViewController1.h"
@interface ViewController1 ()
@end
@implementation ViewController1
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (self.didTouch) {
self.didTouch(YES);
}
}
@end
结论:通过对比我们发现,在传值方面,block传值要比代理传值少写很多代码,实现起来感觉更加轻型,所以类似这种简单的从后往前的传值场景可以优先选择block。
(2)代理回调和block回调
首先说明,不能像传值那样肯定----block传值就是比代理传值要轻型很多。代理和block在作为回调使用时,两者没有绝对的谁好谁坏,而是不同的应用场景,应该选择不同的回调方式,因为它俩各有擅长。但到目前为止我遇到的应用场景来看,代理回调和block回调的选型主要是看回调的多少:
-
如果一个东西有很多的回调,我们应该选择代理。比如像tableView这种,它有很多的代理方法和数据源方法,如果系统写成了block的形式,试想一下我们创建一个tableView,它下面就得跟一坨block回调,那简直太恐怖了,代码臃肿的没法看了,而且这么多的代理方法你也不一定是都想实现的,所以使用代理的另一个好处是可以灵活的选择自己想要实现的回调来实现。
-
如果一个东西的回调比较少,我们应该选择block。因为block会使代码更加集中,便于阅读和维护,编写起来也更加轻型。比如说写网络请求的时候,我们最好使用block来作为请求成功或者失败的回调;再比如加载显示器(菊花)自动消失后,如果我们想要给外界暴露一个回调让外界做自己想做的事,最好也是用block回调来做;再比如cell上面有很多button点击事件的回调,那如果太多就用代理来做,如果少就用block做。
但是,代理的性能会高于block,因为使用block的时候总是需要把它从栈区复制到堆区。
网友评论