美文网首页iOS高级技术文章
iOS 数组的实现原理

iOS 数组的实现原理

作者: tanghaiyang | 来源:发表于2020-02-27 17:38 被阅读0次

    有关NSArray的

    image.png

    不管是NSArray,还是NSMutableArray ,alloc之后的得到都是__NSPlacrholderArray.
    当我们nsarray一个空数组,得到的是__NSArray0
    nsarray只有一个元素时,得到的是__NSSingleObjectArrayI
    nsarray.count > 1 时, 得到 __NSArrayI
    nsmutablearray 返回的都是__NSArrayM
    placeHolder 和 placeHoldes 的内存地址一样,说明是一个单例,该类内部只有一个isa指针,init后被新的实例换掉了

    CFArray 是CoreFoundation中的, 和Foundation中的NSArray相对应,他们是Toll-Free-Briaged. 用的环形缓冲区实现的.

    C数组的原理 连续的内存空间, 在下标0处插入一个元素时, 移动其后面所有的元素, 即memmove原理


    image.png

    同样的移除第一个元素,需要进行相同的动作


    image.png
    当数组非常大时,就有问题了,NSMutableArray使用环形缓冲区,_NSArrayM用了环形缓冲区(circular buffer),这个数据结构相对简单,只是比常规数组/缓冲区复杂点.环形缓冲区的内容能在到达任意一段时绕向另一端.

    环形缓冲区,在删除的时候不会清楚指针, 如果我们在中间进行插入和删除, 只会移动最少的一边元素.


    image.png

    遍历数组的几个方法:

    • for循环
    • NSEnumerator
    • forin
      enumerateObjectsUsingBlock:(通过block回调,在子线程中遍历,对象的回调次序是乱序的,而且调用线程会等待该遍历过程完成:)
      这几个 forin性能最好,for循环较低, 多线程遍历方式是性能最差的.

    __NSArrayI{
    NSInterger _userd; 数组的元素个数,调用[array count]时,返回的就是_userd的值。
    id_list[0]; 当做id_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了。
    }

    __NSArrayI的实现

    image.png
    __NSArrayI对这个方法的实现中,主要把内部数组的_list赋给state->itemsPtr,并返回_used数组大小。state->mutationsPtr指向一个局部静态变量,state->state看起来是一个标志,再次用同一个state调用这个方法就直接返回0.
    快速枚举的意思就是一下就把全部对象获取到了,而且在一个c数组里,之后要获得哪个位置的对象都可以快速寻址到,通过state->itemsPtr来访问数组。
    __NSSingleObjectArrayI{
    id object; 因为只有在创建只包含一个对象的不可变数组时,才会得到__NSSingleObjectArrayI对象,所以其内部结构更加简单
    }
    __NSArrayM{ 它的内部对象数组时一块连续内存id *_list
    NSUInterger _used; 当前对象数目 [nsmutablearray count]
    NSUInterger _offset; 时机对象数组的起始偏移
    int_size: 28; 已分配的_list大小,能存储的对象个数,不是字节数
    int_unused: 4;
    uint32_t _mutations;修改标记,每次对__NSArrayM的修改操作都会使_mutations +1 "Collection <__NSArrayM: 0x1002076b0> was mutated while being enumerated" 这个异常就是通过对_mutations的识别来引发的。
    id *_list;是个循环数组,并且在增删操作时会动态地重新分配以符合当前的存储需求
    }

    __NSArrayM的实现

    image.png

    从实现来看,如果_list还没有构成循环,第一次就获得了全部元素,跟__NSArrayI一样。但是如果_list构成了玄幻,就需要两次,第一次获取_offset到_list末端的元素,第二次获取存放在_list起始处的剩余元素。


    image.png

    __NSArrayM的_list是个循环数组,它的其实由_offset标识.
    forin速度最快的原因是遵从了NSFastEnumertation协议,它是直接从C数组中去对象对于可变数组来说,最多只需要两次就可以获取全部数据。如果数组没有构成循环,第一次就获得了全部元素,跟不可变数组一样,如果数组构成了循环,那么就需要两次,第一次获取对象数组的起始偏移到循环数组末端的元素,第二次获取存放在循环数组起始处的剩余元素。而for循环之所以慢一点,是每次都要调用objectAtIndex:,添加@autoreleasepool,可以提高效率,如果我们每次遍历不需要知道下标,选择forin。


    image.png
    forin是基于快速枚举实现的,编译器将for in 转化为两层循环,外层调用快速枚举方法批量获取元素,内层通过c数组取得一批元素中的每一个,并且在每次获取元素前,检查是否对数组对象进行了变更操作,如果是,则抛出异常。
    block循环,系统已经帮我加了@autoreleasepool,其他的循环可以通过@autoreleasepool来优化。

    NSEnumerationConcurrent+Block的方式耗时最大,我认为是因为它采用多线程,就这个方法来讲,多线程的优势并不在遍历多快,它的回调在各个子线程。

    相关文章

      网友评论

        本文标题:iOS 数组的实现原理

        本文链接:https://www.haomeiwen.com/subject/jnjxhhtx.html