使用block简化API设计

使用 block 简化 API 设计的小技巧。

在做项目的过程中,常常会遇到这种情况:需要做一些类似的操作,但是又有一些差别,以数据库查询为例,经常需要查询符合某种条件的所有用户,比如性别为男的用户,或者年龄大于45的用户等。通常遇到这种情况,是根据查询条件写多个查询,然后分多个 API 进行实现。大概是下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
+ (NSArray<UserInfo *> *)db_selectMale {
NSString *sql = @"SELECT userInfo FROM UserInfo_t where gender = 1;";
...
return @[];
}
+ (NSArray<UserInfo *> *)db_selectAgeMoreThan:(NSUInteger)age {
NSString *sql = [NSString stringWithFormat:@"SELECT userInfo FROM UserInfo_t where age > %@;", @(age)];
...
return @[];
}

如果查询条件更多的话,是不是写的接口更多了呢?
仔细看一下的话,就会发现,实际上每次查询都是根据一定条件来进行查询,之后的数据组合方式是一样的,那进行优化的方向就是如何传递查询条件。一个是直接写查询的 sql 语句,这样写就失去了效率。系统的 API 在这方面已经给出了范例。

1
2
3
4
[ary sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
// 写排序条件,返回结果
return obj1 > obj2;
}];

通过 block 将排序的条件直接返回给数组。
那我们就可以简单这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (NSArray<UserInfo *> *)db_selectAllCondation:(UserInfo *(^)(UserInfo *info))condation {
__block NSMutableArray *ary = [NSMutableArray array];
__block NSString *sql = @"SELECT userInfo FROM UserInfo_t;";
__block FMResultSet *result = nil;
[[FMDatabaseQueue sharedInstance] inDatabase:^(FMDatabase *db) {
result = [db executeQuery:sql];
while ([result next]) {
UserInfo *info = [UserInfo yy_modelWithJSON:[result dataForColumn:@"userInfo"]];
UserInfo *result = condation(info);
if (result) {
[ary addObject:result];
}
}
[result close];
}];
return ary;
}

这样,上面两个 API 就可以使用这个 API 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+ (NSArray<UserInfo *> *)db_selectMale {
return [self db_selectAllCondation:^UserInfo *(UserInfo *info) {
if (info.gender == 1) {
return info;
} else {
return nil;
}
}];
}
+ (NSArray<UserInfo *> *)db_selectAgeMoreThan:(NSUInteger)age {
return [self db_selectAllCondation:^UserInfo *(UserInfo *info) {
if (info.age > age) {
return info;
} else {
return nil;
}
}];
}

这样查询的条件就可以和 API 独立,并且不影响查询的灵活性。
实际上这样的 API 设计在流行的第三方框架中很常见,比如自动布局框架Masonry