候选词顺序乱了
Closed this issue · 1 comments
分析情况:
候选词的位置,也就是顺序是正常的
index 前缀异常
当前选择的候选词不正确
背景
候选词的渲染是通过 ForEach
做的,在使用中,ForEach
要求输入 id
作为唯一标识,当时不求甚解使用 \.self
排查问题
由于上述背景原因,而且并未对出现的bug进行深入的分析,所以一开始认为是 ForEach
的参数 id
使用 \.self
并不对保证唯一导致的。
尝试解决:
经过一翻搜索查询,发现:
对于符合 Hashable
的类型,\.self
表示此对象/结构体的 hashValue
字段
对于实现了 Identifiable
的数据类型, \.self
表示对象/结构体的 id
字段
而候选词的数据类型仅仅是 Hashable
并不 Identifiable
所以一开始解决方案是把候选词类型改为 Identifiable
并声明定义 id = UUID()
以保证生成唯一的标识
运行测试发现bug被修复
到此,bug被解决,故事本该结束
再次思考:
同样的资料发现,原候选词的 Hashable
类型应该是能正常工作的,也就是能作为唯一标识。原因是结构体的默认hash一致性实现是把结构体中可hash的值一起hash运算得到的,所以不可能会有两个不一样的候选词而hash一致的问题。
为了验证上面的结论,输出候选词的 hashValue
发现,hashValue
确实不一样的
但是同时发现了另一个问题,候选词的执行顺序和 ForEach
的第一个参数竟然不一致,这是什么情况??
经过思考和实验,以及在各种文档的协助下,总算弄明白了 ForEach
的工作模式
ForEach
中的View
为了能够实现流畅动画效果,使用id
参数来确定集合中元素的唯一性- 在执行
ForEach
的content
之前,会对原始输入的集合进行处理,这种处理分两个方面
a. 如果集合中有多个元素具有相同的唯一标识,那么content
回调只会执行一次。例如,如果页面中有两个相同的String
hello 那么content
只会被调用一次,第二个hello会直接渲染第一个的调用结果
b. 集合会被重新排序后再依次调用content
,然后再返回值按原顺序进行渲染,我想之所以有这样的结果,应该是出于动画的渲染考虑。
再回来分析这个BUG,发现其实也就只有 2.a
这么一个解释。
至于具体为什么候选词 "去、云、支...." 会按照 "支、去、云..." 这样的顺序来渲染?
分析后,猜想是因为在前一个输入的候选词中也出现了 "支" 这个候选词,并且这两个候选词的数据是完全一致的,生成了一样的 hashValue
, 所以 ForEach
在实际执行时,把前面刚出现的数据放在了前面执行。
至于为什么方法1也能修复这个问题,原因也在这里。在使用UUID作为唯一标识的情况下, 即使两个相同的候选词,ForEach
中也认为是不一样的,所以自然不会有因为前面出现过某个数据而把这个数据提前的优待,所以重排后的顺序就会仍然和输入时一致。
最终解决方案
再回来解决这个问题,主要就是因为生成的 index 是一个外部变量,在 content
回调中执行 index += 1
的操作
所以在 ForEach
的 content
并不按照输入顺序调用的情况下,出现了index的错乱。
明白了原因,就会发现方法1并不是最完美的解决方案——如果我们后面要在ForEach里面做动画,采用方法1就可能会导致不可预期的结果,在没搞明白 ForEach
工作原理的情况下,方法1等于是埋了一个坑。
那我们如何来完美的解决这个问题呢?其实只需要候选词的index不受 ForEach
的影响就行了,比如说直接放在候选词数据中是一个方案,更好的方案在于,swift数组已经为我们提供了这样的一个方案——enumerated方法,此方法会生成一串包含 (index, element)的元组,这样最终的解决方案如下: