NSFastEnumeration usage principle analysis

Introduction: collection classes play a very important role in every programming language, and each language is similar to the implementation of a collection class and the API provided. Compared to Java, C# and other programming languages, Objective-C is relatively weak, but the author has used it for a long time and become less accustomed to it. Before the RACSequence source in the design when Marvel also encountered a lot of fuzzy knowledge, NSFastEnumeration is one of them, the study notes, as for the RAC source code to read and analyze the day and then divided in sections.


First, look at the daily usage code for forin traversal:

NSUInteger IDX = 0; NSArray = *array @[@ @ "A", "B", "C"]; @ for (ID item in array (NSLog) {@ Item =%li, idx++, item% @ ");} / / operation results are as follows Item 0 = Item 1 = A B Item = 2 C

The code is simple, and compared to the regular foreach loop, forin can get the objects stored in the list directly, and the syntax is similar to meeting everyday usage habits. Foreach, forin, and – (void) enumerateObjectsUsingBlock: (void (^) (ID, obj, NSUInteger, IDX, BOOL, *stop)) block; alternating use almost completely meets our daily coding requirements. But when we think deeply, what is the logic behind these grammatical sugars? Are they all implemented on the same underlying code? What happens when custom objects also need to support this syntax sugar? Next, let’s look at the principle behind forin.


When you open the header file for NSArray, you first see the code for the following line:

@interface NSArray< __covariant ObjectType>: NSObject, <, NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

Obviously, the NSArray class implements the NSFastEnumeration protocol, and it is the NSFastEnumeration that makes NSArray support syntax candy such as forin. Open the NSFastEnumeration header file (see below) to see that it contains only one method and one structure, NSFastEnumerationState:

Typedef struct long ID __unsafe_unretained {unsigned state; __nullable * __nullable itemsPtr long * __nullable; unsigned mutationsPtr; unsigned long extra[5];} NSFastEnumerationState; @protocol - NSFastEnumeration (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *) state objects: (ID __unsafe_unretained buffer count: []) (NSUInteger) LEN; @end

The code looks a bit stupid, because the annotation and the structure of the header file without the use of any method introduced. There’s no way I can only search for API documents (click here). Here’s a snippet of API:

NSFastEnumeration usage principle analysis
countByEnumeratingWithState:objects:count:
NSFastEnumeration usage principle analysis
NSFastEnumerationState

I don’t see too much information, before we can continue to look down according to the following information: 1.forin is already thinking about sugar use is certainly behind traversal cycle structure of the original do-while/while? How to do-while/while loop structure index + 2 array of the original array traversal? 3., consider how to optimize the utilization of space?

Continue searching for documents and finding NSFastEnumeration is the Sample document:

NSFastEnumeration usage principle analysis
Sample Code

decided to download it and open it with Xocde. There are three files as follows:

NSFastEnumeration usage principle analysis
Project Files

EnumerationSample is the main debug method that can be ignored. Two files of EnumerableClass.h and EnumerableClass.mm are analyzed emphatically. Open the header file, as expected, there are very detailed notes (not posted here), interface definition @interface EnumerableClass: NSObject < NSFastEnumeration> implementation of the NSFastEnumeration protocol. In addition to realize object support index and block traversal of the API code is very simple, you can directly view the document here, only focus on the method of NSFastEnumeration protocol, the corresponding implementation method are found (comment too much, went straight to the theme of the original English delete notes):

#define USE_STACKBUF 1 - (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *) state objects: (ID __unsafe_unretained stackbuf count: []) (NSUInteger stackbufLength) {NSUInteger count = 0; unsigned = state; long countOfItemsAlreadyEnumerated 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]; countOfItemsAlreadyEnumerate) D++; count++;}} else {count = 0;} / / #else Method Two. if (countOfItemsAlreadyEnumerated) (< _list.size) {__unsafe_unretained const = _list.data (ID * const_array); state-> itemsPtr (__typeof__ = (state-> itemsPtr)) = _list.size (const_array; count); countOfItemsAlreadyEnumerated = (_list.size);} else {count} = 0; #endif return count;}

The above code analysis:
implementation of the 1.countByEnumeratingWithState method in two ways, the code example using macro USE_STACKBUF was distinguished, the main difference between the two ways: the first way is to use the buffer array parameters in second ways, but not directly hold the original array using a weak reference buffer array. I feel that the second is unsafe, so most of the implementation of the situation should be the use of the first medium. 2
read the code, the first approach can be seen: a.countByEnumeratingWithState must be called external circulation, use the structure pointer NSFastEnumerationState records the current state of the number of cycles to traverse, depends on the parameters of the stackbufLength buffer size. State is used to record the last traversal of the position b.NSFastEnumerationState, itemsPtr buffer array, mutationsPtr and extra are used to detect and control the change of the original array, the use of specific examples and did not give more information for future exploration. C. when the final return’Count = 0’is the end of the traversal.

Summary: forin is passed through an external buffer array, in the countByEnumeratingWithState method body, by repeatedly repeating the external call, according to the state state repeat fill buffer array and return the results. So how does the outside set the buffer size exactly? How do I traverse the countByEnumeratingWithState method?

Take the questions above to check the compiler, and translate the following forin code as follows:

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) (objc_getClass) (ID) ("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) {id = 0; 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) {NSLog (enumState.itemsPtr[counter++]; & __NSConstantStringImpl__var_folders_lv_h4gknfp17nq8wm59sqwgtgjm0000gn_T_EnumerationSample_88076b_mi_1 (NSString *), idx++, item);}; __continue_label_1:;} while (counter & lt; while (limit);} (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;

Analysis code: the
1. buffer array initializes an array of fixed size 16 __rw_items.
2. implements the traversal of the array through the nested DO-WHILE loop of the two layer, and answers the questions envisaged earlier. The
3. method, objc_enumerationMutation, is not invoked only when objects are changed in the traversal process to be called.
4. has a certain loss of efficiency compared to while.

Summary: through specific implementation forin code to read, to believe that when implementing NSFastEnumeration protocol must be ready, here is a simple record learning experience, we have found a new idea and welcome to discuss.