99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Objective-C Fast Enumeration 實(shí)現(xiàn)原理

發(fā)布時(shí)間:2016-10-02 23:00  回復(fù):0  查看:2319   最后回復(fù):2016-10-02 23:00  

在 Objective-C 語言2.0 版本中提供了快速枚舉的語法,它是我們遍歷集合元素的首選方法,因?yàn)樗哂幸韵聝?yōu)點(diǎn):

 

比直接使用 NSEnumerator 更高效;

 

語法非常簡潔;

 

如果集合在遍歷的過程中被修改,它會拋出異常;

 

可以同時(shí)執(zhí)行多個(gè)枚舉。

 

那么問題來了,它是如何做到的呢?我想,你應(yīng)該也跟我一樣,對 Objective-C 中快速枚舉的實(shí)現(xiàn)原理非常感興趣,事不宜遲,讓我們來一探究竟吧。

 

解析 NSFastEnumeration 協(xié)議

在 Objective-C 中,我們要想實(shí)現(xiàn)快速枚舉就必須要實(shí)現(xiàn) NSFastEnumeration 協(xié)議,在這個(gè)協(xié)議中,只聲明了一個(gè)必須實(shí)現(xiàn)的方法:

 

/**

 Returns by reference a C array of objects over which the sender should iterate, and as the return value the number of objects in the array.

 @param state  Context information that is used in the enumeration to, in addition to other possibilities, ensure that the collection has not been mutated.

 @param buffer A C array of objects over which the sender is to iterate.

 @param len    The maximum number of objects to return in stackbuf.

 @discussion The state structure is assumed to be of stack local memory, so you can recast the passed in state structure to one more suitable for your iteration.

 @return The number of objects returned in stackbuf. Returns 0 when the iteration is finished.

 */

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state

                                  objects:(id __unsafe_unretained [])stackbuf

                                    count:(NSUInteger)len

其中,結(jié)構(gòu)體 NSFastEnumerationState 的定義如下:

 

typedef struct {

    unsigned long state;

    id __unsafe_unretained __nullable * __nullable itemsPtr;

    unsigned long * __nullable mutationsPtr;

    unsigned long extra[5];

} NSFastEnumerationState;

說實(shí)話,剛開始看到這個(gè)方法的時(shí)候,其實(shí)我是拒絕的,原因你懂的。好吧,先不吐槽了,言歸正傳,下面,我們將對這個(gè)方法進(jìn)行全方位的剖析:

 

首先,我們需要了解的最重要的一點(diǎn),那就是這個(gè)方法的目的是什么?概括地說,這個(gè)方法就是用于返回一系列的 C 數(shù)組,以供調(diào)用者進(jìn)行遍歷。為什么是一系列的 數(shù)組呢?因?yàn)椋谝粋€(gè) for/in 循環(huán)中,這個(gè)方法其實(shí)會被調(diào)用多次,每一次調(diào)用都會返回一個(gè) 數(shù)組。至于為什么是 數(shù)組,那當(dāng)然是為了提高效率了。

 

既然要返回 C 數(shù)組,也就意味著我們需要返回一個(gè)數(shù)組的指針和數(shù)組的長度。是的,我想你應(yīng)該已經(jīng)猜到了,數(shù)組的長度就是通過這個(gè)方法的返回值來提供的,而數(shù)組的指針則是通過結(jié)構(gòu)體 NSFastEnumerationState 的 itemsPtr 字段進(jìn)行返回的。所以,這兩個(gè)值就一起定義了這個(gè)方法返回的 數(shù)組。

 

通常來說,NSFastEnumeration 允許我們直接返回一個(gè)指向內(nèi)部存儲的指針,但是并非所有的數(shù)據(jù)結(jié)構(gòu)都能夠滿足內(nèi)存連續(xù)的要求。因此,NSFastEnumeration 還為我們提供了另外一種實(shí)現(xiàn)方案,我們可以將元素拷貝到調(diào)用者提供的一個(gè) 數(shù)組上,即 stackbuf ,它的長度由參數(shù) len 指定。

 

在本文的開頭,我們提到了如果集合在遍歷的過程中被修改的話,NSFastEnumeration 就會拋出異常。而這個(gè)功能就是通過 mutationsPtr 字段來實(shí)現(xiàn)的,它指向一個(gè)這樣的值,這個(gè)值在集合被修改時(shí)會發(fā)現(xiàn)改變。因此,我們就可以利用它來判斷集合在遍歷的過程中是否被修改。

 

