乐趣区

关于objective-c:Proxy消息转发实战

情谊提醒:点击查看本文所波及的 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 的调用链路。


** 这个公众号会继续更新技术计划、关注业内技术动向,关注一下老本不高,错过干货损失不小。
↓↓↓**

退出移动版