📜 ⬆️ ⬇️

Device Blocks in Objective-C

image
In Objective-C there is such a thing as blocks, which is an implementation of the concept of closures .

There are many articles on how to use blocks correctly (when to call copy, how to get rid of retain cycles, etc.), but the device blocks are usually not affected. Actually, let's fill this gap.

Instruments


Not everyone knows, but in the klang there is an option -rewrite-objc that converts code from Objective-C to C ++. It is in C ++, not C, because in addition to Obective-C, Objective-C ++ is also supported.

It is used as follows:
clang -rewrite-objc -ObjC main.m -o out.cpp 

Actually, the idea is to use this option to understand what the blocks turn into, and thus get rid of the analysis of the assembler code obtained at the output of the compiler.
')

Convert code


So, consider the following code:
 #import <Foundation/Foundation.h> typedef int (^blk_t)(int intVar); int main(int argc, const char * argv[]) { __block NSString *blockString; NSString *string; blk_t blk = ^(int intVar) { blockString = @"Hello"; NSLog(@"%@", string); return intVar; }; blk(0); return 0; } 


After conversion, the code will look like this (insignificant parts are omitted):
 #ifndef BLOCK_IMPL #define BLOCK_IMPL struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; // Runtime copy/destroy helper functions (from Block_private.h) #ifdef __OBJC_EXPORT_BLOCKS extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int); extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int); extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32]; extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32]; #else __OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int); __OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int); __OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32]; __OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32]; #endif #endif #define __block #define __weak typedef int (*blk_t)(int intVar); struct __Block_byref_blockString_0 { void *__isa; __Block_byref_blockString_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSString *blockString; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSString *string; __Block_byref_blockString_0 *blockString; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static int __main_block_func_0(struct __main_block_impl_0 *__cself, int intVar) { __Block_byref_blockString_0 *blockString = __cself->blockString; // bound by ref NSString *string = __cself->string; // bound by copy (blockString->__forwarding->blockString) = (NSString *)&__NSConstantStringImpl__var_folders_v1_v8wjvjd96vl0nhqsjtxs_c2h0000gn_T_main_1cea37_mi_0; NSLog((NSString *)&__NSConstantStringImpl__var_folders_v1_v8wjvjd96vl0nhqsjtxs_c2h0000gn_T_main_1cea37_mi_1, string); return intVar; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockString, (void*)src->blockString, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->string, (void*)src->string, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blockString, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->string, 3/*BLOCK_FIELD_IS_OBJECT*/);} static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_blockString_0 blockString = {(void*)0,(__Block_byref_blockString_0 *)&blockString, 33554432, sizeof(__Block_byref_blockString_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131}; ; NSString *string; blk_t blk = (int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, string, (__Block_byref_blockString_0 *)&blockString, 570425344); ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 0); return 0; } 


What is a block


At first glance, C ++ code looks confusing, but let's take it in order.

Block creation:
 blk_t blk = (int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, string, (__Block_byref_blockString_0 *)&blockString, 570425344); 

This line creates a structure by calling its default constructor (yes, in C ++ there is such a thing) and assigns it to the variable storing the block. Thus, it turns out that the block is a structure.

Let's take a look at this structure now.
 struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSString *string; __Block_byref_blockString_0 *blockString; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 

As you can see, the __block_impl structure is the basis for the block. And contains the isa field, which is familiar to every programmer on Objective-C.
Just in case, let me remind you: each Objective-C object contains an isa attribute - a pointer to the class object for this object.

The basis of any object in Objective-C is as follows:
 typedef struct objc_class *Class; struct objc_object { Class isa ; }; 

Thus, we come to an unexpected conclusion - blocks are classes!
Although, what’s unexpected here, if during the times of the MRC, blocks regularly had to send copy.
 - (void)someMethod { return [^() { ... } copy]; } 

If you look at the constructor of the __main_block_impl_0 structure, you can see how the fields of the block structure are initialized when it is created.
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *_string, __Block_byref_blockString_0 *_blockString, int flags=0) : string(_string), blockString(_blockString->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } 

That is, it turns out that in this case our block is an object of type _NSConcreteStackBlock.
FuncPtr - stores the reference to the C function, which is the body of the block
flags is zero
desc - contains information about the size of the __main_block_impl_0 structure and functions for copying blocks

Variables


In addition to the standard for any block of fields, the structure __main_block_impl_0 stores variables captured by the block.
  NSString *string; __Block_byref_blockString_0 *blockString; 


As you can see, a variable that is captured with the __block modifier wraps itself in a __Block_byref_blockString_0 structure.
 struct __Block_byref_blockString_0 { void *__isa; __Block_byref_blockString_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSString *blockString; }; 

Initialization of which looks like this:
 __attribute__((__blocks__(byref))) __Block_byref_blockString_0 blockString = {(void*)0,(__Block_byref_blockString_0 *)&blockString, 33554432, sizeof(__Block_byref_blockString_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131}; ; 

That is, it turns out that the original object is stored in the blockString field. And the __forwarding field points to itself.
By the way, in __main_block_impl_0 the blockString field is initialized with the value_blockString -> __ forwarding.
Actually, the __forwarding field is made so that several blocks can refer to one variable that is on the stack or on the heap.

Conclusion


So, we can say that a block is an instance of a special class (NSMallocBlock, NSGlobalBlock, NSStackBlock) that necessarily contains a reference (FuncPtr field) to the C function, which is the body of the block, and, optionally, variables that are captured by the block.

The fact that a block is a class means that it can call all sorts of interesting methods (including those added independently through categories). A list of existing ones can be searched here .

All blocks (Global, Stack, Malloc) are inherited from the base NSBlock, the description of which looks like this:
 @interface NSBlock : NSObject <NSCopying> { } - (id)copy; - (id)copyWithZone:(struct _NSZone { }*)arg1; - (void)invoke; - (void)performAfterDelay:(double)arg1; @end 

Since the block is an inheritor of NSObject, it is possible to call everyone’s favorite description method, which will help you understand where your block is stored. For example,
 NSLog(@"%@", [^{} description]); 

will print <__ NSGlobalBlock__: 0x1000010c0>. What hints to us that the block we are using is stored in global memory.
As an alternative to the description, you can use the class method.

Bonus


Let's joke for the sake of adding a repeat method to the block.

 #import <Foundation/Foundation.h> @interface NSBlock : NSObject <NSCopying> - (void)invoke; @end @interface NSBlock (Ext) - (void)repeat:(NSUInteger)count; @end @implementation NSBlock (Ext) - (void)repeat:(NSUInteger)count { for (int i = 0; i < count; i++) { [(NSBlock *)self invoke]; } } @end int main(int argc, const char * argv[]) { [^{ NSLog(@"Hello"); } repeat:3]; return 0; } 

P.S


I deliberately omitted information about the details regarding memory management (copying blocks, ...).
If someone is interested, write. I will try to highlight this topic.

Source: https://habr.com/ru/post/224363/


All Articles