現(xiàn)在,我們還剩下 NSFastEnumerationState 中的 state 和 extra 字段沒有進(jìn)行介紹。實(shí)際上,它們是調(diào)用者提供給被調(diào)用者自由使用的兩個(gè)字段,調(diào)用者根本不關(guān)心這兩個(gè)字段的值。因此,我們可以利用它們來存儲任何對自身有用的值。

 

揭密快速枚舉的內(nèi)部實(shí)現(xiàn)

說了這么多,感覺好像 NSFastEnumeration 是你設(shè)計(jì)的一樣,你到底是怎么知道的呢?額,我說我是瞎猜的,你信么?好了,不開玩笑了。接下來,我們就一起來探究一下快速枚舉的內(nèi)部實(shí)現(xiàn)。假設(shè),我們有一個(gè) main.m 文件,其中的代碼如下:

 

#import

 

  

   

 

int main(int argc, char * argv[]) {

    NSArray *array = @[ @1, @2, @3 ];

    for (NSNumber *number in array) {

        if ([number isEqualToNumber:@1]) {

            continue;

        }

        NSLog(@"%@", number);

        break;

    }

}

 

  

接著,我們使用下面的 clang 命令將 main.m 文件重寫成 C++ 代碼:

 

clang -rewrite-objc main.m

得到 main.cpp 文件,其中 main 函數(shù)的代碼如下:

 

int main(int argc, char * argv[]) {

    // 創(chuàng)建數(shù)組 @[ @1, @2, @3 ]

    NSArray *array = ((NSArray *(*)(Class, SEL, const ObjectType *, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);

    {

        NSNumber * number;

        // 初始化結(jié)構(gòu)體 NSFastEnumerationState

        struct __objcFastEnumerationState enumState = { 0 };

        // 初始化數(shù)組 stackbuf

        id __rw_items[16];

        id l_collection = (id) array;

        // 第一次調(diào)用 - countByEnumeratingWithState:objects:count: 方法,形參和實(shí)參的對應(yīng)關(guān)系如下:

        // state -> &enumState

        // stackbuf -> __rw_items

        // len -> 16

        _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) {

            // 獲取 mutationsPtr 的初始值

            unsigned long startMutations = *enumState.mutationsPtr;

            // 外層的 do/while 循環(huán),用于調(diào)用 - countByEnumeratingWithState:objects:count: 方法,獲取 數(shù)組

            do {

                unsigned long counter = 0;

                // 內(nèi)層的 do/while 循環(huán),用于遍歷獲取到的 數(shù)組

                do {

                    // 判斷 mutationsPtr 的值是否有發(fā)生變化,如果有則使用 objc_enumerationMutation 函數(shù)拋出異常

                    if (startMutations != *enumState.mutationsPtr) objc_enumerationMutation(l_collection);

                    // 使用指針的算術(shù)運(yùn)算獲取相應(yīng)的集合元素,這是快速枚舉之所以高效的關(guān)鍵所在

                    number = (NSNumber *)enumState.itemsPtr[counter++];

                    {

                        if (((BOOL (*)(id, SEL, NSNumber *))(void *)objc_msgSend)((id)number, sel_registerName("isEqualToNumber:"), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1))) {

                            // continue 語句的實(shí)現(xiàn),使用 goto 語句無條件轉(zhuǎn)移到內(nèi)層 do 語句的末尾,跳過中間的所有代碼

                            goto __continue_label_1;

                        }

                        NSLog((NSString *)&__NSConstantStringImpl__var_folders_cr_xxw2w3rd5_n493ggz9_l4bcw0000gn_T_main_fc7b79_mi_0, number);

                        // break 語句的實(shí)現(xiàn),使用 goto 語句無條件轉(zhuǎn)移到最外層 if 語句的末尾,跳出嵌套的兩層循環(huán)

                        goto __break_label_1;

                    };

                    // goto 語句標(biāo)號,用來實(shí)現(xiàn) continue 語句

                    __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)));

            number = ((NSNumber *)0);

            // goto 語句標(biāo)號,用來實(shí)現(xiàn) break 語句

            __break_label_1: ;

        } else {

            number = ((NSNumber *)0);

        }

    }

}

