惰性计算的前提:
创建非常大的属性、或者创建对象的时候有一些必要的副作用不能提前创建之外,几乎不应该使用惰性求值来处理类似逻辑。
一种简单的惰性计算:
- (id)someBigProperty
{
if (_someBigProperty == nil) {
NSMutableArray *someBigProperty = [NSMutableArray array];
for (int i = 0; i < 100000; ++i) {
[someBigProperty addObject:@(i)];
}
_someBigProperty = [someBigProperty copy];
}
return _someBigProperty;
}
除了
观察上面的代码,你会发现 _someBigProperty 是一个非常规则的 NSArray,它的 item 内容与下标相等。我们可以看出 item 的结果与 index 存在如下关系:
f(x) = x
那我们现在就基于NSArray这个类簇,实现一个特殊的类吧!
关于类簇,相信很多同学都有所了解,大概的说法是不可以直接继承一个NSArray、NSNumber、NSString这样的类。如果要继承需要实现全部的必要方法,在NSArray这个类簇来说,就是如下的方法:
@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
@property (readonly) NSUInteger count;
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType [])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end
当然除了NSArray类的基本方法,还有NSCopying、NSMutableCopying、NSSecureCoding这些协议需要实现,另外NSFastEnumberation协议已经默认实现完成,不需要额外处理。与惰性计算无关的细节大家可以自己填补,对于本例,我们只需要关心这几个方法的实现:
typedef id(^ItemBlock)(NSUInteger index);
@interface ZDynamicArray : NSArray
- (instancetype)initWithItemBlock:(ItemBlock)block count:(NSUInteger)cnt;
- (id)objectAtIndex:(NSUInteger)index;
- (NSUInteger)count;
@end
按照上文的说法,对于这样一个特殊的NSArray,我们真正要储存的数据只有一个 count 值外加一个函数,所以我们用这两个作为init参数。实现也很简单:
@interface ZDynamicArray()
@property (nonatomic, readonly) ItemBlock block;
@property (nonatomic, readonly) NSUInteger cnt;
@end
@implementation ZDynamicArray
- (instancetype)initWithItemBlock:(ItemBlock)block count:(NSUInteger)cnt
{
if (self = [super init]) {
_block = block;
_cnt = cnt;
}
return self;
}
- (NSUInteger)count
{
return self.cnt;
}
- (id)objectAtIndex:(NSUInteger)index
{
if (self.block) {
return self.block(index);
} else {
return nil;
}
}
@end
瞧,就这么简单的写好了。让我们试一下吧!
ZDynamicArray *array = [[ZDynamicArray alloc] initWithItemBlock:^id(NSUInteger index) {
return @(index);
} count:100000];
for (id v in array) {
NSLog(@"%@", v);
}
NSLog(@"%@", array[15]);
一个看似 10w 数据的数组,其实占用空间微乎其微,但是作用和最开始那样的代码效果一样。很不错吧。大家也可以动手实践,写一些自己需要用到的惰性计算代码,例如一个Model的数组,并非所有的Model都需要用到,我们也可以做成这样的一个数组,等用到的时候再从NSDicitonary转换成Model。就像这样:
NSArray *downloadData = @[@{}, @{}, @{}, @{}];
NSArray *modelArray = [[ZDynamicArray alloc]initWithItemBlock:^id(NSUInteger index) {
return [SomeModel modelFromDictionary:downloadData[index]];
} count:downloadData.count];