Intercepted object

The following source code generates and holds an object of the NSMutableArray class, which is immediately released and discarded because the scope of the copy target variable with the __strong modifier ends immediately.

{
	id array = [[NSMutableArray alloc] init];
}
Copy the code
typedef void (^blk_t)(id);
blk_t blk ;
    
{
    id array =[[NSMutableArray alloc] init];
     blk = [^(id obj){
        [array addObject:obj];
        NSLog(@"array count = %ld",[array count]);
    } copy];
}

    
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
Copy the code

The NSMutableArray class object copied to array must be released and discarded, but the result is normal:

2017-12-14 09:48:26.226464+0800 ImageOrientation = 1 2017-12-14 09:48:26.226664 +0800 ImageOrientation = 1 2017-12-14 09:48:26.226621+0800 ImageOrientation[7963:2739050] ImageOrientation = 2 2017-12-14 09:48:26.226759+0800 ImageOrientation[7963:2739050 count = 3Copy the code

The converted code looks like this:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) { id array = __cself->array; // bound by copy ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_6__v3tcjsp9277_82bgpvhr8l6c0000gn_T_main_3310c3_mi_0,((NSUInteger  (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count"))); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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, char * argv[]) { typedef void (*blk_t)(id); blk_t blk ; { id array =((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
    }


    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));


}

Copy the code

OC’s runtime library is able to accurately timing when blocks are copied from the stack to the heap and blocks on the heap are deprecated, so blocks can be properly initialized and deprecated even if they contain variables with __strong or __weak modifiers. To do this, copy and dispose, the member variables added to the __main_block_desc_0 structure, are used, along with the __main_block_copy_0 function and __main_block_dispose_0 assigned as Pointers to the member variables

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};
Copy the code

The __main_block_copy_0 function uses the _Block_object_assign function to assign an object of type to the Block’s structure member variable array and holds the object.

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

The _Block_object_assign function is equivalent to the retain instance method, which assigns an object to a structure member variable of the object type.

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); }Copy the code

The __main_block_dispose_0 function uses the _Block_object_dispose method, which is equivalent to the release instance method, to free an object assigned to a structure member variable of the object type.

Although copy and dispose function Pointers are assigned to the __main_block_DESc_0 structure member variables copy and Dispose, they are not called in the converted code, but when blocks are copied from the stack to the heap and blocks on the heap are discarded.

function Call time
copy Blocks on the stack are copied to the heap
dispose Blocks on the heap are discarded

Blocks on the stack can be copied to the heap in one of the following ways:

  • When a Block’s copy instance method is called
  • When a Block is returned as a function return value
  • When assigning a Block to a class with the __strong modifier ID or to a member variable of Block type
  • When passing a Block in a Cocoa framework method with usingBlock in the method name, or in the Grand Central Dispatch API

The above can be summarized as follows: Block is copied from stack to heap when the __Block_copy function is called.

Instead, dispose is called when a Block copied to the heap is released and no one is holding it so that it is discarded. The Dispose function is equivalent to the Dealloc instance method.

In fact, copy and dispose functions are already used when interpreting the __block specifier.


#include <stdio.h>int main(int argc, char * argv[]) { __block int val = 0; void(^blk)(void) = ^{val=1; }; blk();printf("val = %d\n",val);
    
}

Copy the code

Converted code:

struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; int val; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_val_0 *val; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; // bound by ref (val->__forwarding->val)=1; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/); } 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, char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0}; void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);printf("val = %d\n",(val.__forwarding->val));

}

Copy the code

The difference:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/); }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); }

The BLOCK_FIELD_IS_OBJECT and BLOCK_FIELD_IS_BYREF arguments are used to distinguish the object type of copy and Dispose functions from that of __block.

object BLOCK_FIELD_IS_OBJECT
__block variable BLOCK_FIELD_IS_BYREF

Thus, objects in blocks that are copied to automatic variables with the __strong modifier and __block variables copied to the heap can exist outside their variable scope because they are held by the Block on the heap.


__block variables and objects

The __block specifier can specify any type of automatic variable.

__block id obj = [[NSObject alloc] init];
Copy the code

This code is equivalent to

__block id __strong obj = [[NSObject alloc] init];

Copy the code

When ARC is in effect, the ID type and object type variables must have an ownership modifier attached, and the __strong modifier is the default default.

Converted code:

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id obj;
};



static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}



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


    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};


}

Copy the code

In the case of an ID type or an object type automatic variable with an __strong modifier attached to the Block, the _Block_object_assign function holds the objects that the Block intercepts when the Block is copied from the stack to the heap. When a Block on the heap is discarded, use the _Block_object_dispose function to dispose the objects that the Block intercepts.

When a __block variable is copied from the stack to the heap, the _Block_object_assign function holds the object assigned to the __block variable. When a __block variable on the heap is discarded, use the _Block_object_dispose function to release the assignment to the object holding the __block variable.

Thus, even if an object assignment is copied to the __block variable of the object type with the __strong modifier on the heap, the object will continue to be held as long as the __block variable persists on the heap.

If you use the __weak modifier, run the following code. Objective-c advanced programming for iOS and OS X multithreading and memory management explains that the __weak variable array is assigned nil out of scope, so the result is 0, but what I’m actually running is 1,2,3

int main(int argc, char * argv[]) {
    
    
    typedef void (^blk_t)(id);
    blk_t blk ;
    
    {
        id array =[[NSMutableArray alloc] init];
        id __weak array2 = array;
        blk = [^(id obj){
            [array2 addObject:obj];
            NSLog(@"array count = %ld",[array count]);
        } copy];
    }
    
    
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    
}
Copy the code

