窥探Swift之数组安全索引与数组切片

时间:2023-01-19 19:10:27

  今天是元宵节,祝大家元宵节快乐!在Swift中的数组和字典中下标是非常常见的,数组可以通过索引下标进行元素的查询,字典可以通过键下标来获取相应的值。在使用数组时,一个常见的致命错误就是数组越界。如果在你的应用程序中数组越界了,那么对不起,如果由着程序的性子的话是会崩溃的。为了防止崩溃呢,我们会对集合做一些安全的处理。比如对数组进行扩展,从而对数组的索引进行安全检查,保证数组的index在正常范围内。在Objective-C中也是经常对数组,字典等做一些处理操作。

  今天的博客的主要内容是先对Objective-C中常用集合的安全扩展进行介绍,由此在窥探一下Swift语言中的处理。并且还会介绍Swift中自定义下标,说白了自定义下标其实就是通过下标的形式与特定的下标值来访问一个对象。自定义下标在有些场合中是非常实用的。然后下方还会给出数组切片的概念与实用方式。废话少说进入今天的主题。

一、安全的索引集合元素

  对一个集合索引进行安全检查是很有必要的,也是经常实用的,最常见的就是对数组和字典索引的安全检查,该部分内容就是类比这Objective-C中的数组索引的安全检查来扩充Swift的数组,从而让你的Swift数组也同样具备对数组安全检查的功能。

  1. Objective-C中NSArray对索引的安全扩展

  下方这段代码是非常简单的,它是对Objective-C中的NSArray做的扩展,该方法位于NSArray相关的延展中。在你的项目中,如果添加了此段延展代码,那么你就可以通过objectAtIndexSafe:方法对数组进行安全的索引。有代码不难看出在定义该函数参数时,我们将index声明为NSUInteger,也就是正整数,这就排除了你对下标传入一个负数。紧接着又对index的合法性进行验证,如果index不在数组有效范围内,那么就返回nil。当你查找的元素不存在时,你返回nil是不会造成程序崩溃的,因为nil的地址是0x0, 这和归零若引用有些类似。

  当然下方只是NSArray安全扩展其中一个方法,还有许多扩展的安全方法,比如数组的增删改查都可以进行相应的安全扩展,扩展的方式和思路与下方这段简单代码类似,再次就不花过多的篇幅对其进行介绍了。

 - (id)objectAtIndexSafe:(NSUInteger)index {
if (index > self.count-) {
return nil;
}
return [self objectAtIndex:index];
}

  

  2.Swift中对Array的安全扩展

  上面简单的对Objective-C中的安全方法进行了简单的介绍,就算是对Swift相关内容的引子吧,下方将会给出Swift语言中类似的方法。对Swift相关方法介绍时,我会尽量的详细一些,因为毕竟本篇博客主要是关于Swift内容的。接下来将对上面Objective-C中NSArray数组索引安全验证的方法使用Swift语言进行重新。当然重写的内容也是非常容易理解的。

    (1)主要是对subscript方法进行重载,在重载的subscript方法中,对index的范围通过三目运算符进行了安全检查。如果index在0..<count这个半开区间内,那么就返回当前索引的值,如果不在该范围内就返回nil, 下方就是对Array索引的安全检查。

 extension Array {
subscript (safe index: Int) -> Element? {
return (..<count).contains(index) ? self[index] : nil
}
}

    

    (2)上面是对Swift中的Array进行了安全索引扩展,接下来就是简单的使用了,下方的代码段是对上面安全扩展函数的测试。首先创建了一个数组testArray, 然后创建了一个索引数组indexs, 然后遍历indexs中的元素值,将其作为testArray的下标,对testArray进行检索。当然检索时,使用的是我们上面定义的safe方法,并且在indexs下标数组中存在非法的下标。在这种情况下,我们来验证一下我们的安全方法。

    当然在数组遍历中,我们使用了for-in循环取出indexs中的每个index, 然后使用guard语句取出testArray中的值。使用guard语句能很好的过滤掉因为非法的index而返回的nil值。具体代码段如下所示:

窥探Swift之数组安全索引与数组切片

    上面的代码段理解起来并不难,上述测试代码的运行结果如下所示,从运行结果可以很好的说明问题,并且在index非法时不会崩溃,并合理的给出相应的错误提示,请看下方具体运行结果。

窥探Swift之数组安全索引与数组切片

    上面的延展也可以通过对整个集合类型,也就是CollectionType进行扩展,不过在扩展CollectionType时要对Index使用where子句进行限制,使Index必须符合Comparable协议,具体实现如下所示,不过下面的方法比较少用,因为一般是数组存在越界的情况,因为在字典中,如果你对一个不存在的键进行值的索引,会返回nil值,而不会崩溃。但是在数组中,你对不存在的index进行索引,就会抛出错误。下方是另一种处理方式,不过该方式用的比较少。

    实现下方延展后,同样可以在数组中使用safe方法。

窥探Swift之数组安全索引与数组切片

  

