Words Of Life

对只读数组进行修改

  • 摘要:关于对只读数组修改的一些思考


前言

  • 在公司里面有个需求,需要通过修改一个只读数组属性来实现。这个需求实现说简单可以很简单,但是有些场景可能就需要经过一些”思考”了,本文将对其进行讨论。

只读数组

首先我们先来定义一下只读数组,究竟什么是只读数组呢?

我们知道,下面这段代码定义了一个最常见的只读数组

1
@property (nonatomic, copy, readonly) NSArray *readonlyData;

我们知道上面这段代码写在不同的地方编译器会为我们生成不同的代码

  • 定义在 Class 中:编译器会为我们合成 _storingData 的实例变量,以及生成 storingData 的 getter
  • 定义在 Category 中:编译器只会为我们生成 storingData 的 getter,此时如果实现了 getter 并返回了元素,那么这个属性就有点类似于 Swift 中的计算属性(Computed Property)了。至于为什么不会生成实例变量,可以去、参考一下美团这篇文章深入理解Objective-C:Category

为什么要首先讨论这个呢,因为对于不同的情况,我们需要采用不同的方案

目标是什么

为了方便下文阐述,此处先简单说一下我们要实现的目标

首先我们有一个只读数组 readonlyData

1
@property (nonatomic, copy, readonly) NSArray *readonlyData;

它的原始值是 @[@”A”, @”B”, @”C”]

我们的目标是把它改成 @[@”X”, @”B”, @”C”]

定义在 Class 内的只读数组

对于定义在 Class 里面的只读数组,我们可以简单的使用 KVC 修改数组,流程如下:

  1. 对是否需要修改的条件进行判断
  2. 若条件为真,取出只读数组 mutableCopy
  3. 改变数组元素
  4. 通过 KVC 进行赋值

关键代码如下:

1
2
3
4
5
6
BOOL condition = YES; // here only a condition mock
if (condition) {
NSMutableArray *mutableBubbleData = [bubbleData.readonlyData mutableCopy];
mutableBubbleData[0] = @"X";
[bubbleData setValue:mutableBubbleData forKey:@"readonlyData"];
}

为什么通过 KVC 能够修改只读数组呢?

这是因为 KVC 机制会找到 bubbleData 内部的 _readonlyData 实例变量,并对其进行赋值

所以对于有实例变量的只读数组而言,我们可以简单地通过 KVC 手段实现修改数组元素

定义在 Category 内的只读数组 / 计算属性

对于定义在 Category 里面的只读数组,或计算属性(没有实例变量)的属性而言

如果我们需要修改计算属性,我们需要通过 Method-Swizzle 的方法修改 getter,关键代码如下:

首先是简单封装一下一个 swizzleMethod,便于方法互换

1
2
3
4
5
6
7
8
9
10
11
12
static void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}

接下来实现 swizzle_computedData,我们在这个方法里面对是否更换元素进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSArray *)swizzle_computedData {
NSArray *origin = [self swizzle_computedData];
BOOL condition = YES; // here only a condition mock
if (!condition) {
return origin;
}
NSMutableArray *replaced = [origin mutableCopy];
replaced[0] = @"X";
origin = replaced;
return replaced;
}

最后在 load 方法中调用 swizzleMethod

1
2
3
+ (void)load {
swizzleMethod([self class], @selector(computedData), @selector(swizzle_computedData));
}

为什么我们这里不能够使用 KVC 呢?主要对于定义在 Category 内的只读属性 / 计算属性,并没有实例变量。所以我们只能对 getter 进行处理。若我们试图使用 KVC 对其修改,会得到 setValue:forUndefinedKey: 的反馈


  • 总结:对于普通的只读数组,我们只需要使用 KVC 即可对其修改;对于没有实例变量的计算属性,我们则需要使用 Method-Swizzle 对 getter 进行修改
  • 本文代码下载

  • Last Edited:2017.4.30
  • Author:@Seahub
  • Please contact me if you want to share this Article, 3Q~
五毛也是情, 一元也是爱