本片文章主要是展示函数式方法的实现,所以代码篇幅占比较多,希望可以帮助到仍然在使用
objective-c语言开发的同学
背景说明
在编写程序中适当的使用高阶函数,链式调用,柯里化函数可以使我们的代码更具可读性,更加优雅,所以现代化的高级语言swift
kotlin
python
dart
go
都无一例外的原生支持函数式的特性,其中一个支持就是集合或者容器类型中定义了很多函数操作。 而相对较老的objective-c
语言对函数式编程的支持则比较弱,并且缺少真正的泛型,但是还好有闭包这个特性至少可以让我们做一些函数式的事情。接下来我将展示通过给集合类NSArray增加分类,并给他添加函数式的方法扩展。
定义支持泛型的分类
为了更方便使用,我们给定义的分类加上泛型
的支持,oc中引入的泛型被称之为轻量级泛型
, 它的作用是使我们操作容器类型时更加便捷,但实际上并不能真正的对类型进行约束。oc中定义泛型类的方式只是在头文件(.h)声明而已,实现文件(.m)中并不能引用头文件中的泛型标记
@interface NSArray<ObjectType> (EEFunction)
- (NSArray<ObjectType> *)ee_select:(BOOL(^)(ObjectType item))function;
- (NSArray<ObjectType> *)ee_filter:(BOOL(^)(ObjectType item))function;
- (NSArray<ObjectType> *)ee_take:(NSUInteger)count;
- (BOOL)ee_any:(BOOL(^)(ObjectType item))function;
- (BOOL)ee_all:(BOOL(^)(ObjectType item))function;
- (NSUInteger)ee_count:(BOOL(^)(ObjectType item))function;
- (NSDictionary<id, NSArray<ObjectType> *> *)ee_groupBy:(id(^)(ObjectType item))function;
- (NSArray<ObjectType> *)ee_sort:(id(^)(ObjectType item))function;
- (NSArray<ObjectType> *)ee_sortDescending:(id(^)(ObjectType item))function;
- (NSArray *)ee_flatMap;
- (NSArray *)ee_flatMap:(id(^)(ObjectType item, NSUInteger idx))function;
- (id)ee_reduce:(id(^)(id result, ObjectType item))function;
- (id)ee_reduce:(id(^)(id result, ObjectType item))function initial:(id)initial;
@end
.h文件中的ObjectType
在.m文件中都将成为id
类型
实现并测试
@interface EEPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation EEPerson
@end
- (NSArray<EEPerson *> *)generateObjects {
NSMutableArray<EEPerson *> *arr = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
EEPerson *person = EEPerson.new;
person.name = [NSString stringWithFormat:@"%@_%d", i < 5 ? @"man" : @"woman", i + 1];
person.age = 20 + i;
[arr addObject:person];
}
return arr.copy;
}
select 查找
select
是根据提供的选择条件函数
,遍历序列,将符合条件元素组成新的序列
- (NSArray *)ee_select:(BOOL(^)(id item))function {
NSMutableArray *result = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (function(obj)) {
[result addObject:obj];
}
}];
return result.copy;
}
// 查找满了18周岁的人
NSArray<EEPerson *> *original = [self generateObjects];
NSArray<EEPerson *> *result = [original ee_select:^BOOL(EEPerson * _Nonnull item) {
return item.age >= 18;
}];
filter 过滤
filter
是根据提供的过滤条件函数
,遍历序列,将不符合条件元素组成新的序列
- (NSArray *)ee_filter:(BOOL(^)(id item))function {
NSMutableArray *result = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (!function(obj)) {
[result addObject:obj];
}
}];
return result.copy;
}
NSArray<EEPerson *> *original = [self generateObjects];
NSArray<EEPerson *> *result = [original ee_filter:^BOOL(EEPerson * _Nonnull item) {
return item.age % 2 == 0;
}];
// XCTAssertNotNil(result);
// XCTAssertTrue(result.count == 5);
// XCTAssertTrue(result[0].age == 21);
// XCTAssertTrue(result[1].age == 23);
// XCTAssertTrue(result[2].age == 25);
// XCTAssertTrue(result[3].age == 27);
// XCTAssertTrue(result[4].age == 29);
groupBy 分组
groupBy
是根据提供的key值函数
,遍历序列,对元素进行分组,组成一个分组字典,例如根据年龄区间进行分组
- (NSDictionary *)ee_groupBy:(id(^)(id item))function {
NSMutableDictionary *groupedItems = [NSMutableDictionary dictionary];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id key = function(obj);
if (!key) {
key = [NSNull null];
}
NSMutableArray *arrayForKey = [groupedItems objectForKey:key];
if (!arrayForKey) {
arrayForKey = [NSMutableArray array];
[groupedItems setObject:arrayForKey forKey:key];
}
[arrayForKey addObject:obj];
}];
return groupedItems.copy;
}
NSArray<EEPerson *> *original = [self generateObjects];
NSDictionary<NSString *, NSArray<EEPerson *> *> *result = [original ee_groupBy:^id _Nonnull(EEPerson * _Nonnull item) {
return [item.name componentsSeparatedByString:@"_"].firstObject;
}];
// XCTAssertNotNil(result);
// XCTAssertTrue(result.count == 2);
// XCTAssertTrue([result.allKeys containsObject:@"man"]);
// XCTAssertTrue([result.allKeys containsObject:@"woman"]);
// XCTAssertTrue(result[@"man"].count == 5);
// XCTAssertTrue(result[@"woman"].count == 5);
map 映射
map
是根据提供的元素映射闭包,遍历序列,对元素进行逐一映射,映射后的元素放到一个新的数组中,并返回。
在MVVM
架构中,map
操作常用在遍历model
层返回的模型数组将其映射成viewModel
数组
- (NSArray *)ee_map:(id(^)(id item, NSUInteger idx))function {
NSMutableArray *result = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id mapItem = function(obj, idx);
if (mapItem) {
[result addObject:mapItem];
}
}];
return result.copy;
}
NSArray<EEPerson *> *original = [self generateObjects];
NSArray<EEPerson *> *result = [original ee_map:^id _Nonnull(EEPerson * _Nonnull item, NSUInteger idx) {
item.age = 18;
return item;
}];
// XCTAssertNotNil(result);
// XCTAssertTrue(result.count == 10);
// XCTAssertTrue([result ee_all:^BOOL(EEPerson * _Nonnull item) {
// return item.age == 18;
// }]);
flatMap 扁平映射
flatMap
严格来说是映射扁平
, 它是根据提供的映射函数
先将数组进行map
操作,再对map
后的数组进行扁平
降维操作,对于二维数组来说扁平
操作会将数组直接变成一维数组,而对一维数组进行扁平
操作将不会有任何效果;另外swift
中的扁平
会优先对可选类型进行解包,如果不是可选类型则正常扁平
- (NSArray *)ee_flatMap {
return [self ee_flatMap:^id _Nonnull(id _Nonnull item, NSUInteger idx) {
return item;
}];
}
- (NSArray *)ee_flatMap:(id _Nonnull (^)(id _Nonnull, NSUInteger))function {
NSArray *tempArray = [self ee_map:function];
BOOL isAllItemArray = [tempArray ee_all:^BOOL(id _Nonnull item) {
return [item isKindOfClass:NSArray.class];
}];
if (isAllItemArray) {
NSMutableArray *flatArray = [NSMutableArray array];
[tempArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[flatArray addObjectsFromArray:obj];
}];
return flatArray.copy;
} else {
return tempArray;
}
}
NSMutableArray<EEPerson *> *arr1 = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
EEPerson *person = EEPerson.new;
person.name = [NSString stringWithFormat:@"man_%d", i + 1];
[arr1 addObject:person];
}
NSMutableArray<EEPerson *> *arr2 = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
EEPerson *person = EEPerson.new;
person.name = [NSString stringWithFormat:@"woman_%d", i + 1];
[arr2 addObject:person];
}
NSArray *original = @[arr1, arr2];
NSArray *result = [original ee_flatMap];
// XCTAssertNotNil(result);
// XCTAssertTrue(result.count == 5 + 5);
reduce 归纳累计
reduce
通过接收一个累积函数
并作用于序列的所有元素,最终累积
为一个值
但需要注意的是,reduce
的累积函数
并不能对所有元素作转换
后再累积
,例如求整型数组的平方和使用reduce
函数并不能直接得到结果,应该使用map
对数组元素进行转换之后再reduce
;
- (id)ee_reduce:(id(^)(id result, id item))function {
return [self ee_reduce:function initial:nil];
}
- (id)ee_reduce:(id(^)(id result, id item))function initial:(id)initial {
if (!function) {
return nil;
}
__block id result = initial;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx == 0 && !result) {
result = obj;
} else {
result = function(result, obj);
}
}];
return result;
}
// 原始数组
NSArray *arr = @[@1, @4, @6, @2];
// 求数组的平方和
NSNumber *num1 = [[arr ee_map:^id(NSNumber *item, NSUInteger idx) {
return @(item.integerValue * item.integerValue);
}] ee_reduce:^id(NSNumber *result, NSNumber *item) {
return @(result.integerValue + item.integerValue);
}];
// XCTAssertTrue(num1.integerValue == 57);
// 将数组连接成整型数
NSNumber *num2 = [arr ee_reduce:^id(NSNumber *result, NSNumber *item) {
return @(result.integerValue * 10 + item.integerValue);
}];
// XCTAssertTrue(num2.integerValue == 1462);
其他函数
集合类的函数式的便捷方法可以有很多,可以根据自己的实际需要添加扩展,以下是其他常见的函数以及描述:
- first 第一个符合条件的元素
- any 是否有任意一个符合条件
- all 是否所有元素都符合条件
- count 符合条件的个数
- sum 统计求和
... ...
集合的函数式方法不仅可以简化我们的代码,也可以培养我们编码的函数式思维。实际上除了集合类,字典类外,
字符串
等可序列的类型
都可以添加类似集合的函数式的通用操作支持。
欢迎拍砖留言