如上代碼所示,快速枚舉其實(shí)就是用兩層 do/while 循環(huán)來實(shí)現(xiàn)的,外層循環(huán)負(fù)責(zé)調(diào)用 - countByEnumeratingWithState:objects:count: 方法,獲取 數(shù)組,而內(nèi)層循環(huán)則負(fù)責(zé)遍歷獲取到的 數(shù)組。同時(shí),我想你應(yīng)該也注意到了它是如何利用 mutationsPtr 來檢測集合在遍歷過程中的突變的,以及使用 objc_enumerationMutation 函數(shù)來拋出異常。

 

正如我們前面提到的,在快速枚舉的實(shí)現(xiàn)中,確實(shí)沒有用到結(jié)構(gòu)體 NSFastEnumerationState 中的 state 和 extra 字段,它們只是提供給 - countByEnumeratingWithState:objects:count: 方法的實(shí)現(xiàn)者自由使用的字段。

 

值得一提的是,我特意在 main.m 中加入了 continue 和 break 語句。因此,我們有機(jī)會看到了在 for/in 語句中是如何利用 goto 來實(shí)現(xiàn) continue 和 break 語句的。

 

實(shí)現(xiàn) NSFastEnumeration 協(xié)議

看到這里,我相信你對 Objective-C 中快速枚舉的實(shí)現(xiàn)原理已經(jīng)有了一個(gè)比較清晰地認(rèn)識。下面,我們就一起來動手實(shí)現(xiàn)一下 NSFastEnumeration 協(xié)議。

 

我們前面已經(jīng)提到了,NSFastEnumeration 在設(shè)計(jì)上允許我們使用兩種不同的方式來實(shí)現(xiàn)它。如果集合中的元素在內(nèi)存上是連續(xù)的,那么我們可以直接返回這段內(nèi)存的首地址;如果不連續(xù),比如鏈表,就只能使用調(diào)用者提供的 數(shù)組 stackbuf 了,將我們的元素拷貝到這個(gè) 數(shù)組上。

 

接下來,我們將通過一個(gè)自定義的集合類 Array ,來演示這兩種不同的實(shí)現(xiàn) NSFastEnumeration 協(xié)議的方式。注:完整的項(xiàng)目代碼可以在這里找到。

 

@interface Array : NSObject

 

  

   

 

- (instancetype)initWithCapacity:(NSUInteger)numItems;

@end

@implementation Array {

    std::vector

 

   

    

   _list;

}

- (instancetype)initWithCapacity:(NSUInteger)numItems {

    self = [super init];

    if (self) {

        for (NSUInteger i = 0; i < numItems; i++) {

            _list.push_back(@(random()));

        }

    }

    return self;

}

