设计模式-责任链

0x01 What?

类似车间流水线的模式,各环节依次处理好自己的事.
工程中常用于拦截器的设计.
有单程链,往返链,

1.0 优缺点

  • 优点:
    1. 每个环节专注做好自己的事
    2. 很容易新增/替换/删除节点,交换节点顺序
  • 缺点:
    1. 一件完整的事务被细分为多个环节,增加了各环节的维护成本
    2. 代码调试不容易,因为可以会出现循环递归调用

0x02 How?

场景一:

评论系统要屏蔽一些用户回复,比如一些敏感字眼, HTML标签, 还有一些其它的规则

完整代码请在Github以下目录查看,结合单元测试查看效果更加

DesignPatterns/DesignPatterns/FilterChain/TextFilter/*
DesignPatterns/DesignPatternsTests/FilterChainTests.m

step1: 创建过滤屏蔽协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
FOUNDATION_EXTERN NSString *const kHTMLTag_OLD;
FOUNDATION_EXTERN NSString *const kHTMLTag_NEW;

NS_INLINE NSDictionary* makeFilterRule(NSString *htmlTag, NSString *aString){
return @{kHTMLTag_OLD:htmlTag,
kHTMLTag_NEW:aString
};
}

@protocol StringFilter <NSObject>

@required
- (NSString *)filterString:(NSString *)aString;

@optional
@property (nonatomic,strong) NSMutableArray<NSDictionary *> *filteRules;
/*
添加过滤规则 eg: {kHTMLTag_OLD: @"<" ,kHTMLTag_NEW: @"["}
将字符串中的html标签替换为其它字符串
*/
@optional
- (void)addFilteRule:(NSDictionary *)rule;

@end

step2: 创建各种需要的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

//HTML标签过滤器
@interface HTMLFilter : NSObject<StringFilter>
@end

@implementation HTMLFilter
@synthesize filteRules;

- (instancetype)init{
if (self = [super init]) {
filteRules = [NSMutableArray array];
}
return self;
}

- (void)addFilteRule:(NSDictionary *)rule{
[self.filteRules addObject:rule];
}

- (NSString *)filterString:(NSString *)aString{
for (NSDictionary *rule in self.filteRules) {
NSString *old = rule[kHTMLTag_OLD];
NSString *replaced = rule[kHTMLTag_NEW];
aString = [aString stringByReplacingOccurrencesOfString:old withString:replaced];
}
return aString;
}

@end

//敏感字过滤器
@interface SensitiveFilter : NSObject<StringFilter>
@end

@implementation SensitiveFilter
- (void)addFilteRule:(NSDictionary *)rule{
[self.filteRules addObject:rule];
}

- (NSString *)filterString:(NSString *)aString{
for (NSDictionary *rule in self.filteRules) {
NSString *old = rule[kHTMLTag_OLD];
NSString *replaced = rule[kHTMLTag_NEW];
aString = [aString stringByReplacingOccurrencesOfString:old withString:replaced];
}
return aString;
}
@end

//其它过滤器
@interface OtherFilter : NSObject<StringFilter>
@end

step3: 创建响应链条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//StringFilterChain.h
//由若干个环节组成一个链条,每个环节实现同一协议
@interface StringFilterChain : NSObject

//添加过滤器
- (void)addFilter:(id<StringFilter>)filter;

//添加过滤器
- (StringFilterChain* (^)(id<StringFilter> filter))addFilter;

//执行过滤任务
- (NSString *)doFilterFor:(NSString *)aString;

@end

//StringFilterChain.m
- (NSString *)doFilterFor:(NSString *)aString{
for (id<StringFilter> filter in self.filters) {
aString = [filter filterString:aString];
}
return aString;
}

step4: 构建响应链对象并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
StringFilterChain *chain = [[StringFilterChain alloc] init];
// id<StringFilter> filter1;
// id<StringFilter> filter2;
// id<StringFilter> filter3;
//
{
HTMLFilter *filter = [[HTMLFilter alloc] init];
[filter addFilteRule:makeFilterRule(@"<", @"[")];
[filter addFilteRule:makeFilterRule(@">", @"]")];
[filter addFilteRule:makeFilterRule(@"&", @" and ")];
[chain addFilter:filter];
// filter1 = filter;
}