二、使用多个索引下标的数组

  延展的功能是非常强大的,该部分将会给出另一个数组的延展。该延展的功能是可以通过多个索引给数组设置值,以及通过多个索引一次性获取多个数组的值。该功能是非常强大的,接下来将一步步实现该功能。

  1. 了解zip()函数以及Zip2Sequence

    在实现数组多个索引扩展时,需要使用到zip()函数,zip()函数接收两个序列,并且返回一个Zip2Sequence类型的数据。zip()函数究竟是干嘛的呢?接下来将会通过一个小的实例来搞一下zip()函数。首先看一下Apple的帮助文档上对zip()函数的介绍。具体如下所示:

窥探Swift之数组安全索引与数组切片

    上面那句英文的意思大概就是“基于两个基本序列构建了一个序列对,在序列对中,第i对,代表着每个基本序列中的第i个元素。”在zip函数定义的过程中,我们可以看到,zip()是一个泛型函数,其接收两个SequenceType类型的参数,然后返回一个Zip2Sequence类型的数据。新创建的序列对就存在于Zip2Sequence中。说这么多还是来个小Demo实惠一些,通过一个小实例,看zip()函数的用法一目了然。

    (1) 创建两个数组zip1和zip2, 将这两个数组作为zip()函数的参数,将两个数组进行合并。具体实现如下:

窥探Swift之数组安全索引与数组切片

    (2) 通过上面的程序可以看出,zipSum是一个Zip2Sequence<Array<Int>, Array<Int>>类型的常量,我们可以使用dump()对zipSum常量进行打印,观察其中的数据存储结构,具体结构如下所示:

窥探Swift之数组安全索引与数组切片

    输出结果如下,由结果容易看出,在序列中有两个元素,第一个元素对应着数组zip1, 第二个元素对应着数组zip2。

窥探Swift之数组安全索引与数组切片

    (3)接下来就是对zipSum这个序列通过for-in循环进行遍历,下方就是对zipSum进行遍历的代码。

窥探Swift之数组安全索引与数组切片

      上面对zipSum遍历的结果如下所示,由下方输出结果可知,输出是成对遍历的,如果某个数组中的元素是多余的,那么就会被忽略掉。

窥探Swift之数组安全索引与数组切片

  2. 数组多个索引的延展实现

    在这个将要实现的延展中,我们对Array进行了扩展,在延展中对subscript方法进行重载,使其可以接受多个下标,并且对多个下标对应的值进行索引,并把索引结果组成数组。在subscript方法中通过get方法获取索引相应的值,通过set方法为相应的索引值进行设置。下方代码段就是该延展的实现:    

 extension Array {
subscript(i1: Int, i2: Int, rest: Int...) -> [Element] {
//通过实现get方法,获取数组中相应的值
get {
var result: [Element] = [self[i1], self[i2]]
for index in rest {
result.append(self[index])
}
return result
} //通过set方法,对数组相应的索引进行设置
set (values) {
for (index, value) in zip([i1, i2] + rest, values) {
self[index] = value
}
}
}
}

    在上述延展的实现中,并没有多少困难的地方。在subs两个cript函数中,使用的是可变参数,subscript函数参数的个数是两个以上(包括两个)。然后就是通过zip()函数以及对zip()函数返回的结果集进行遍历,从而对多个下标索引进行值的设置。经过上述延展,我们就可以通过多个索引对数组进行操作了。上述延展的使用方式如下: 

窥探Swift之数组安全索引与数组切片

三、数组切片

  数组切片在OC中也是不存在的,是Swift新引入的概念,该部分将会对数组切片进行讨论,研究一下数组切片的使用方式及其特点。下方先通过一个小Demo来看一下如何生成数组切片。下方代码段先将一个字符串通过map函数转换成一个数组arrayTest, 然后我们创建一个该数组的切片。下方代码段创建了arrayTest数组中的下标3到下标6这个范围区间中的切片,arraySlices就是数组切片变量,它是ArraySlice<String>类型的,具体代码段如下所示。

窥探Swift之数组安全索引与数组切片

  在数组切片中有一点需要注意,数组切片的下标与原始数组中的下标保持一致。如果要取出切片arraySlices中的第一个值,我们要使用arraySlices[3], 而不是arraySlices[0], 如果使用arraySlices[0]就会报错,如下所示:

窥探Swift之数组安全索引与数组切片

  

  因为数组是值类型,尽管切片与原数组有着对应的数组下标,但是切片是原始数组的部分拷贝,所以修改切片或者修改原数组,两者互不影响,下方示例给出了该测试,如下所示:

窥探Swift之数组安全索引与数组切片

   

  如果把切片转换成枚举,那么切片中与原始数组对应的下标关系将不存在,下方是将切片转换成枚举序列,然后对其进行遍历,代码如下:

窥探Swift之数组安全索引与数组切片

  上述代码段输出结果如下:

窥探Swift之数组安全索引与数组切片

    今天博客就先写到这儿,关于数组的延展还有许多,以后有机会再讨论。其实我们还可以通过一些方式来为我们自己的对象添加下标。也就是可以通过下标来访问对象属性,这个以后在讨论吧。