代理设计模式
知识点掌握
- 什么是设计模式
- 代理设计模式的作用
- 掌握 iOS 开发中,代理设计模式的工作原理和代码实现
什么是设计模式
- 设计模式的产生是在无数份代码的经验累积下产生的
- 每一个设计模式用于解决一种问题
- 在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案
- 设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案
面向对象设计模式
通常以类或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类或对象
代理设计模式
- 代理设计模式的核心就是“解耦”
代理设计模式的代码实现
目标
- 用
伪代码
的方式体验代理设计模式的工作原理和 iOS 中代理设计模式的编写方法 - 模拟目标:缩放视图
准备工作
- 新建项目
- 新建视图
CZScrollView
继承自UIView
模拟滚动视图
/// 模拟 ScrollView
@interface CZScrollView : UIView
@end
- 在
CZScrollView
实现以下代码,当用户创建视图时,调用 setupUI 方法设置界面
@implementation CZScrollView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupUI];
}
return self;
}
#pragma mark - 设置界面
- (void)setupUI {
}
@end
- 在
ViewController
中导入头文件
#import "CZScrollView.h"
- 实现以下代码添加滚动视图
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
#pragma mark - 设置界面
- (void)setupUI {
CZScrollView *sv = [[CZScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
sv.center = self.view.center;
sv.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:sv];
}
@end
模拟系统交互缩放视图
要求,增加一个按钮,点击按钮缩放视图
在 CZScrollView
的 setupUI
方法中添加以下代码
// 1. 添加按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = self.center;
[self addSubview:btn];
- 定义监听方法
#pragma mark - 监听方法
- (void)缩放给定的视图 {
}
- 在
setupUI
方法中给按钮添加监听方法
// 2. 添加监听方法
[btn addTarget:self action:@selector(缩放给定的视图) forControlEvents:UIControlEventTouchUpInside];
- 准备实现监听方法,模拟缩放视图
问题来了
不知道要缩放谁?
代理设计模式闪亮登场!
第一步 —— 协议 & 代理
- 在
CZScrollView
中定义代理属性
/// 我的代理,负责告诉我我来缩放谁
@property (nonatomic, weak) id delegate;
滚动视图对代理的要求
- 负责告诉我缩放谁
- 滚动视图不关心代理的类型 ——
id
,只要你告诉我,我就给你干活提示:
weak
属性单独讲矛盾点出来了:
代理怎么告诉滚动视图呢?——
协议
协议的特点:
- 只是定义方法,不负责实现
- <遵守> 协议方法的对象负责实现协议方法
- 可以给 <遵守> 协议方法的对象发送消息
- 定义协议,新建
.h
文件CZScrollViewDelegate.h
- 编写以下代码定义协议方法
#import <UIKit/UIKit.h>
/**
CZScrollViewDelegate 定义协议
<NSObject> 表示任何一个 NSObject 都可以遵守协议
*/
@protocol CZScrollViewDelegate <NSObject>
- (UIView *)要缩放的视图;
@end
- 在
CZScrollView.h
导入头文件
#import "CZScrollViewDelegate.h"
- 修改
delegate
属性的描述,指定代理遵守协议
@property (nonatomic, weak) id<CZScrollViewDelegate> delegate;
- 实现按钮监听方法
#pragma mark - 监听方法
- (void)缩放给定的视图 {
// 1. 让代理告诉我来缩放谁
UIView *v = [_delegate 要缩放的视图];
// 2. 缩放视图
CGFloat scale = 0.9;
v.transform = CGAffineTransformScale(v.transform, scale, scale);
}
代码实现至此,代码结构图如下:
第二步 —— 视图控制器
- 添加图像素材
003.png
- 修改
setupUI
方法给 滚动视图添加UIImageView
// 给 scrollView 添加图像
UIImage *image = [UIImage imageNamed:@"003"];
UIImageView *iv = [[UIImageView alloc] initWithFrame:sv.bounds];
iv.image = image;
[sv insertSubview:iv atIndex:0];
- 设置
滚动视图
的代理
// 设置 scrollView 的代理 - 想知道缩放谁吗?问我
sv.delegate = self;
出现警告信息
Assigning to 'id' from incompatible type 'ViewController *const __strong'ViewController 没有遵守 CZScrollViewDelegate 协议
此事运行程序会崩溃,崩溃原因是:
-[ViewController 要缩放的视图]: unrecognized selector sent to instance 0x7fb9fad4be20
- 在私有扩展遵守协议
@interface ViewController () <CZScrollViewDelegate>
细节:因为
CZScrollView.h
已经#import
了CZScrollViewDelegate.h
,所以在视图控制器中不需要再次#import
- 实现协议方法
#pragma mark - CZScrollViewDelegate
- (UIView *)要缩放的视图 {
return nil;
}
再次运行,因为控制器已经实现了协议方法,所以不会再崩溃了
- 定义属性记录图像视图
@property (nonatomic, weak) UIImageView *imageView;
- 在
setupUI
方法中记录图像视图
_imageView = iv;
- 修改协议方法
#pragma mark - CZScrollViewDelegate
- (UIView *)要缩放的视图 {
return _imageView;
}
运行程序,搞定!:D,示意图如下
代理属性为什么要是弱引用?
- 为了避免
循环引用
!- 因为视图控制器强引用 view
- view 强引用 subviews
- 在通过 addSubview 方法添加子视图的时候,subviews 数组会对 scrollview 强引用
- 如果 scrollview 对 delegate 强引用,那么当
sv.delegate = self;
时,相当于 scrollView 对视图控制器做了强引用