• Swift调用OC之NSArray

    2015-03-02 19:59:38
    // Swift调用OC之NSArray // // 1.NSArray与Array的互相转换: // 2.NSArray初始化 // 3.NSArray的常用属性和方法 //  --获取成员个数 //  --获取数组第一个元素 //  --获取数组最后一个元素 //  --...

    //**********************

    //

    // Swift调用OCNSArray

    //

    // 1.NSArrayArray的互相转换:

    // 2.NSArray初始化

    // 3.NSArray的常用属性和方法

    //   --获取成员个数

    //   --获取数组第一个元素

    //   --获取数组最后一个元素

    //   --获取指定区间的子数组:objectsAtIndexes方法

    //   --NSArray查询指定元素是否存在,返回Bool

    //   --Array  查询指定元素是否存在,返回Bool

    //   --NSArray查找的同时获得元素索引

    //   --Array  查找的同时获得元素索引

    //

    //**********************

    import Foundation

     

    //==========NSArrayArray的互相转换==========

    let array1: [Int] = [1,2,3,4]

    let nsArray1: NSArray = array1

    let swiftArray1 = nsArray1 as [Int]

     

    //可以将任一NSArray对象转换成Array,因为所有OC对象都是AnyObject类型

    let array2: [AnyObject] = [4,2,1,5,3,"happy"]

    let nsArray2: NSArray = array2

    var swiftArray2 = nsArray2 as [AnyObject]

    //转成swift数组后可以用swift数组的方法

    swiftArray2 += [8,9]

     

    //NSArray是静态数组,如需修改成员,要使用动态NS数组NSMutableArray

    var swiftArray3: [Int] = [1,2,3,4]

    var mutableArray = NSMutableArray(array: swiftArray3)

    mutableArray.removeLastObject()

     

    //==========     NSArray初始化     ==========

    let swiftArray4 = [1,2,3,4]

    let nsArray401 : NSArray = swiftArray4

    let nsArray402 = NSArray(array: swiftArray4)

    let nsArray403 = swiftArray4 as NSArray

     

    //==========  NSArrayArray的常用属性和方法   ==========

    //获取成员个数

    swiftArray4.count

    nsArray401.count

     

    //获取数组第一个元素

    swiftArray4.first       //{Some 1}

    nsArray401.firstObject  //{Some 1}

    nsArray401.firstObject! //1

     

    //获取数组最后一个元素

    swiftArray4.last

    swiftArray4[swiftArray4.count-1]

    nsArray401.lastObject

    nsArray401.objectAtIndex(nsArray401.count-1)

    nsArray401[nsArray401.count-1]

     

    //获取指定区间的子数组:objectsAtIndexes方法

    let range001 = NSRange(location: 0, length: 2)

    let indexSet = NSIndexSet(indexesInRange:range001)

    nsArray401.objectsAtIndexes(indexSet//[1, 2]

     

    var i = 0, j = 1

    swiftArray4[i...j//[1, 2]

     

    //NSArray查询指定元素是否存在,返回Bool

    nsArray401.containsObject(3)    //  true

    nsArray401.containsObject("3"//  false

     

    //Array查询指定元素是否存在,返回Bool

    contains(swiftArray4, 3)

     

    //NSArray查找的同时获得元素索引

    i = 1

    let index001 = nsArray401.indexOfObject(i)

    ifindex001 == NSNotFound {

        println("index of \(i)not found")

    }else{

        println("index of \(i)is \(index001)") //index of 1 is 0

    }

     

    //Array查找的同时获得元素索引

    find(swiftArray4, 1)    //{Some 0}

    find(swiftArray4, 1)!   //0

    展开全文
  • 【Swift初见】Swift数组

    2015-02-27 20:25:04
    在Objective-C中数组是常用的数据类型,在Swift中同样如此,在OC中有NSArray与NSMutableArray之分,但是在Swift中只有通过let和var来区分数组是否可变,Swift中的数组是类型安全的,所以在某个数据被存入到某个数组...
    在Objective-C中数组是常用的数据类型,在Swift中同样如此,在OC中有NSArray与NSMutableArray之分,但是在Swift中只有通过let和var来区分数组是否可变,Swift中的数组是类型安全的,所以在某个数据被存入到某个数组之前类型必须明确,假如我们创建了一个String类型的数组,那么该数组中就不能添加非String的数据类型,这是Swift与OC的一个很重要的区别。

    数组的构造
    我们以创建一个数组并存储Int类型的数组为例:
    var array = [2, 3, 4, 5]
    var array1: Array = [2, 3, 4, 5]
    var array2: Array<Int> = [2, 3, 4, 5]
    var array3: [Int] = [2, 3, 4, 5]
    

    数组的数量:数组有一个只读属性 count来获取数组中的数据项数量。
    array.count

    检查数组是否为空可以用isEmpty来检查count是否为0
    if array.isEmpty {
        println("array is Empty")
    }else{
        println("array is not Empty")
    }

    当要在数组后面添加新的数据项时可以使用append方法来添加:
    array .append(6)

    此时数组中就有5个值了,
    当数组的数据类型为字符串时,也可以使用加法赋值运算符(+=)直接在数组的后面添加新的数据项;
    加法运算符也可以直接添加拥有相同类型的数组:
    array += [7,8]

    获取数组中数据项的时候,可以用索引来获取值:
    var intV = array[0]

    注:不管是OC还是Swift,数组的索引都是从0开始。

    修改数组中的某一项时,也可以通过索引来改变:
    array[0] = 9

    此时数组就为:[9, 3, 4, 5, 6, 7, 8]
    swift中也可以通过下标一次性改变多个数据值:
    array[1...3] = [10,11,12]

    此时数组值就是:[9, 10, 11, 12, 6, 7, 8]
    在数组尾部添加新项时,不能使用下标来添加新项,此时数组越界,会引发一个运行期错误。

    在某个具体索引值之前添加数据项,调用数组的insert(atIndex)来添加:
    array.insert(13, atIndex: 0)

    此时的数组值为:[13, 9, 10, 11, 12, 6, 7, 8]

    同样的,移除数组的某一项时用removeAtIndex 方法;
    移除最后一项的时候用removeLast

    数组遍历
    普遍的我们使用for-in循环来遍历数组中的所有数据项
    for i in array {
        println(i)
    }


    swift提供一个enumerate函数来遍历数组,会同时返回数据项和索引值:
    for (index, value) in enumerate(array){
        println("index : \(index) value: \(value)")
    }


    此时打印的值为:
    index : 0 value: 13
    index : 1 value: 9
    index : 2 value: 10
    index : 3 value: 11
    index : 4 value: 12
    index : 5 value: 6
    index : 6 value: 7
    index : 7 value: 8

    如果我们需要创建一个数组,该数组有特定的大小并且所有的数据都是被默认的,此时swift提供了一个数组构造函数:
    var newArray = [String](count: 4, repeatedValue: "test")

    当然,swift具有类型推断功能,我们也可以如下定义:
    var newArray2 = Array(count: 3, repeatedValue: "today")

    我们知道,两种数据项类型相同的数组我们可以通过(+)来组合到一起形成一个新的数组:
    var newArray3 = newArray + newArray2


    欢迎大家共同学习指导。
    展开全文
  • @OC调用Swift 方法 OC调用传多值参数Swift方法的处理 截止目前, Swift 已经达到4.2版本。有很多优秀的Swift库。有时需要OCSwift混编。学习研究了一下FaceAware 人脸感知开源库。 Swift方法传多值参数及OC调用 ...

    OC调用传多值参数Swift方法的处理

    截止目前, Swift 已经达到4.2版本。有很多优秀的Swift库。有时需要OC和Swift混编。例如 FaceAware 人脸感知开源库。

    Swift方法传多值参数及OC调用

    比如一个开源的图片处理框架,人脸感知库FaceAware
    FaceAware GitHub 地址页面,在Swift 内部可以这样设置一个已知Image和Frame的方法,即多值参数。

        func setLocalFaceImageAndFrame(aImage: UIImage?, aFrame: CGRect?) {
            let layer = self.imageLayer()
            layer.contents = aImage!.cgImage
            layer.frame = aFrame!
        }
    

    OC与Swift混编,在"ProjectName_iOS-Swift.h"的头文件中,并没有暴露出上述所写方法,若加上 @objc ,会报错

    Method cannot be marked @objc because the type of the parameter 2 cannot be represented in Objective-C
    

    不能运行。故调整如下:

    把多值参数封装成一个Dictionary,通过Dictionary传值处理。

        @objc
        public func setLocalFaceImageAndFrame(infoDic: NSDictionary) {
            let aImage = infoDic["image"] as! UIImage
            let aFrame = infoDic["frame"]
            
            let layer = self.imageLayer()
            layer.contents = aImage.cgImage
            layer.frame = aFrame as! CGRect
        }
    

    附加:
    FaceAware https://github.com/BeauNouvelle/FaceAware

    展开全文
  • Swift数组扩容原理

    2019-02-27 02:17:44
    首先把结论写在文章开头,因为接下来的分析会有些啰嗦、复杂,如果不愿意深究的话只要记住Swift数组扩容的原理是: Swift2中创建一个空数组,默认容量为2,当长度和容量相等,且还需要再添加元素时,创建一个 ...

    #结论

    首先把结论写在文章开头,因为接下来的分析会有些啰嗦、复杂,如果不愿意深究的话只要记住Swift中数组扩容的原理是:

    Swift2中创建一个空数组,默认容量为2,当长度和容量相等,且还需要再添加元素时,创建一个
    复制代码
    两倍长度于旧数组的新数组,把旧数组的元素拷贝过来,再把元素插入到新数组中。
    复制代码

    #引子

    下面这段代码希望通过多线程技术,加速向数组中添加数字这个过程,我们来看看它有什么问题:

    var array: [Int] = []
    let concurrentQueue = dispatch_queue_create("com.gcd.kt", DISPATCH_QUEUE_CONCURRENT)
    
    for i in 1...10000 {
    dispatch_async(concurrentQueue, { () -> Void in
    array.append(i)
    })
    }
    复制代码

    代码很简短,看上去问题不大。不过如果你运行完这段代码而且程序没有崩溃,我强烈建议买一份彩票,因为你的运气已经好到逆天了。

    通常情况下,你会遇到这样的报错

    fatal error: UnsafeMutablePointer.destroy with negative count

    #Append方法实现

    程序会断在array.append(i)这一行。也就是append方法出了问题。我们知道Swift里的数组不像C语言,不需要提前定义好长度,更像是C++的vector和OC的NSMutableArray

    所以,会不会是数组的可变性,导致了append方法是线程不安全的呢,带着这样的猜想,我们来研究一下Swift是如何实现Append方法的。

    Swift已经开源了,github上相关源码已经可以下载。

    虽然明知道有些文件夹不会包含append方法的实现源码,但真想找到也不容易。如果你试着搜索"append"的话,相关文件非常多,因为"append"本身就是一个非常常用的单词。我采取的办法是搜索完整的函数定义,而函数定义是我们很容易看到的。

    当我们搜索"mutating func append(newElement: Element)"后,就只有六个相关文件了。如图所示:

    前三个文件无法直接打开,暂时先不管。其实第三个一看也知道是单元测试文件。第六个是字符串,也不是我们感兴趣的。所以我们依次打开"ArrayType.swift"和"RangeReplaceableCollectionType.swift"这两个文件。

    提示:这两个文件的目录都是"/swift/stdlib/public/core"
    复制代码

    遗憾的是,ArrayType.swift文件中没有找到相关函数,RangeReplaceableCollectionType.swift文件倒是有一个append方法,不过参数类型对不上。于是我想到第一个文件——"Arrays.swift.gyb",去掉gyb后缀后果然可以打开了。并且成功的找到了我们感兴趣的append方法:

    public mutating func append(newElement: Element) {
    _makeUniqueAndReserveCapacityIfNotUnique()
    let oldCount = _getCount()
    _reserveCapacityAssumingUniqueBuffer(oldCount)
    _appendElementAssumeUniqueAndCapacity(oldCount, newElement: newElement)
    }
    复制代码

    #源码分析

    代码不长,我们逐行看一下

    • 第一行的函数,看名字表示,如果这个数组不是惟一的,就把它变成惟一的,而且保留数组容量。在Xcode里面搜索一下这个函数:
    internal mutating func _makeUniqueAndReserveCapacityIfNotUnique() {
    if _slowPath(!_buffer.isMutableAndUniquelyReferenced()) {
    _copyToNewBuffer(_buffer.count)
    }
    }
    复制代码

    这个函数会进行一个判断判断——如果数组不是被唯一引用的,就会复制一份新的。这其实就是“写时赋值(copy-on-write)”技术。如果你想了解它的具体使用,可以参考我的这篇文章——《第二章——集合(数组与可变性)》

    不过对于文章开头那个例子的数组来说,它肯定是唯一引用的。所以_copyToNewBuffer函数不会调用,我们先记下这个方法。然后回到append方法继续研究。

    • 第二行用一个变量oldCount保存数组当前长度。

    • 第三行的函数表示,在假设当前数组是唯一引用的前提下,保留数组容量。之所以做出这样的假设,是因为此前已经调用过_makeUniqueAndReserveCapacityIfNotUnique方法,即使这个数组不是唯一引用,也被复制了一份新的。我们来看看_reserveCapacityAssumingUniqueBuffer方法的实现:

    internal mutating func _reserveCapacityAssumingUniqueBuffer(oldCount : Int) {
    _sanityCheck(_buffer.isMutableAndUniquelyReferenced())
    
    if _slowPath(oldCount + 1 > _buffer.capacity) {
    _copyToNewBuffer(oldCount)
    }
    }
    复制代码

    第一行有一个_sanityCheck来判断数组是否可变且唯一引用。"sanity"说明这个判断是符合常理的,虽然它很有可能并没有效果,但也是为了确保万无一失。

    下面还有一个判断,检查当前数组长度加一后是否大于数组容量。如果判断成立,说明oldCount == _buffer.capacity,在实际编程中,就意味着数组需要扩容了。可以看到又会执行刚刚提到过的_copyToNewBuffer函数。我们还是先把这个函数放一放,接着往后看。

    • 最后一行的函数表示,假设数组是唯一引用的,且数组容量也设置正确,把新的元素添加到数组中。这其实是真正执行了“append”操作的地方。它的实现如下:
    internal mutating func _appendElementAssumeUniqueAndCapacity(
    oldCount : Int,
    newElement : Element) {
    
    _sanityCheck(_buffer.isMutableAndUniquelyReferenced())
    _sanityCheck(_buffer.capacity >= _buffer.count + 1)
    
    _buffer.count = oldCount + 1
    (_buffer.firstElementAddress + oldCount).initialize(newElement)
    }
    复制代码

    首先是两个基本判断,然后把count属性加一,最后获取到将要添加的位置的地址,用一个新的值初始化它。

    OK,append方法的结构基本上了解了,首先会保证数组是唯一引用的,然后处理数组的容量问题,最后把待插入的元素放到指定位置上。其中最关键,也是目前还没有彻底明白的一步,就是之前所说的_copyToNewBuffer函数了

    #copyToNewBuffer

    先来看看copyToNewBuffer函数的实现:

    internal mutating func _copyToNewBuffer(oldCount: Int) {
    let newCount = oldCount + 1
    var newBuffer = Optional(
    _forceCreateUniqueMutableBuffer(
    &_buffer, countForNewBuffer: oldCount, minNewCapacity: newCount))
    _arrayOutOfPlaceUpdate(
    &_buffer, &newBuffer, oldCount, 0, _IgnorePointer())
    }
    复制代码

    这个方法又分为两步,_forceCreateUniqueMutableBuffer_arrayOutOfPlaceUpdate。前者实现了新存储区域的创建,而后者完成了数据的复制工作。

    为了简单起见,我们先看看_arrayOutOfPlaceUpdate函数,这个函数的实现太长了,不过好在有注释:

    /// Initialize the elements of dest by copying the first headCount
    /// items from source, calling initializeNewElements on the next
    /// uninitialized element, and finally by copying the last N items
    /// from source into the N remaining uninitialized elements of dest.
    ///
    /// As an optimization, may move elements out of source rather than
    /// copying when it isUniquelyReferenced.
    复制代码

    大意是说,源数组中已存在的元素会被复制到目标数组中,如果新数组比较长,空缺部分会调用initializeNewElements方法来初始化。为了优化性能,被唯一引用的元素可能会直接从源数组移到新数组而不是复制。其实就是换了一个指针指向那个元素,从而避免了复制。

    接下来我们再研究一下比较关键的_forceCreateUniqueMutableBuffer部分,也就是数组是怎样扩容的:

    @inline(never)
    func _forceCreateUniqueMutableBuffer<_Buffer : _ArrayBufferType>(
    inout source: _Buffer,  countForNewBuffer: Int,  minNewCapacity: Int
    ) -> _ContiguousArrayBuffer<_Buffer.Element> {
    
    //其实什么也没干,多加了一个参数就转发给 _forceCreateUniqueMutableBufferImpl了
    return _forceCreateUniqueMutableBufferImpl(
    &source, countForBuffer: countForNewBuffer, minNewCapacity: minNewCapacity,
    requiredCapacity: minNewCapacity)
    }
    
    internal func _forceCreateUniqueMutableBufferImpl<_Buffer : _ArrayBufferType>(
    inout source: _Buffer,  countForBuffer: Int, minNewCapacity: Int,
    requiredCapacity: Int
    ) -> _ContiguousArrayBuffer<_Buffer.Element> {
    _sanityCheck(countForBuffer >= 0)
    _sanityCheck(requiredCapacity >= countForBuffer)
    _sanityCheck(minNewCapacity >= countForBuffer)
    
    let minimumCapacity = max(
    requiredCapacity, minNewCapacity > source.capacity
    ? _growArrayCapacity(source.capacity) : source.capacity)
    
    return _ContiguousArrayBuffer(
    count: countForBuffer, minimumCapacity: minimumCapacity)
    }
    复制代码

    _forceCreateUniqueMutableBufferImpl函数刚开始的三个检查读者可以自行理解,关键部分在于minimumCapacity的计算。因为它会作为容量参数被传到用于创建新的Buffer的函数中。

    这个函数有四个参数,第一个参数buffer可以理解为数组中真正用于数据存放的那个部分。对于最后两个参数,意思有点像,我们不妨考虑一个实际的、需要进行数组扩容的情况,向一个容量为3,长度为3的数组新增一个元素1,此时函数的调用顺序如下:

    1. append(1)
    2. _reserveCapacityAssumingUniqueBuffer(3)
    3. _copyToNewBuffer(3)
    4. _forceCreateUniqueMutableBuffer(&_buffer, countForNewBuffer: 3, minNewCapacity: 4)
    5. _forceCreateUniqueMutableBufferImpl(&_buffer, countForBuffer: 3, minNewCapacity: 4, requiredCapacity: 4)

    还记得_copyToNewBuffer()方法里的let newCount = oldCount + 1么,所以oldCount(=3)作为minNewCapacity,而newCount(=4)作为requiredCapacity参数被传入_forceCreateUniqueMutableBufferImpl方法。

    此时,minimumCapacity的计算,其实就是以下表达式的值:

    max(4, 4 > 3 ? _growArrayCapacity(3) : 3)
    复制代码

    我们知道,如果数组需要扩容,source.capacity总是等于minNewCapacity的。也就是说上式可以写为:

    max(length+1, length+1 > length ? _growArrayCapacity(length) : length)
    
    //等价于
    max(length+1, _growArrayCapacity(length))
    复制代码

    可以看到_growArrayCapacity返回值是传入参数的两倍:

    @warn_unused_result
    internal func _growArrayCapacity(capacity: Int) -> Int {
    return capacity * 2
    }
    复制代码

    所以minimumCapacity = max(length+1, 2 * length) = 2 * length。也就是新扩容的数组长度其实翻倍了。

    #线程安全

    现在我们可以理解为什么append方法不是线程安全的了。如果在某一个线程中插入新元素,导致了数组扩容,那么Swift会创建一个新的数组(意味着地址完全不同)。然后ARC会为我们自动释放旧的数组,但这时候可能另一个线程还在访问旧的数组对象。

    #验证

    说了这么多,我们来证明一下Swift数组扩容的工作原理:

    let semaphore = dispatch_semaphore_create(1)
    var array: [Int] = []
    for i in 1...100000 {
    array.append(i)
    let arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count)
    print(arrayPtr)
    }
    复制代码

    运行结果如下,可以验证文章开头的结论:“初始容量为2,扩容时容量翻倍”

    展开全文
  • Swift中的集合最近翻译完了《Advanced Swift》中文版的“集合”章节。书的质量非常高,讲解非常细致。但不可避免的导致篇幅有点长,有些前面的知识点看到后面无法串联起来。同时由于偏重于讲解,所以个人感觉总结还...

    Swift中的集合

    最近翻译完了《Advanced Swift》中文版的“集合”章节。书的质量非常高,讲解非常细致。但不可避免的导致篇幅有点长,有些前面的知识点看到后面无法串联起来。同时由于偏重于讲解,所以个人感觉总结还不够,比如我们可以考虑这几个问题:

    • 数组类型(_ArrayType)、集合(Collection)、序列(Sequence)、生成器(Generator)、元素(Element)、下标(Index),这些类型(协议)各自的作用。
    • 数组是如何利用上面这些类实现各种方法的。
    • mapreducefilter等方法的作用是什么,他们是怎么实现的。
    • 只有数组有上面这些方法么,如果不是,什么样的类型才有这些方法。
    • 如果实现一个自定义的集合类型,应该怎么做。

    而这些问题恰恰是过去我们没有重视或根本无法找到答案的问题。因为在OC中,由于NSArray封装的非常好,而且在单纯的iOS开发中对数组的理解不用非常深入,所以我们很少深究数组背后的实现原理。

    其实答案就遍布在《Advanced Swift》中文版的“集合”章节的各篇文章中。本文会通过分析Swift中数组的实现,回答上述问题并尝试着建立完整的知识体系。由于篇幅所限,本文不会给出非常详细的源码,而是做总结性的提炼。

    参考文章

    相关类型简介

    我们从零开始,根据集合最本质的特点开始思考,逐步抽象。

    元素(Element)

    对于任何一种集合来说,它本质上是一种数据结构,也就是用来存放数据的。我们在各种代码中见到的Element表示的就是最底层数据的类型别名。因为对于集合这种范型类型来说,它不关心具体存放的数据的类型,所以就用通用的Element来代替,比如我们写这样的代码:

    let array: Array<Int> = [1,2,3]

    这就相当于告诉数组,Element现在具体变为Int类型了。

    生成器(Generator)

    对于集合来说,它往往还需要提供遍历的功能。那怎么把它设计为可遍历的呢?不要指望for循环,要知道目前我们什么都没有,没有数组的概念,更没有下标的概念。有一句名言说:“任何编程问题都可以通过增加一个中间层来解决”。于是,我们抽象出一个Generator(生成器)的概念。我们把它声明成一个协议,任何实现这个协议的类都要提供一个next方法,返回集合中的下一个元素:

    protocol GeneratorType {
        typealias Element
        mutating func next() -> Element?
    }

    可以想象的是这样一来,实现了GeneratorType协议的类,就可以隐藏具体元素的信息,通过不断调用next方法就可以获得所有元素了。比如你可以用一个生成器,不断生成斐波那契数列的下一个数字。

    总的来说,生成器(Generator)允许我们遍历所有的元素。

    序列(Sequence)

    生成器不断生成下一个元素,但是已经生成过的元素就无法再获取到了。比如在斐波那契数列中通过3和5得到了8,那么这个生成器永远也不会再生成元素3了,下一个生成的元素是13。也就是说对于生成器来说,已经访问过的元素就无法再次获取到。

    然而对于集合来说,它所包含的元素总是客观存在的。为了能够多次遍历集合,我们抽象出了序列(Sequence)的概念。Sequence封装了Generator对象的创建过程:

    protocol SequenceType {
        typealias Generator: GeneratorType
        func generate() -> Generator
    }

    序列(Sequence)相比于单纯的Generator,使反复遍历集合中的元素成为可能。这时候,我们已经可以通过for循环而不是Generator来遍历所有元素。

    集合(Collection)

    对比一下现有的Sequence和数组,会发现它还欠缺一个特性——下标。

    回顾一下GeneratorSequence,它们只是实现了集合的遍历,但没有指定怎么遍历。也就是说,只要Generator设计“得当”,即使是1和2这两个元素,我们也可以不断遍历:“1的next是2,2的next是1”。这种情况显然不符合我们对数组的认识。归根结底,还是Sequence中无法确定元素的位置,也就无法确保不遍历到已经访问过的元素。

    基于这种考虑,我们抽象出集合(Collection)的概念。在集合中,每个元素都有确切的位置,因此集合有明确的开始位置和结束位置。给定一个位置,就可以找到这个位置上的元素。CollectionSequence的基础上实现了Indexable协议

    
    public protocol CollectionType : Indexable, SequenceType {
        public var startIndex: Self.Index { get }
        public var endIndex: Self.Index { get }
        public subscript (position: Self.Index) -> Self._Element { get }
    }

    当然,CollectionType中的内容远远不止这些。这里列出的仅仅是为了表示CollectionType的特性。

    下标(Index)

    虽然我们在使用数组的时候,元素下标总是从0开始,并且逐个递增。但下标不必是从0开始递增的整数。比如a、b、c……也可以作为下标。下标类型的关键在于能根据某一个下标推断出下一个下标是什么,比如对于整数来说下一个下标就是当前下标值加1。下标类型的协议如下:

    public protocol ForwardIndexType : _Incrementable {
        ///....
    }
    
    public protocol _Incrementable : Equatable {
        public func successor() -> Self
    }

    对于下标类型来说,它们必须实现successor()方法,同时也得是Equatable的,否则怎么知道某个位置上的元素已经被访问过了呢。

    数组简介

    有了以上基本知识做铺垫,接下来就可以研究一下Swift中数组是怎么实现的了。

    基本定义

    我们可能早已体会到Swift数组的强大,它的下标脚本不仅可以读取元素,还可以直接修改元素。它可以通过字面量初始化,还可以调用
    appendContentsOf方法从别的数组中添加元素。甚至于我们可能都没有仔细考虑过为什么可以用for number in array这样的语法。

    首先,我们需要明确一个概念:数组丰富强大的特性绝不是由Array这一个类型可以提供的。实际上,这主要是协议的功劳。用OC写iOS时,协议几乎就是代理的代名词(可能是我对OC不甚精通,目光短浅),但毋庸置疑的是在Swift中,协议的功能被空前的强化。数组通过实现协议、以及协议的嵌套、拓展功能,具有了很多特性。数组的背后交织着错综复杂的协议、方法和拓展。

    下面是数组的定义:

    public struct Array<Element> : CollectionType, MutableCollectionType, _DestructorSafeContainer {
        public var startIndex: Int { get }
        public var endIndex: Int { get }
        public subscript (index: Int) -> Element
        public subscript (subRange: Range<Int>) -> ArraySlice<Element>
    }

    数组是一个结构体,它实现了三个协议,有两个变量和两个下标脚本。除此以外,数组还有大量的拓展。

    数组拓展

    数组的大量功能在拓展中完成,由于拓展很多,我就不列出完整代码,只是做一个整理。数组一共拓展了四类协议:

    • ArrayLiteralConvertible: 这个协议是为了支持这样的语法的:let a: Array<Int> = [1,2,3]。实现协议很简单,只要提供一个自定义方法即可:
    public init(arrayLiteral elements: Element...)
    • _Reflectable:这个协议用于处理反射相关的内容,这里不做详解
    • CustomStringConvertibleCustomDebugStringConvertible:这两个是方便我们调试的协议,与数组自身的功能无关。
    • _ArrayType:这是数组最关键的部分。在实现这个协议之前,数组还只是一个普通的集合类型,它仅仅是拥有下标,而且可以重复遍历所有元素而已。而通过实现_ArrayType协议,它可以在指定位置(下标)添加或移除一个或多个元素,它还有了自己的count属性。

    这一点也许有些颠覆我们此前的认识。一个集合类型,是不能添加或删除元素的。数组通过实现了_ArrayType协议提供了这样的功能。但这也很容易理解,因为集合的本质还是在于元素的收集,而非考虑如何改变这些元素。

    _ArrayType协议还给了我们一个非常重要的启示。比如说我想实现自己的数据结构——栈,那么就应该实现对应的_StackType协议。这种协议要充分考虑数据结构自身的特点,从而提供对应的方法。比如我们不可能向栈的某个特定位置添加若干个元素(只能向栈顶添加一个)。所以_StackType协议中不会定义appendappendContentsOf这样的方法,而是应该定义poppush方法。

    SequenceType

    接下来的任务是搞清楚ColectionType的原理了。不过在此之前,我们先来看看SequenceType的一些细节,毕竟CollectionType协议是继承了SequenceType协议的。

    在有了Generator之后,我们已经可以在while循环中用Generatornext方法遍历所有元素了。之前也说过,SequenceType使对元素的多次遍历成为可能。

    注意,仅仅是成为可能而已。如果遍历的细节由Generator控制,那么多次遍历是没有问题的。在极个别情况下,但如果遍历的细节依赖于SequenceType自身的某个属性,而且这个属性会发生变化,那么就不能多次遍历所有元素了。

    SequenceType协议的基本部分非常简单,只有一个generator()方法,封装了Generator的创建过程。

    一旦有了遍历元素的概念,SequenceType立刻就有了非常多的拓展。这里简单列举几个比较关键的:

    • forEach:这个拓展使得我们可以用for循环遍历集合了:for item in sequence
    • dropFirst(n: Int)dropLast(n: Int):这两个方法返回的是除了前(后)n个元素之外的Sequence。需要提一下的是,由于此时的SequenceType还没有下标的概念,这两个方法的实现是非常复杂的。
    • prefix(maxLength: Int)suffix(maxLength: Int):和刚刚两个方法类似,这两个方法返回的是前(后)maxLength个元素,实现也不简单。
    • elementsEqualcontainsminElementmaxElement等,这些都是针对元素的判断和选择。
    • mapflatMapfilterreduce这些方法是针对所有元素的变换。

    SequenceType的拓展实在是太多了,但总结来看不外乎两点:

    1. 由于可以多次遍历元素了,我们可以对元素进行各种比较、处理、筛选等等操作。这些派生出来的方法和函数极大的强化了SequenceType的功能。
    2. 由于SequenceType自身的局限性,不能保证一定可以多次遍历所有元素,还没有下标和元素位置的概念,因此某些方法的实现还不够高效,

    带着这样的遗憾,我们来看看最关键的CollectionType是如何实现的。

    细谈CollectionType

    之前我们说过CollectionType协议是在SequenceType的基础上实现了Indexable协议。由于协议的继承关系,任何实现了CollectionType协议的类,必须实现Indexable协议规定的两个参数:startIndexendIndex,以及一个下标脚本:subscript (position: Self.Index) -> Self._Element { get }。即使这三个要求在CollectionType中没有直接标出来。

    回顾一下数组定义的前三行,正是满足了这三个要求。再看CollectionType,它不仅重载了Indexable的一个下标脚本,还额外提供了一个下标脚本用来访问某一段元素,这个下标脚本返回的类型是切片(Slice)。这也正是数组定义的第四行,实现的内容。

    细心的读者可能已经注意到,CollectionType还定义了很多属性和方法,比如:prefixUpTosuffixFromisEmptyfirst等等。但数组没有实现其中的任何一个。

    事实上,这不仅不是数组设计的失败之处,而正是Swift协议的强大之处。Swift可以通过协议拓展,为计算属性和方法提供默认实现。因此,数组可以不用写任何代码就具备这些方法。更赞的是,任何实现了CollectionType协议的类型也因此具有了这些方法。

    观察一下CollectionType的其它拓展,大部分都是重写了SequenceType中的实现。之前已经提到过SequenceType没有下标的概念,而类似于dropFirst这样的方法,利用下标的概念是非常容易实现的。

    除了对一些已有方法的重写之外,CollectionType还新增了一些基于下标的方法。比如indexOf()等。

    套用官方文档中对CollectionType的总结就是:

    A multi-pass sequence with addressable positions
    

    也就是说CollectionType是可以多次遍历,元素可定位的SequenceType

    总结

    ElementGeneratorSequenceTypeCollectionTypeArray由下至上构造了数组背后的层次结构。他们的关系如下图所示:

    Swift数组层次结构

    如果我们希望定义一个自己的数据结构,比如链表。首先可以明确它要实现CollectionType协议。链表应该是和Array同层次的类型。然后我们需要定义一个_ListType的协议,这个协议对应数组的_ArrayList协议,根据数据结构自身的特性定义一些方法。

    如果觉得CollectionType甚至是SequenceType不满足我们的需求,我们还可以自己实现对应的类型。难度不会很大,因为它们虽然负责,但大多数方法已有默认实现,我们只要重写一些关键的逻辑即可。

    最后需要说明的是,Swift中对集合的实现实在是太复杂了,如果每个都详细分析,怕是可以写一本书。希望读完这篇文章后,读者可以举一反三,自行阅读源码解决相关问题。

    展开全文
  • Swift4.0数组的声明

    2019-01-11 13:01:17
    总所周知,Swift是强类型语言 所以其数组中元素都必须是同一个类型的  而OC中的数组是可以存放任意Object-C对象的 如下 ...下面来看看Swift数组的声明 //普通声明方法 let nums = [1,2,3,4,5] let string = [...
  • swift中的数组

    2017-04-05 16:41:12
    func demo5(){  //[String]  var array:[Any] = ["张三" as NSObject,"李四" as NSObject,"小哥" as NSObject]  //[NSObject]  let array2 = ["老刀","1",180] as [Any]  //将array2
  • import Foundation func ave(num: Array&lt;Double&gt;) -&gt; Double { var sum: Double = 0 for x in num{ sum += x } return sum / Double(num.count) } print(ave(num: [1, 2, 5])) ......
  • swift Array 数组 总结

    2015-09-09 17:59:43
    swift刚一出现的时候, 数组是作为引用类型出现的, 虽然作为一个结构体, 但是更像是类的对象的性质, 这可见数组的这个结构体的底层不单单是像C语言和OC中在栈区开辟空间去存储值类型那么简单, 猜测也是要去堆区开辟...
  • Swift数组总结

    2018-12-14 13:25:20
    // Swift数组总结 // // Created by Goddog on 15/7/14. // Copyright (c) 2015年 Goddog. All rights reserved. // import Foundation println("Swift数组对存储数据有具体要求。 不同于 Objective-C的...
  • Swift 允许我们将原生的字符串直接传递给一个接受 C String(即 char *)的 C API。 比如说,你可以在 Swift调用 strlen 函数,如下所示: import Darwin // or Glibc on Linuxstrlen("Hello
  • Swift 提供了三种基本数据类型,包括数组、集合、字典,都可用于存储值集合。数组是值的有序集合。集合是无序值的唯一值集合。字典是关键值关联的无序集合。   Swift 中的数组、集合、字典对它们可以存储的值和...
  • 字符串String1.声明// 空字符串 let emptyString = "" let anatherString = String() if emptyString.isEmpty { print("空的,我擦") }2.转义字符/// 转义字符\0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)...
  • iOS开发44-iOS Swift数组

    2015-11-24 20:51:12
    iOS开发44-iOS Swift数组 著作权声明:本文由http://my.csdn.net/Nathan1987_原创,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢
  • 该项目主要介绍了ocswift之间、swift内部几种常见的传值方式(属性传值、代码块传值、代理传值、通知) 如果ocswift之间的桥接有疑问请参考:OCswift桥接互调 项目介绍 1、swift之间的页面跳转与返回...
  • swift 中的数组

    2015-10-13 10:06:32
    swift可无缝的将oc中的NSArray和NSMubableArray转换为swift对象直接使用.不过在swift中本身也有个Array.他们之间还有 不少的区别. 1.swift找那个的Array是结构体,而oc中的两个数组是类 2.swift array的声明方式...
  • 判断数组是否为空 有人说可以用([array count]==0 )来判断是否为空,都是坑,如果array为空的话,执行count就会直接报错,程序崩溃退出。 正确判断NSArray是否为空的方法:用 (!array) if (array != nil &...
  • Swift调用OC之NSDictionary

    2015-03-08 09:55:22
    // Swift调用OC之NSDictionary // // 1.NSArray与Array的互相转换 // 2.NSDictionary初始化 // 3.NSDictionary常用的属性和方法 // --获得成员个数 // --获取Key或Value组成的数组(swiftDictiona
  • Swift3.0--懒加载

    2017-02-09 18:32:21
    // ViewController.swift import UIKit class ViewController: UIViewController { //初始化并且分配空间,会提前创建 //移动开发,延迟加载,减少内存消耗,就是懒加载 //懒加载 - lazy /** 1.能够延迟创建...
1 2 3 4 5 ... 20
收藏数 2,843
精华内容 1,137