1. 程式人生 > >iOS總結-NSArray的底層實現

iOS總結-NSArray的底層實現


有關NSArray的

不管是NSArray,還是nsmutable ,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原理

同樣的移除第一個元素,需要進行相同的動作

 

當陣列非常大時,就有問題了,NSMutableArray使用環形緩衝區,_NSArrayM用了環形緩衝區(circular buffer),這個資料結構相對簡單,只是比常規陣列/緩衝區複雜點.環形緩衝區的內容能在到達任意一段時繞向另一端.

環形緩衝區,在刪除的時候不會清楚指標, 如果我們在中間進行插入和刪除, 只會移動最少的一邊元素.

遍歷陣列的幾個方法:

for迴圈   NSEnumerator   forin   enumerateObjectsUsingBlock:(通過block回撥,在子執行緒中遍歷,物件的回撥次序是亂序的,而且呼叫執行緒會等待該遍歷過程完成:)  
這幾個 forin效能最好,for迴圈較低, 多執行緒遍歷方式是效能最差的.

__NSArrayI{
    NSInterger _userd;  陣列的元素個數,呼叫[array count]時,返回的就是_userd的值。
     id_list[0]; 當做id_list來用,即一個儲存id物件的buff.由於__NSArrayI的不可變,所以_list一旦分配,釋放之前都不會再有移動刪除操作了。
}
__NSArrayI的實現

 __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的實現

從實現來看,如果_list還沒有構成迴圈,第一次就獲得了全部元素,跟__NSArrayI一樣。但是如果_list構成了玄幻,就需要兩次,第一次獲取_offset到_list末端的元素,第二次獲取存放在_list起始處的剩餘元素。

__NSArrayM的_list是個迴圈陣列,它的其實由_offset標識.

forin速度最快的原因是遵從了NSFastEnumertation協議,它是直接從C陣列中去物件對於可變陣列來說,最多隻需要兩次就可以獲取全部資料。如果陣列沒有構成迴圈,第一次就獲得了全部元素,跟不可變陣列一樣,如果陣列構成了迴圈,那麼就需要兩次,第一次獲取物件陣列的起始偏移到迴圈陣列末端的元素,第二次獲取存放在迴圈陣列起始處的剩餘元素。而for迴圈之所以慢一點,是每次都要呼叫objectAtIndex:,新增@autoreleasepool,可以提高效率,如果我們每次遍歷不需要知道下標,選擇forin。

forin是基於快速列舉實現的,編譯器將for in 轉化為兩層迴圈,外層呼叫快速列舉方法批量獲取元素,內層通過c陣列取得一批元素中的每一個,並且在每次獲取元素前,檢查是否對陣列物件進行了變更操作,如果是,則丟擲異常。
block迴圈,系統已經幫我加了@autoreleasepool,其他的迴圈可以通過@autoreleasepool來優化。

NSEnumerationConcurrent+Block的方式耗時最大,我認為是因為它採用多執行緒,就這個方法來京,多執行緒的優勢並不在遍歷多快,它的回撥在各個子執行緒。

參考https://www.sohu.com/a/235998120_208051
https://www.jianshu.com/p/66f8410c6bbc