Block device in Objective-C
- Tutorial
Objective-C has 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 loops, etc.), but block devices are usually not affected. Actually, let's fill this gap.
Instruments
Not everyone knows, but the cling has the -rewrite-objc option, which 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 assembler code received at the output of the compiler.
Convert the code
So, consider the following code:
#import
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 the 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 the structure by calling its default constructor (yes, in C ++ there is such a thing) and assigns it to a variable that stores 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 it contains an isa field that is familiar to every Objective-C programmer.
Just in case, let me remind you: each Objective-C object contains the isa attribute - a pointer to a class object for this object.
The base of any object in Objective-C looks like this:
typedef struct objc_class *Class;
struct objc_object {
Class isa ;
};
Thus, we come to an unexpected conclusion - blocks are classes!
Although, what is unexpected here, if in the days of 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 a link to the C function, which is the body of the
flags block - is zero
desc - contains information about the size of the __main_block_impl_0 structure and the function for copying blocks
Variables
In addition to the standard fields for any block, the __main_block_impl_0 structure stores variables captured by the block
NSString *string;
__Block_byref_blockString_0 *blockString;
As you can see, the variable captured with the __block modifier is wrapped in the __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;
};
The 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 of_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 link (field FuncPtr) to the C function, which is the body of the block, and, optionally, variables captured by the block.
The fact that a block is a class means that you can call all sorts of interesting methods on it (including those added independently through categories). A list of existing ones can be found here .
All blocks (Global, Stack, Malloc) are inherited from the base NSBlock, the description of which looks like this:
@interface NSBlock : NSObject {
}
- (id)copy;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)invoke;
- (void)performAfterDelay:(double)arg1;
@end
Since the block is the successor of NSObject, you can call everyone’s favorite description method on it, which helps you understand where your block is stored. For instance,
NSLog(@"%@", [^{} description]);
will output <__ NSGlobalBlock__: 0x1000010c0>. What hints at us is that the block we are using is stored in global memory.
As an alternative to description, you can use the class method.
Bonus
For fun, let's add a repeat method to the block.
#import
@interface NSBlock : NSObject
- (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 will be interested, then write. I will try to cover this topic.