objective-c集合的函数式扩展

本片文章主要是展示函数式方法的实现,所以代码篇幅占比较多,希望可以帮助到仍然在使用objective-c语言开发的同学

背景说明

在编写程序中适当的使用高阶函数,链式调用,柯里化函数可以使我们的代码更具可读性,更加优雅,所以现代化的高级语言swift kotlin python dart go都无一例外的原生支持函数式的特性,其中一个支持就是集合或者容器类型中定义了很多函数操作。 而相对较老的objective-c语言对函数式编程的支持则比较弱,并且缺少真正的泛型,但是还好有闭包这个特性至少可以让我们做一些函数式的事情。接下来我将展示通过给集合类NSArray增加分类,并给他添加函数式的方法扩展。

定义支持泛型的分类

为了更方便使用,我们给定义的分类加上泛型的支持,oc中引入的泛型被称之为轻量级泛型, 它的作用是使我们操作容器类型时更加便捷,但实际上并不能真正的对类型进行约束。oc中定义泛型类的方式只是在头文件(.h)声明而已,实现文件(.m)中并不能引用头文件中的泛型标记

@interface NSArray (EEFunction)

- (NSArray *)ee_select:(BOOL(^)(ObjectType item))function;
- (NSArray *)ee_filter:(BOOL(^)(ObjectType item))function;
- (NSArray *)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 *> *)ee_groupBy:(id(^)(ObjectType item))function;
- (NSArray *)ee_sort:(id(^)(ObjectType item))function;
- (NSArray *)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 *)generateObjects {
    NSMutableArray *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 *original = [self generateObjects];
NSArray *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 *original = [self generateObjects];
NSArray *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 *original = [self generateObjects];
NSDictionary *> *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是根据提供的key值函数,遍历序列,对元素进行分组,组成一个分组字典。
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 *original = [self generateObjects];
NSArray *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 *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 *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 统计求和
    ... ...

集合的函数式方法不仅可以简化我们的代码,也可以培养我们编码的函数式思维。实际上除了集合类,字典类外,字符串可序列的类型都可以添加类似集合的函数式的通用操作支持。

欢迎拍砖留言

你可能感兴趣的:(objective-c集合的函数式扩展)