前言:集合類在每門編程語(yǔ)言中都有著非常重要的地位末盔,每一門語(yǔ)言對(duì)于集合類的實(shí)現(xiàn)和提供的API也大同小異拾因。相比于Java浇辜、C#等編程語(yǔ)言O(shè)bjective-C相對(duì)薄弱一些刘急,不過筆者用久了了就慢慢習(xí)慣了函荣,思考也少了显押。之前看
RACSequence
源碼時(shí)驚嘆于其中的設(shè)計(jì)之余也遇到好多模糊的知識(shí)點(diǎn)扳肛,NSFastEnumeration
就是其中之一,在此做下學(xué)習(xí)筆記乘碑,至于RAC的源碼閱讀和分析日后再分章節(jié)奉上挖息。
首先看一下forin遍歷的日常使用代碼:
NSUInteger idx = 0;
NSArray *array = @[@"A",@"B",@"C"];
for(id item in array)
{
NSLog(@"Item %li = %@", idx++, item);
}
//運(yùn)行結(jié)果如下
Item 0 = A
Item 1 = B
Item 2 = C
代碼再簡(jiǎn)單不過了,相比于普通的foreach
循環(huán)forin
可以直接得到列表中存儲(chǔ)的對(duì)象兽肤,語(yǔ)法類似滿足日常的使用習(xí)慣套腹。foreach、forin以及-(void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
交替使用幾乎完全滿足了我們?nèi)粘>幋a需求资铡〉缳鳎可是當(dāng)我們?cè)偻罾锼伎家幌拢@些語(yǔ)法糖背后的邏輯是什么笤休?是不是都是基于相同的底層代碼實(shí)現(xiàn)的呢尖飞?當(dāng)自定義的對(duì)象也需要支持這樣的語(yǔ)法糖該怎么辦?接下來就一起看看forin
背后的原理店雅。
打開NSArray
的頭文件政基,首先看到的是下面一行的代碼:
@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
很顯然NSArray
類實(shí)現(xiàn)NSFastEnumeration
協(xié)議,正是NSFastEnumeration
使得NSArray
支持forin
這樣的語(yǔ)法糖底洗。打開NSFastEnumeration
頭文件(如下)可以看到其中只包含一個(gè)方法和一個(gè)結(jié)構(gòu)體NSFastEnumerationState
:
typedef struct {
unsigned long state;
id __unsafe_unretained __nullable * __nullable itemsPtr;
unsigned long * __nullable mutationsPtr;
unsigned long extra[5];
} NSFastEnumerationState;
@protocol NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len;
@end
代碼看起來有點(diǎn)懵腋么,因?yàn)轭^文件中沒有任何有關(guān)方法使用的注釋和結(jié)構(gòu)體的介紹。沒辦法只能去搜API文檔(點(diǎn)此前往)亥揖,下面我貼出來API介紹的片段:
依然看不出太多的信息珊擂,在繼續(xù)往下看之前我們不妨根據(jù)已有信息做如下的思考:1.forin
只是語(yǔ)法糖,背后遍歷使用的肯定是原始的循環(huán)結(jié)構(gòu)do-while/while
? 2.數(shù)組的下標(biāo)索引+
原始的do-while/while
循環(huán)結(jié)構(gòu)如何實(shí)現(xiàn)數(shù)組的遍歷费变?3.再考慮空間利用率該如何優(yōu)化摧扇?
繼續(xù)搜索文檔找到了NSFastEnumeration
是Sample文檔:
果斷下載下來并用Xocde打開,里面有如下三個(gè)文件:
EnumerationSample
是main
調(diào)試方法可以忽略。重點(diǎn)分析EnumerableClass.h
和EnumerableClass.mm
兩個(gè)文件挚歧。打開頭文件果然里面有非常詳細(xì)的注釋(這里就不貼出來)扛稽,接口定義@interface EnumerableClass : NSObject <NSFastEnumeration>
實(shí)現(xiàn)了NSFastEnumeration
協(xié)議。此外還實(shí)現(xiàn)了對(duì)象支持下標(biāo)索引和block遍歷的API滑负,代碼很簡(jiǎn)單可以直接查看文檔在张,這里只關(guān)注NSFastEnumeration
的協(xié)議方法,直奔主題找到對(duì)應(yīng)的實(shí)現(xiàn)方法如下(注釋太多矮慕,刪除了原先英文注釋):
#define USE_STACKBUF 1
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
objects:(id __unsafe_unretained [])stackbuf
count:(NSUInteger)stackbufLength
{
NSUInteger count = 0;
unsigned long countOfItemsAlreadyEnumerated = state->state;
if(countOfItemsAlreadyEnumerated == 0)
{
state->mutationsPtr = &state->extra[0];
}
#if USE_STACKBUF // Method One.
if(countOfItemsAlreadyEnumerated < _list.size())
{
state->itemsPtr = stackbuf;
while((countOfItemsAlreadyEnumerated < _list.size()) && (count < stackbufLength))
{
stackbuf[count] = _list[countOfItemsAlreadyEnumerated];
countOfItemsAlreadyEnumerated++;
count++;
}
}
else
{
count = 0;
}
#else // Method Two.
if (countOfItemsAlreadyEnumerated < _list.size())
{
__unsafe_unretained const id * const_array = _list.data();
state->itemsPtr = (__typeof__(state->itemsPtr))const_array;
count = _list.size();
countOfItemsAlreadyEnumerated = _list.size();
}
else
{
count = 0;
}
#endif
state->state = countOfItemsAlreadyEnumerated;
return count;
}
分析一下上面的代碼:
1.countByEnumeratingWithState
方法的實(shí)現(xiàn)有兩種方式帮匾,例子代碼中使用宏USE_STACKBUF
進(jìn)行了區(qū)分,兩種方式的主要區(qū)別是:第一種方式是利用了參數(shù)中的緩沖數(shù)組痴鳄,第二種方式?jīng)]有使用緩沖數(shù)組而是直接持有了原數(shù)組的一個(gè)弱引用瘟斜。筆者感覺第二種是不安全的,因此大多情況的實(shí)現(xiàn)應(yīng)該是使用第一中方式。
2.仔細(xì)閱讀第一種方式的實(shí)現(xiàn)代碼螺句,可以看出:a.countByEnumeratingWithState
肯定是被外部循環(huán)調(diào)用的虽惭,使用結(jié)構(gòu)體指針NSFastEnumerationState
記錄當(dāng)前最新的遍歷狀態(tài),循環(huán)的次數(shù)取決于參數(shù)stackbufLength
緩沖區(qū)的大小蛇尚。b.NSFastEnumerationState
中state
用來記錄最后遍歷的位置芽唇,itemsPtr
指向緩沖區(qū)數(shù)組,mutationsPtr
和extra
是用來檢測(cè)和控制原始數(shù)組改變的,具體使用例子中并沒有給出更多信息佣蓉,待以后探索披摄。c.當(dāng)最終返回'count = 0'是表示遍歷結(jié)束。
小結(jié):
forin
是通過外部傳入一個(gè)緩沖數(shù)組勇凭,在countByEnumeratingWithState
方法體內(nèi)通過外部的不斷循環(huán)調(diào)用根據(jù)state狀態(tài)重復(fù)填充緩沖區(qū)數(shù)組并返回結(jié)果疚膊。那么外部是究竟是如何設(shè)置緩沖區(qū)大小的?如何遍歷countByEnumeratingWithState
方法虾标?
帶著上面的疑問來查看一下編譯器翻譯后的forin
代碼如下:
int main (int argc, const char * argv[])
{
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool;
EnumerableClass *example = ((id (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)((EnumerableClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("EnumerableClass"), sel_registerName("alloc")), sel_registerName("initWithCapacity:"), (NSUInteger)50);
NSUInteger idx = 0;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lv_h4gknfp17nq8wm59sqwgtgjm0000gn_T_EnumerationSample_88076b_mi_0);
idx = 0;
{
id item;
struct __objcFastEnumerationState enumState = { 0 };
id __rw_items[16];
id l_collection = (id) example;
_WIN_NSUInteger limit =
((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)
((id)l_collection,
sel_registerName("countByEnumeratingWithState:objects:count:"),
&enumState, (id *)__rw_items, (_WIN_NSUInteger)16);
if (limit) {
unsigned long startMutations = *enumState.mutationsPtr;
do {
unsigned long counter = 0;
do {
if (startMutations != *enumState.mutationsPtr)
objc_enumerationMutation(l_collection);
item = (id)enumState.itemsPtr[counter++];
{
NSLog((NSString *)&__NSConstantStringImpl__var_folders_lv_h4gknfp17nq8wm59sqwgtgjm0000gn_T_EnumerationSample_88076b_mi_1, idx++, item);
};
__continue_label_1: ;
} while (counter < limit);
} while ((limit = ((_WIN_NSUInteger (*) (id, SEL, struct __objcFastEnumerationState *, id *, _WIN_NSUInteger))(void *)objc_msgSend)
((id)l_collection,
sel_registerName("countByEnumeratingWithState:objects:count:"),
&enumState, (id *)__rw_items, (_WIN_NSUInteger)16)));
item = ((id)0);
__break_label_1: ;
}
else
item = ((id)0);
}
}
return 0;
}
分析代碼:
1.緩沖區(qū)數(shù)組初始化固定的大小為16
的數(shù)組__rw_items
寓盗。
2.通過兩層的do-while
嵌套循環(huán)實(shí)現(xiàn)了數(shù)組的遍歷,回答了前面的設(shè)想的問題璧函。
3.方法objc_enumerationMutation
只有遍歷過程中對(duì)象被改變了才會(huì)被調(diào)用傀蚌,這一點(diǎn)還沒有深入研究。
4.相比于while
循環(huán)效率上會(huì)有一定的損失蘸吓。
總結(jié):通過
forin
具體實(shí)現(xiàn)代碼的閱讀善炫,相信以后再去實(shí)現(xiàn)NSFastEnumeration
協(xié)議時(shí)肯定是胸有成竹了,這里只是簡(jiǎn)單的記錄學(xué)習(xí)心得库继,大家有新的發(fā)現(xiàn)和想法歡迎一起討論箩艺。