{
SensitiveFilter *filter = [[SensitiveFilter alloc] init];
[filter addFilteRule:makeFilterRule(@"共产党", @"伟大领袖")];
[filter addFilteRule:makeFilterRule(@"敏感", @"不敏感")];
[filter addFilteRule:makeFilterRule(@"中国男足", @"男猪")];
[chain addFilter:filter];
// filter2 = filter;
}

{
OtherFilter *filter = [[OtherFilter alloc] init];
[chain addFilter:filter];
// filter3 = filter;
}

// chain.addFilter(filter1).addFilter(filter2).addFilter(filter3);
NSString *result = [chain doFilterFor:@"<真是太敏感了> & 共产党万岁, other ..中国男足"];
NSString *expected = @"[真是太不敏感了] and 伟大领袖万岁, 其它的过滤器 ..男猪";
XCTAssertTrue([result isEqualToString:expected]);

场景二:

对网络请求添加各种前置后置处理(黑白名单,参数校验,安全签名加密等)

完整代码请在Github以下目录查看,结合单元测试查看效果更加

DesignPatterns/DesignPatterns/FilterChain/NetworkingFilter/*
DesignPatterns/DesignPatternsTests/FilterChainTests.m

step1: 创建协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NS_INLINE NSError* makeFilterError(NSUInteger errorCode, NSString *desc){
return [NSError errorWithDomain:@"com.finderTiwk"
code:errorCode
userInfo:@{NSLocalizedDescriptionKey:desc}];
}

@class NetworkingFilterChain;
@protocol NetworkingFilter <NSObject>

- (void)doFilterForRequest:(FTURLRequest *)request
response:(FTURLResponse *)response
chain:(NetworkingFilterChain *)chain
error:(NSError **)error;
@end

step2: 创建各种需要的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

//黑名单过滤器
@interface NotFoundFilter : NSObject<NetworkingFilter>
@end

@implementation NotFoundFilter

- (void)doFilterForRequest:(FTURLRequest *)request
response:(FTURLResponse *)response
chain:(NetworkingFilterChain *)chain
error:(NSError **)error{

[self checkRequest:request
error:error];

[chain doFilterForRequest:request
response:response
chain:chain
error:error];

}

- (void)checkRequest:(FTURLRequest *)aRequest
error:(NSError **)error{
if ([self.blackList containsObject:aRequest.interface]) {
*error = makeFilterError(-1, @"404 Not Found!");
}
}

@end


//参数校验过滤器
@interface ParameterFilter : NSObject<NetworkingFilter>
@end


@implementation ParameterFilter

- (void)doFilterForRequest:(FTURLRequest *)request
response:(FTURLResponse *)response
chain:(NetworkingFilterChain *)chain
error:(NSError **)error{

[self checkRequest:request
error:error];

[chain doFilterForRequest:request
response:response
chain:chain
error:error];
if (*error) {
return;
}

[self checkResponse:response
error:error];
}



- (void)checkRequest:(FTURLRequest *)aRequest
error:(NSError **)error{

if (!aRequest.domain ||
aRequest.domain.length == 0) {
*error = makeFilterError(-1, @"请求域名为空");
return;
}

if (!aRequest.interface ||
aRequest.interface.length == 0) {
*error = makeFilterError(-1, @"请求API为空");
return;
}

if (!aRequest.httpHeadField ||
aRequest.httpHeadField.allKeys.count == 0) {
*error = makeFilterError(-1, @"请求头为空");
return;
}
}

- (void)checkResponse:(FTURLResponse *)aResponse
error:(NSError **)error{

NSInteger responseCode = aResponse.statusCode;
if (responseCode != 200) {

NSString *desc = [NSString stringWithFormat:@"网络异常,请重试(%@)",@(responseCode)];
*error = makeFilterError(aResponse.statusCode, desc);
return;
}

if (!aResponse.retData ||
aResponse.retData.allKeys.count == 0) {
*error = makeFilterError(-1, @"返回数据为空");
return;
}
}

@end


//安全签名过滤器
@interface NetworkingSecretFilter : NSObject<NetworkingFilter>
@end

@implementation NetworkingSecretFilter

- (void)doFilterForRequest:(FTURLRequest *)request
response:(FTURLResponse *)response
chain:(NetworkingFilterChain *)chain
error:(NSError *__autoreleasing *)error{

[self encryptionFor:&request];

[chain doFilterForRequest:request
response:response
chain:chain
error:error];

if (*error) {
return;
}

[self decryptionFor:response
error:error];
}


//做加密处理
- (void)encryptionFor:(FTURLRequest **)aRequest{

[(*aRequest) addValue:@"FinderTiwk" forHTTPHeaderField:@"sign"];
[(*aRequest) addValue:@"findertiwk.github.io" forHTTPHeaderField:@"host"];
}


//做验签处理
- (void)decryptionFor:(FTURLResponse *)aResponse
error:(NSError **)error{

NSArray *allHeadKeys = aResponse.allHeaderFields.allKeys;
if (![allHeadKeys containsObject:@"host"] ||
![allHeadKeys containsObject:@"sign"]) {
*error = makeFilterError(-1, @"无效的响应头");
return;
}

if (![aResponse.allHeaderFields[@"sign"] isEqualToString:@"FinderTiwk"]) {
*error = makeFilterError(-1, @"响应数据加密签名验证失败");
return;
}
}

@end

step3: 创建响应链条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

//NetworkingFilterChain.h
@interface NetworkingFilterChain : NSObject<NetworkingFilter>

- (NetworkingFilterChain* (^)(id<NetworkingFilter> filter))addFilter;

@end

//NetworkingFilterChain.m
@interface NetworkingFilterChain()
@property (nonatomic,strong) NSMutableArray<id<NetworkingFilter>> *filters;
@property (nonatomic,assign) NSUInteger index;
@end

@implementation NetworkingFilterChain


- (instancetype)init{
if (self = [super init]) {
_filters = [NSMutableArray array];
}
return self;
}

- (NetworkingFilterChain *(^)(id<NetworkingFilter>))addFilter{
return ^(id<NetworkingFilter> filter){
[self.filters addObject:filter];
return self;
};
}


#pragma mark - NetworkingFilter
- (void)doFilterForRequest:(FTURLRequest *)request
response:(FTURLResponse *)response
chain:(NetworkingFilterChain *)chain
error:(NSError *__autoreleasing *)error{
if (*error) {
self.index = 0;
return;
}
if (self.index == self.filters.count) {
self.index = 0;
return;
}
id<NetworkingFilter> filter = self.filters[self.index++];
[filter doFilterForRequest:request
response:response
chain:self
error:error];

}

@end

step4: 构建并使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//FilterChainTests.m
- (void)setUp {
[super setUp];
self.networkingChain = [[NetworkingFilterChain alloc] init];
self.networkingChain.addFilter([NotFoundFilter new]);
self.networkingChain.addFilter([ParameterFilter new]);
self.networkingChain.addFilter([NetworkingSecretFilter new]);

self.request = [[FTURLRequest alloc] init];
self.request.requestMethod = FTRequestMethod_GET;
self.request.domain = @"https://api.github.com";
self.request.interface = @"/orgs/octokit/repos";
[self.request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[self.request addParam:@"finderTiwk" forKey:@"usrename"];
[self.request addParam:@(1514739661000) forKey:@"timestamp"];

self.response = [[FTURLResponse alloc] init];
self.response.statusCode = 200;
self.response.retData = @{
@"code":@"0",
@"result":@{
@"name":@"FinderTiwk",
@"age":@"28",
@"email":@"136652711@qq.com",
}
};
NSError *error;
[self.networkingChain doFilterForRequest:self.request
response:self.response
chain:self.networkingChain
error:&error];
XCTAssertNil(error);
}

0x03 iOS系统中的响应链设计模式的应用

事件传递响应机制

1. 事件传递

UIApplication–>UIWindow–>递归找到最适配的事件接收者(遍历视图子控件时,从后向前,因为后加上来的控件在最上面)

1
2
3
4
5
6
7
8
9
10
/*
UIView不能接收触摸事件的三种情况:
不接受用户交互:userInteractionEnabled = NO;
隐藏:hidden = YES;
透明:alpha = 0.0~0.01
*/

//通过这两个方法来寻找能响应事件的控件
- hitTest:withEvent:
- pointInside: withEvent:

2. 事件响应

最适合的控件——>扔给父控制直到最上层控件 —-> 最终到UIApplication,如果处理不了的话事件丢弃