And then I use clang-rewrite-objC-f /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Framew Orks/Foundation framework. The main m trying to convert a c + + code, direct error TAT

 cannot create __weak reference because the current deployment target does
      not support weak references
        id __attribute__((objc_ownership(weak))) array2 = array;

Copy the code

I then replaced __weak with the __unsafe_unretained modifier, and the conversion succeeded as follows:

struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id array2; id array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array2, id _array, int flags=0) : array2(_array2), array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) { id array2 = __cself->array2; // bound by copy id array = __cself->array; // bound by copy ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_6__v3tcjsp9277_82bgpvhr8l6c0000gn_T_main_aef6da_mi_0,((NSUInteger  (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count"))); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array2, (void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/); _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/); _Block_object_dispose((void*)src->array, 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, char * argv[]) { typedef void (*blk_t)(id); blk_t blk ; { id array =((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        id __attribute__((objc_ownership(none))) array2 = array;
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, array, 570425344)), sel_registerName("copy"));

    }


    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));

}


Copy the code

My own understanding is: Array2 = array2 = array2 = array2 = array2 = array2 = array2 = array2 = array2 The __main_block_copy_0 method, which calls the _Block_object_assign method, holds the __main_block_IMPL_0 member variables array and array2, so both objects exist at the time of the call. So it runs 1,2,3.

Welcome to discuss, is the running result in the book wrong? (~ y▽)~* Cover mouth and laugh


Block loop reference

typedef void (^blk_t)(void);

@interface MyObject:NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject

-(id) init
{
    self = [super init];
    blk_ = ^{NSLog(@"self = %@",self); };return self;
}

-(void) dealloc
{
    NSLog(@"dealloc");
}

@end


int main(int argc, char * argv[]) {
    
    id o =[[MyObject alloc] init];
    NSLog(@"% @",o);
    return 0;
}

Copy the code

Warning at compile time

Capturing 'self' strongly in this block is likely to lead to a retain cycle
Copy the code

The running results are as follows:

2017-12-14 20:51:48.691611+0800 ImageOrientation[10678:3502717] <MyObject: 0x60400000CCD0 >Copy the code

The Block member variable BLK_ of class MyObject holds a strong reference copied as a Block. That is, the MyObject class holds blocks in the opposite direction. The Block syntax executed in the init instance method uses the id-type variable self with the __strong modifier. And because the Block syntax assignment is in the member variable BLk_, the Block generated on the stack by Block syntax is then copied to the heap, holding the self used. Self holds the Block, and the Block holds self, and a circular reference occurs.

In addition, the compiler already warns about ~

To avoid circular references, assign self to a variable with an __weak modifier attached.

-(id) init { self = [super init]; id __weak temp = self; // In this code, since self must exist, there is no need to determine if temp is nil"self = %@",temp); };return self;
}

Copy the code

In addition, the following code also has a circular reference. This is because self references Block, and Block intercepts self’s member variable obj_.

typedef void (^blk_t)(void);

@interface MyObject:NSObject
{
    blk_t blk_;
    id obj_;
}
@end

@implementation MyObject

-(id) init
{
    self = [super init];
    blk_ = ^{NSLog(@"obj_ = %@",obj_); }; //blk_ = ^{NSLog(@"obj_ = %@",self->obj_)};
    return self;
}
Copy the code

This problem can also be solved with the __weak modifier.

-(id) init
{
    self = [super init];
    id __weak temp = obj_;
    blk_ = ^{NSLog(@"obj_ = %@", temp); };return self;
}
Copy the code

In addition, you can use __block variables to avoid circular references.

typedef void (^blk_t)(void);

@interface MyObject:NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject

-(id) init
{
    self = [super init];
    __block id  temp = self;
    blk_ = ^{
        NSLog(@"self = %@",temp);
        temp = nil;
    };
    return self;
}

-(void)execBlock
{
    blk_();
}

-(void) dealloc
{
    NSLog(@"dealloc");
}

@end


int main(int argc, char * argv[]) {
    
    id o =[[MyObject alloc] init];
    [o execBlock];
    return 0;
}
Copy the code

This code causes circular references and memory leaks if it does not call the exeBlock instance method, that is, does not execute the Block assigned to the member variable BLK_.

The following circular reference is raised in a state that generates and holds an object of class MyObject:

  • MyObject class objects hold blocks
  • Block holds a __block variable
  • The __block variable holds the MyObject class object

By executing the execBlock method, the Block is executed and the __block variable temp is assigned to nil. Therefore, strong references to MyObject are invalidated by the __block variable temp

  • MyObject class objects hold blocks
  • Block holds a __block variable

Contrast the __block variable with the __weak(__unsafe_unretained) modifier to avoid recurring references:

  • The holding period of an object is controlled by a __block variable
  • Do not use __unsafe_unretained in environments where the __weak modifier cannot be used

Nil or any other object can be dynamically assigned to a __block variable when a Block is executed.

The disadvantage of using a __block variable is that blocks must be executed in order to avoid circular references.


copy/release

When ARC is invalid, you generally need to manually copy blocks from the stack to the heap. Copy is assigned and release is released by the release class.

As long as a Block is copied once and configured on the heap, it can be held through the retain instance method.

However, calling the retain instance method for a Block configured on the stack does nothing. Therefore, the copy instance method is recommended to hold blocks.

In addition, Blocks is an extension of C, so Block syntax can be used in C. In this case, the Block_copy and Block_release functions can be used instead of copy/release.

In addition, __block specifiers are used to avoid circular references in blocks when ARC is invalid. This is because when a Block is copied from stack to heap, automatic variables with an ID or object type attached to the __block specifier will not be retained. An automatic variable that is not accompanied by a __block specifier is retained.