OC advanced programming-deep block, how to capture variables, and how to store them On Stacks
First, let's take a look at several block-related questions.
This is a long blog post. The first part is the block test question, with the block syntax and features in the middle. The block explains the internal implementation of the block and the block storage location. Please read it with patience. If you have the block foundation, you can directly transfer it to the block implementation.
Five questions are listed below to check whether two or three questions can be correctly answered. It mainly involves block stack, stack, and how to capture variables. The answer is in the last line of the blog.
// ----------- First question: -------------- void exampleA () {char a = 'a'; ^ {printf ("% c \ n", A) ;};}. always running properly B. c. can run normally without using ARC. D. never run properly // ----------- Question 2: the answer is the same as that in question -------------- void exampleB_addBlockToArray (NSMutableArray * array) {char B = 'B'; [array addObject: ^ {printf ("% c \ n", B) ;}] ;}void exampleB () {NSMutableArray * array = [NSMutableArray array]; exampleB_addBlockToArray (array ); void (^ block) () = [array objectAtIndex: 0]; block ();} // ----------- third question: the answer is the same as the first question -------------- void exampleC_addBlockToArray (NSMutableArray * array) {[array addObject: ^ {printf ("C \ n") ;}];} void exampleC () {NSMutableArray * array = [NSMutableArray array]; exampleC_addBlockToArray (array ); void (^ block) () = [array objectAtIndex: 0]; block ();} // ----------- fourth question: the answer is the same as the first question -------------- typedef void (^ dBlock) (); dBlock exampleD_getBlock () {char d = 'D'; return ^ {printf ("% c \ n", D) ;};} void exampleD () {exampleD_getBlock ();} // ----------- fifth question: the answer is the same as the first question ------------ typedef void (^ eBlock) (); eBlock exampleE_getBlock () {char e = 'E'; void (^ block) () = ^ {printf ("% c \ n", E) ;}; return block ;} void exampleE () {eBlock block = exampleE_getBlock (); block ();}
Note: The above question is taken from: CocoaChina Forum click to open the link
Block summary what is block Blocks is an extension function of C language. Blocks's extended function can be expressed in one sentence: An anonymous function with an automatic variable (local variable. Naming is the essence of work. function names, variable names, method names, attribute names, class names, and framework names must all be available. The ability to write functions without names is quite attractive to programmers. For example, we want to make a URL request. In this case, how does the request result notify the caller? It is usually through the proxy (delegate), but writing delegate itself is the cost, we need to write classes, methods, and so on. At this time, we use block. Block provides methods similar to generating instances or objects by C ++ and OC classes to maintain variable values. Using block in this way can not declare C ++ and OC classes, nor use static variables, static global variables, or global variables, you can use an anonymous function with an automatic variable value by writing the source code of the C language function. Block is also used in other languages. The block implementation syntax seems very special, but it is actually processed as a very common C language code. Here we can take advantage of the clang Compiler's ability to convert to readable source code. The console command is clang-rewrite-objc source code file name.
int main(){ void (^blk)(void) = ^{printf("block\n");}; blk(); return 0;}
After clang-rewrite-objc, code programming is like this (simplified code allows readers to search for keywords in the generated file ):
struct __block_impl{ void *isa; int Flags; int Reserved; void *FuncPtr;};static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size}__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};struct __main_block_impl_0{ struct __block_impl impl; struct __main_block_desc_0 *Desc;}static struct __main_block_func_0(struct __main_block_impl_0 *__cself){ printf("block\n");}int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA); (*blk->impl.FuncPtr)(blk);}
Many struct, many underlined variables and function names. One by one: __block_impl: it is more like the base class of a block. All blocks have these fields.
_ Main_block_impl_0: block variable.
_ Main_block_func_0: Although block is called, it is an anonymous function. However, this function is still named by the compiler.
_ Main_block_desc_0: block description. Note that he has an instance named _ main_block_desc_0_DATA. The above naming rules apply: main is the name of the function where the block is located, the suffix 0 is the 0th blocks in this function. Since the above Code is C ++, You can summarize the struct of _ main_block_impl_0 and get the following form:
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc;}
Conclusion: block is the automatic variable value intercepted by Objective-C objects.
int val = 10;void (^blk)(void) = ^{printf("val=%d\n",val);};val = 2;blk();
In the above Code, the output value is: val = 10. Instead of 2. block intercepts the instantaneous values of automatic variables. Because the block stores the value of the automatic variable, after the block syntax is executed, the value of the automatic variable during block execution is not affected even if the value of the automatic variable used in the block is rewritten.
If you try to rewrite the automatic variables captured in the block, it will be a compilation error. I prefer to understand this as: All the automatic variables captured by the block will be converted to the const type. The solution is to add the modifier _ block to the automatic variable. What if the intercepted automatic variable is an OC object?
^ {[Array addObject: obj];};
There is no problem in writing this Because array is a pointer and we have not changed the value of the pointer. This can also explain the following problems.
Const char text [] = "hello ";
^ {Printf ("% c \ n", text [2]);};
This will cause compilation errors. Why? This is because the method for capturing automatic variables does not implement the C language array type. You can replace it with a pointer: const char * text = "hello"; so what is the object structure of this block? See the following:
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; int val;}
How is the val passed to the block struct?
int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val);}
Note that the last parameter of the function call is the val parameter. The code page of the function call is converted to the following code. The cself here is the same as the this and OC self of C ++.
static struct __main_block_func_0(struct __main_block_impl_0 *__cself){ printf("val=%d\n",__cself-val);}
Therefore, block capture variables are more like passing functions by value.
_ Block specifiers refer to the function where the block is located to capture automatic variables. But you cannot modify it. Otherwise, it is a compilation error. However, global variables, static variables, and Global static variables can be changed. In fact, it is difficult to understand these two features: first, why not let the variable be modified: This is determined by the compiler. In theory, you can modify the variable, but the block captures a copy of the automatic variable, with the same name. In order not to confuse developers, the assignment is not allowed. The truth is a bit like: Use pointers for function parameters. Otherwise, a copy is passed. 2. You can modify the value of a static variable. Static variables belong to a class, not a specific variable. Therefore, the cself pointer does not need to be called inside the block. So block can be called. Another way to solve the problem that block cannot save values is to use the _ block modifier.
__block int val = 10;void (^blk)(void) = ^{val = 1;};
The source code is converted as follows:
struct __block_byref_val_0{ void *__isa; __block_byref_val_0 *__forwarding; int _flags; int __size; int val;}
_ Main_block_impl_0 naturally has a field of _ block_byreg_val_0. Note: __block_byref_val_0 struct has its own pointer object. Do you want _ block int val = 10? This line of code is converted into the following struct _ block) byref_val_0 val = {0, & val, 0, sizeof (_ block_byref_val_0), 10}; // hold your own pointer. It turns into a struct. The reason for generating a struct is described in detail later. You cannot directly Save the val pointer Because val is on the stack and it is dangerous to save the pointer of the stack variable. The block storage area requires three terms: ● _ NSConcretStackBlock ● _ NSConcretGlobalBlock
● _ NSConcretMallocBlock
The three block storage methods are described as follows: Stack, global, and heap. Isa in the _ main_block_impl_0 struct is the value. [Point 1] If the block defined outside the function is global, and if the block inside the function is not captured, it is global. For example, the following code:
typedef int (^blk_t)(int);for(...){ blk_t blk = ^(int count) {return count;};}
Although this block is in a loop, the blk address remains unchanged. This block is in the global segment. [Point 2] One case cannot be compiled without ARC: typedef int (^ blk_t) (int); blk_t func (int rate) {return ^ (int count) {return rate * count ;}} this is because the block captures the rate automatic variable on the stack. At this time, the rate has become a struct, and the block has a pointer to this struct. That is, if block is returned, it is the pointer to the local variable. This point is precisely determined by the compiler. This problem is not found in ARC because ARC uses autorelease. [Point 3] Sometimes we need to call the block copy function to copy the block to the stack. See the following code:
-(id) getBlockArray{ int val =10; return [[NSArray alloc]initWithObjects: ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk1:%d",val);},nil];}id obj = getBlockArray();typedef void (^blk_t)(void);blk_t blk = (blk_t){obj objectAtIndex:0};blk();
This code will be abnormal in the last line of blk (), because the block in the array is on the stack. Because val is on the stack. The solution is to call the copy method. [Point 4] No matter where the block is configured, copying using the copy method will not cause any problems. In the ARC environment, if you are not sure whether to copy the block, just copy it. ARC will clean the battlefield. NOTE: If copy is called on the stack, copy is copied to the stack, and copy is called on the global block. The block reference count on the stack is added. [note] I used Xcode 5.1.1 iOS sdk 7.1 to compile and found that: not as described in advanced programming in Objective-C, int val must be on the stack. I saved the val address and checked whether the block changes before and after calling. The output consistency is on the stack, and the inconsistency is on the stack.
Typedef int (^ blkt1) (void);-(void) stackOrHeap {_ block int val = 10; int * valPtr = & val; // use the int pointer, to check whether the block is on the stack or on the stack, blkt1 s =^{ NSLog (@ "val_block = % d", ++ val); return val ;}; s (); NSLog (@ "valPointer = % d", * valPtr );}
Under the ARC -- block captures automatic variables, the block is directly generated to the stack. Val_block = 11 valPointer = 10 under non-ARC -- the block captures the automatic variable, and the block is still on the stack. Val_block = 11 valPointer = 11
The result after the copy operation is called:
-(Void) stackOrHeap {_ block int val = 10; int * valPtr = & val; // use the int pointer to check whether the block is on the stack, or heap blkt1 s = ^ {NSLog (@ "val_block = % d", ++ val); return val ;}; blkt1 h = [s copy]; h (); NSLog (@ "valPointer = % d", * valPtr );}
---------------- Under ARC >>>>>>>>>>>>> no effect. Val_block = 11 valPointer = 10
---------------- Under non-ARC >>>>>>>>>> it is indeed copied to the stack. Val_block = 11 valPointer = 10
Use this table to indicate/* When the block captures automatic variables -------------------------------------------------------------------- | where block stay | ARC | non-ARC | callback -------------------------------------------------------------------
| Copy | heap | ------------------------------------------------------------------ | no copy | heap | stack | Snapshot ------------------------------------------------------------------
*/_ Block Variable storage area when a block is copied to the stack, all the objects and variables it captures are also copied to the stack. Recall that when a block captures automatic variables, the automatic variable will program a struct. The struct contains a field named _ forwarding, which is used to point to the automatic struct. With this _ forwarding pointer, the value of the automatic variable will be correctly accessed whether the block on the stack is copied to the stack or copied to the stack. The intercepted object block will hold the captured object. To distinguish between automatic variables and objects, the compiler has a type to distinguish them.
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){ _Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);}static void __main_block_dispose_0(struct __main_block_impl_0 *src){ _block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF);}
BLOCK_FIELD_IS_BYREF indicates a variable. BLOCK_FIELD_IS_OBJECT indicates the object [_ block variables and objects]
_ Block modifier can be used for automatic variables of any type
[_ Block loop reference]
According to the content above, when a block holds an object, if the object holds a block, it will cause circular reference. There are two solutions:
1. Use the _ weak modifier. Id _ weak obj = obj _
2. Use the _ block modifier. _ Block id tmp = self; then, tmp = nil in the block; this breaks the loop. Remember to set tmp = nil. Not recommended!
Block test answer: ABABB
int val = 10;void (^blk)(void) = ^{printf("val=%d\n",val);};val = 2;blk();
In the above Code, the output value is: val = 10. Instead of 2. block intercepts the instantaneous values of automatic variables. Because the block stores the value of the automatic variable, after the block syntax is executed, the value of the automatic variable during block execution is not affected even if the value of the automatic variable used in the block is rewritten.
If you try to rewrite the automatic variables captured in the block, it will be a compilation error. I prefer to understand this as: All the automatic variables captured by the block will be converted to the const type. The solution is to add the modifier _ block to the automatic variable. What if the intercepted automatic variable is an OC object?
^ {[Array addObject: obj];};
There is no problem in writing this Because array is a pointer and we have not changed the value of the pointer. This can also explain the following problems.
Const char text [] = "hello ";
^ {Printf ("% c \ n", text [2]);};
This will cause compilation errors. Why? This is because the method for capturing automatic variables does not implement the C language array type. Can be replaced by a pointer: const char * text = "hello ";