#define USE_STACKBUF 1

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])stackbuf count:(NSUInteger)len {

    // 這個(gè)方法的返回值,即我們需要返回的 數(shù)組的長度

    NSUInteger count = 0;

    // 我們前面已經(jīng)提到了,這個(gè)方法是會被多次調(diào)用的

    // 因此,我們需要使用 state->state 來保存當(dāng)前遍歷到了 _list 的什么位置

    unsigned long countOfItemsAlreadyEnumerated = state->state;

    // 當(dāng) countOfItemsAlreadyEnumerated 為 時(shí),表示第一次調(diào)用這個(gè)方法

    // 我們可以在這里做一些初始化的設(shè)置

    if (countOfItemsAlreadyEnumerated == 0) {

        // 我們前面已經(jīng)提到了,state->mutationsPtr 是用來追蹤集合在遍歷過程中的突變的

        // 它不能為 NULL ,并且也不應(yīng)該指向 self

        //

        // 這里,因?yàn)槲覀兊?nbsp;Array 類是不可變的,所以我們不需要追蹤它的突變

        // 因此,我們的做法是將它指向 state->extra 的其中一個(gè)值

        // 因?yàn)槲覀冎?nbsp;NSFastEnumeration 協(xié)議本身并沒有用到 state->extra

        //

        // 但是,如果你的集合是可變的,那么你可以考慮將 state->mutationsPtr 指向一個(gè)內(nèi)部變量

        // 而這個(gè)內(nèi)部變量的值會在你的集合突變時(shí)發(fā)生變化

        state->mutationsPtr = &state->extra[0];

    }

#if USE_STACKBUF

    // 判斷我們是否已經(jīng)遍歷完 _list

    if (countOfItemsAlreadyEnumerated < _list.size()) {

        // 我們知道 state->itemsPtr 就是這個(gè)方法返回的 數(shù)組指針,它不能為 NULL

        // 在這里,我們將 state->itemsPtr 指向調(diào)用者提供的 數(shù)組 stackbuf

        state->itemsPtr = stackbuf;

        // 將 _list 中的元素填充到 stackbuf 中,直到以下兩個(gè)條件中的任意一個(gè)滿足時(shí)為止

        // 1. 已經(jīng)遍歷完 _list 中的所有元素

        // 2. 已經(jīng)填充滿 stackbuf

        while (countOfItemsAlreadyEnumerated < _list.size() && count < len) {

            // 取出 _list 中的元素填充到 stackbuf 

            stackbuf[count] = _list[countOfItemsAlreadyEnumerated];

            // 更新我們的遍歷位置

            countOfItemsAlreadyEnumerated++;

            // 更新我們返回的 數(shù)組的長度,使之與 state->itemsPtr 中的元素個(gè)數(shù)相匹配

            count++;

        }

    }

#else

    // 判斷我們是否已經(jīng)遍歷完 _list

    if (countOfItemsAlreadyEnumerated < _list.size()) {

        // 直接將 state->itemsPtr 指向內(nèi)部的 數(shù)組指針,因?yàn)樗膬?nèi)存地址是連續(xù)的

        __unsafe_unretained const id * const_array = _list.data();

        state->itemsPtr = (__typeof__(state->itemsPtr))const_array;

        // 因?yàn)槲覀円淮涡苑祷亓?nbsp;_list 中的所有元素

        // 所以,countOfItemsAlreadyEnumerated 和 count 的值均為 _list 中的元素個(gè)數(shù)

        countOfItemsAlreadyEnumerated = _list.size();

        count = _list.size();

    }

#endif

    // 將本次調(diào)用得到的 countOfItemsAlreadyEnumerated 保存到 state->state 

    // 因?yàn)?nbsp;NSFastEnumeration 協(xié)議本身并沒有用到 state->state

    // 所以,我們可以將這個(gè)值保留到下一次調(diào)用

    state->state = countOfItemsAlreadyEnumerated;

    // 返回 數(shù)組的長度

    return count;

}

@end

 

   

 

  

我已經(jīng)在上面的代碼中添加了必要的注釋,相信你理解起來應(yīng)該沒有什么難度。不過,值得一提的是,在第二種方式的實(shí)現(xiàn)中,我們用到了 ARC 下不同所有權(quán)對象之間的相互轉(zhuǎn)換,代碼如下:

 

__unsafe_unretained const id * const_array = _list.data();

state->itemsPtr = (__typeof__(state->itemsPtr))const_array;

其實(shí),這里面涉及到兩次類型轉(zhuǎn)換,第一次是從 __strong NSNumber * 類型轉(zhuǎn)換到 __unsafe_unretained const id * 類型,第二次是從 __unsafe_unretained const id * 類型轉(zhuǎn)換到 id __unsafe_unretained * 類型,更多信息可以查看 AutomaticReferenceCounting 中的 4.3.3 小節(jié)。

 

另外,我在前面的文章《ReactiveCocoa v2.5 源碼解析之架構(gòu)總覽》中,已經(jīng)有提到過,ReactiveCocoa 中的 RACSequence 類其實(shí)是實(shí)現(xiàn)了 NSFastEnumeration 協(xié)議的。因?yàn)?nbsp;RACSequence 中的元素在內(nèi)存上并不連續(xù),所以它采用的是第一種實(shí)現(xiàn)方式。對此感興趣的同學(xué),可以去看看它的實(shí)現(xiàn)源碼,這里不再贅述。

 

總結(jié)

本文從NSFastEnumeration 協(xié)議的定義出發(fā),解析了 - countByEnumeratingWithState:objects:count: 方法中的返回值以及各個(gè)參數(shù)的含義;接著,我們使用 clang -rewrite-objc 命令探究了快速枚舉的內(nèi)部實(shí)現(xiàn);最后,通過一個(gè)自定義的集合類 Array 演示了兩種實(shí)現(xiàn) NSFastEnumeration 協(xié)議的方式,希望本文能夠?qū)δ阌兴鶐椭?/span>

 

 

文章來源:CocoaChina

您還未登錄,請先登錄

熱門帖子

最新帖子

?