情谊提醒:点击查看本文所波及的demo代码
导语
代理即是代表受权方处理事务(From Wikipedia)。
思考一下咱们生存中什么时候会用到代理呢?
租房、买房时,咱们须要一位中介帮咱们分割房东,解决手续上的事件,升高咱们和房东的沟通老本。叫外卖时,咱们须要外卖小哥帮咱们送外卖,好让咱们有更多工夫去专一别的事件。
所以能够了解为中介帮咱们解决两个层面上的问题:
- 缩小相互依赖的问题
- 缩小做反复的事件
所以从实质上来说,Proxy体现的还是"中间层"的设计思维,具体利用于"音讯转发"的业务场景。
循环援用
在讲述明天这个Demo前,咱们先回忆一下之前咱们接触过的Proxy的利用场景,我想你脑海中必定第一工夫浮现出:应用Proxy解决NSTimer循环援用的问题。
所以咱们首先聊一聊 Proxy 应用最刚需的 「解决循环援用」的场景。
循环援用是怎么产生的
下图是内存失常回收的过程:
上面是产生循环援用导致内存透露的过程:
验证是否产生循环援用的最佳形式就是判断是否产生了一个援用环。
NSTimer 循环援用问题
NSTimer 问题最乏味的点是,网上对于 NSTImer 为什么会导致循环援用的解释 80%都是不清晰的,比方这样一个最广泛的说法:
这样的说法就好似有人问小明:"NSTimer为什么会导致循环援用?"
小明却答复:"NSTimer会导致循环援用"。
演出了一出"搁着搁着呢"的好戏。
循环援用肯定是 ViewController 和 NSTimer 互相强援用,但为什么 NSTimer addTarget 会导致循环援用,但平时咱们应用的 UIButton addTarget却不会导致循环援用呢?
答复分明这个问题,才算是说分明了"NSTimer为什么会导致循环援用"。
其实解答这个问题也很简略 咱们查一下大苹果提供的文档阐明,
对于 UIControl :
The control does not retain the object in the target parameter. It is your responsibility to maintain a strong reference to the target object while it is attached to a control.
翻译成中文含意就是: 控件不保留指标参数中的对象。在指标对象附加到控件时,保护对指标对象的强援用是您的责任
对于 NSTimer 阐明如下:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
中文含意是:当定时器触发时,要发送由aSelector指定的音讯到的对象。计时器保护对该对象的强援用,直到它(计时器)生效。
这样一看就明确多了,之所以 NSTimer 会导致强援用,但 UIControl 不会导致强援用,是大苹果的feature,达到了真正的类比深刻的成果。
破解 NSTimer 循环援用的办法咱们都很纯熟了,咱们贴一张图即可:
简略小Demo
开发时咱们常常会写出这样的代码:
- (UIButton *)closeBtn { if (!_closeBtn) { _closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 150, 50)]; [_closeBtn setTitle:@"敞开" forState:UIControlStateNormal]; [_closeBtn addTarget:self action:@selector(onClickCloseBtn) forControlEvents:UIControlEventTouchUpInside]; } return _closeBtn;}- (void)onClickCloseBtn { if (self.delegate && [self.delegate conformsToProtocol:@protocol(NSTimerViewControllerDelegate)]) { [self.delegate onClickTimerAction]; }}
音讯转发流程是: click button -> button selector -> delegate method,
而实际上咱们只须要: click button -> delegate method
咱们想省去 button selector 这个步骤,怎么做呢?
既然是音讯转发的事件,那就采纳 Proxy 的思路:
#import "DelegateMethodProxy.h"#import <UIKit/UIKit.h>#import <objc/runtime.h>@interface DelegateMethodProxy ()@property (nonatomic, copy) dispatch_block_t block;@end@implementation DelegateMethodProxy+ (instancetype)initWithBlock:(dispatch_block_t)block { DelegateMethodProxy *proxy = [[DelegateMethodProxy alloc] init]; proxy.block = block; return proxy;}- (void)addClickActionToButton:(UIButton *)view { objc_setAssociatedObject(view, @"DelegateMethodProxy", self, OBJC_ASSOCIATION_RETAIN); [view addTarget:self action:@selector(onClick) forControlEvents:UIControlEventTouchUpInside];}- (void)onClick { if (self.block) { self.block(); }}@end
优化 UIButton 的调用办法:
- (UIButton *)closeBtn { if (!_closeBtn) { _closeBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 150, 50)]; [_closeBtn setTitle:@"敞开" forState:UIControlStateNormal]; [DelegateMethodProxy initWithBlock:^{ if (self.delegate && [self.delegate conformsToProtocol:@protocol(NSTimerViewControllerDelegate)]) { [self.delegate onClickTimerAction]; } }] addClickActionToButton:_closeBtn]; [_closeBtn addTarget:self action:@selector(onClickCloseBtn) forControlEvents:UIControlEventTouchUpInside]; } return _closeBtn;}
如此一来,业务层就间接实现了 click button -> delegate method 的调用链路。
**这个公众号会继续更新技术计划、关注业内技术动向,关注一下老本不高,错过干货损失不小。
↓↓↓**