Block

iOS Block Part7:block^内存管理

2017-08-24  本文已影响148人  破弓

参考文档1:BlocksRuntime/runtime.c
参考文档2:BlocksRuntime/Block_private.h
参考文档3:BlocksRuntime/Block.h

理解block的拷贝只是开始,
1.block(int any),block(NSString * any),block(__block int any),block(__block NSString * any)真的就这四类block吗?
2.iOS Block Part6所提到的block拷贝是栈block拷贝生成堆block的过程,那么对全局block进行拷贝会有什么效果?对堆block进行拷贝又会有什么效果?block的内存到底如何管理?
3.block如何造成循环引用?
4.如何避免?为什么用weak修饰的self就不会产生循环引用?
......诸如此类的问题.
本系列文章也只是抛砖引玉,很多东西,还得自己去看去理解!

1. 四小类block,其实有些狭隘

/*******************************************************

Entry points used by the compiler - the real API!


A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables

In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers.  The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign.  The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16).

So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.

When  a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions.  Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor.  And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.

So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
    __block id                   128+3
        __weak block id              128+3+16
    __block (^Block)             128+7
    __weak __block (^Block)      128+7+16
        
The implementation of the two routines would be improved by switch statements enumerating the eight cases.

以上是BlocksRuntime/runtime.c内的一段话.
很容易看出,block捕获的外围参数绝不仅限于int any,NSString * any,__block int any,__block NSString * any这四种.
本系列文章未深入解析这部分内容的原因是:文字陈述功力有限,不敢越雷池.
就以上面提到的3) Other Blocks为例子:
block捕获block,这内存关系,臣妾做不到啊!所以笔者只写出基本的四类.想了解更复杂类型block的内存关系,得自己对block的原理有了一定了解后,自己再看源码.

2.block的内存管理

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}
static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    //printf("_Block_copy_internal(%p, %x)\n", arg, flags);
    if (!arg) return NULL;

    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
}

回到block拷贝调用的_Block_copy_internal,前篇文章已经说了,栈block拷贝生成堆block只用了这个方法内的部分代码.

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
     return aBlock;
}

返回原来的block

堆block的flags为什么是BLOCK_NEEDS_FREE,栈block拷贝生成堆block的时候有赋值,很容易看明白,不多解释

if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
      latching_incr_int(&aBlock->flags);
      return aBlock;
}

latching_incr_int加引用计数.
在看block的结构:

//Block_private.h内Block_layout的结构:
struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

//编译的结构:
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;
};

很容易看出:
Block_layout = __main_block_impl_0与__block_impl的合二为一

既有isa又有引用计数(记在flags内),所以block也是一个对象.block是存在还是销毁全看block的引用计数.

_Block_copy,就有_Block_release.
latching_incr_int,就有latching_decr_int.
相生相克,无休无止,有兴趣可以在BlocksRuntime/runtime.c里看看.

3.造成循环引用

3.1引用对象
NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
    NSLog(@"%@",any);
};
test();
block(NSString)_map.png
block捕获参数的拷贝方法

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
   .
   .
   .
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);//变量加引用计数
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

由上图很明显能看出,由block编译生成的结构体__main_block_impl_0是如何持有NSString * any的.
再加上代码,我也能看到,持有变量的引用计数相应加1.

所以block销毁,NSString * any才有可以释放.

3.2循环引用

ViewController内使用block来说明问题.
一概而论,block写了self就会造成循环引用肯定是错的.
比如在ViewController写了如下代码:

[UIView animateWithDuration:0.5 animations:^{
     [self doSomethings];
}];

无论block内的代码执行与否.ViewController的销毁都会正常.

那怎么才会造成循环引用呢?
所谓循环引用多数情况下都是多方在内存上构成了一个引用闭环.

在日常开发中,用Block做消息传递十分常见.请看一下代码:

@interface ZCOnView : UIView
@property(nonatomic,copy)void (^clickMarkBlock)(BOOL isMarked);
@end

ZCOnView * any = [[ZCOnView alloc]initWithFrame:CGRectMake(64.0, 64.0, 64.0, 64.0)];
any.backgroundColor = [UIColor redColor];
any.clickMarkBlock = ^(BOOL isMarked){
   [self doSomethings];
};
[self.view addSubview:any];
VC与UI控件关系不必多言--强
Block被UI控件持有,UI控件存在,Block也就存在--强
Block持有VC--强
       VC
    /      \
   /        \
UI控件------Block
引用闭环形成.

在以上代码的Block内写上self(或者用了成员变量),肯定会造成循环引用.所以我称这种Block持有型Block.

再反观代码:

[UIView animateWithDuration:0.5 animations:^{
     [self doSomethings];
}];
VC在堆上
Block在堆上

Block执行完毕,Block销毁==>Block不持有VC.
没有形成引用闭环,当然就没有循环引用.
我们姑且称这类block使用型Block吧!

Block可以分为持有型Block+使用型Block.持有型Block内部肯定不能用self.而使用型Block内部可以放心的使用self.才怪!!!(😁 使用型Block内部用self,虽然不会造成循环引用,但会有Block使用时的另一个问题==>延迟.本文就不细说了.想了解请戳)

4.避免循环引用

4.1 ARC环境下

区分Block类型再看看能不能用self.如果觉得麻烦,一竿子打死也行,全用__weak __typeof(&*self)weakSelf = self也可以.
那么weak修饰的变量又是如何躲过循环引用的呢?

环境:ARC
命令:clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations AnyThing.m

#import <Foundation/Foundation.h>
@interface AnyThing : NSObject
@end

#import "AnyThing.h"
@implementation AnyThing
- (void)someAct{
    void (^test)() = ^ {
        NSLog(@"%@",self);
    };
    test();
}
@end
// @implementation AnyThing


struct __AnyThing__someAct_block_impl_0 {
    struct __block_impl impl;
    struct __AnyThing__someAct_block_desc_0* Desc;
    AnyThing *const __strong self;//<<<不同
    __AnyThing__someAct_block_impl_0(void *fp, struct __AnyThing__someAct_block_desc_0 *desc, AnyThing *const __strong _self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __AnyThing__someAct_block_func_0(struct __AnyThing__someAct_block_impl_0 *__cself) {
    AnyThing *const __strong self = __cself->self; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_AnyThing_51e757_mi_0,self);
}

static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __AnyThing__someAct_block_dispose_0(struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __AnyThing__someAct_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __AnyThing__someAct_block_impl_0*, struct __AnyThing__someAct_block_impl_0*);
    void (*dispose)(struct __AnyThing__someAct_block_impl_0*);
} __AnyThing__someAct_block_desc_0_DATA = { 0, sizeof(struct __AnyThing__someAct_block_impl_0), __AnyThing__someAct_block_copy_0, __AnyThing__someAct_block_dispose_0};

static void _I_AnyThing_someAct(AnyThing * self, SEL _cmd) {
 void (*test)() = ((void (*)())&__AnyThing__someAct_block_impl_0((void *)__AnyThing__someAct_block_func_0, &__AnyThing__someAct_block_desc_0_DATA, self, 570425344));
 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}

// @end
#import <Foundation/Foundation.h>
@interface AnyThing : NSObject
@end

#import "AnyThing.h"
@implementation AnyThing
- (void)someAct{
    __weak __typeof(&*self)weakSelf = self;
    void (^test)() = ^ {
        NSLog(@"%@",weakSelf);
    };
    test();
}
@end
// @implementation AnyThing


struct __AnyThing__someAct_block_impl_0 {
    struct __block_impl impl;
    struct __AnyThing__someAct_block_desc_0* Desc;
    __weak typeof (&*self) weakSelf;//<<<不同
    __AnyThing__someAct_block_impl_0(void *fp, struct __AnyThing__someAct_block_desc_0 *desc, __weak typeof (&*self) _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __AnyThing__someAct_block_func_0(struct __AnyThing__someAct_block_impl_0 *__cself) {
    __weak typeof (&*self) weakSelf = __cself->weakSelf; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_AnyThing_fda013_mi_0,weakSelf);
}

static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __AnyThing__someAct_block_dispose_0(struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __AnyThing__someAct_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __AnyThing__someAct_block_impl_0*, struct __AnyThing__someAct_block_impl_0*);
    void (*dispose)(struct __AnyThing__someAct_block_impl_0*);
} __AnyThing__someAct_block_desc_0_DATA = { 0, sizeof(struct __AnyThing__someAct_block_impl_0), __AnyThing__someAct_block_copy_0, __AnyThing__someAct_block_dispose_0};

static void _I_AnyThing_someAct(AnyThing * self, SEL _cmd) {
 __attribute__((objc_ownership(weak))) __typeof(&*self)weakSelf = self;
 void (*test)() = ((void (*)())&__AnyThing__someAct_block_impl_0((void *)__AnyThing__someAct_block_func_0, &__AnyThing__someAct_block_desc_0_DATA, weakSelf, 570425344));
 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}

// @end

根据上面的不同,知道:

strong
struct __AnyThing__someAct_block_impl_0对self保持强引用
weak
struct __AnyThing__someAct_block_impl_0对self保持弱引用

strong weak 对捕获对象的拷贝,传入flag一模一样

static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    ......
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);//加引用计数?
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

strong self 加引用计数,可以理解!
weak self 加引用计数,是不是有些矛盾?

不要奇怪,ARC环境有了更完善的内存管理,如果外部变量由__strongcopystrong修饰时,Block会把捕获的变量用__strong来修饰进而达到持有的目的.这里的_Block_retain_object只不过是一个空操作.
以下代码见于BlocksRuntime/runtime.c

static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void _Block_retain_object_default(const void *ptr __unused) {
}
4.2 MRC环境下

在一些文章里也看到过,在MRC环境下,用__block修饰对象也能避免循环引用.那我们也通过编译后的代码来看一下其内在原理.

注意上面提到过的_Block_retain_object方法,在ARC中,它是一个空操作.
在MRC中,_Block_retain_object可不是一个空控制,_Block_retain_object会给传入的对象加引用计数.

iOS Block Part5里,我们已经知道,在ARC环境下__block修饰对象,会有以下对对象的拷贝动作.

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
131==BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER;

而MRC环境下也是一样的.
所以在_Block_object_assign内走的是以下代码片段,很好的避开了给对象加引用计数的_Block_retain_object方法,所以也就不会有循环引用.

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    ...
}

5.抛砖引玉

虽然我已经仔细的检查了自己的相关代码和相关的措辞,但是请不要盲目相信本文的正确性。
我已经见过非常多的经验开发者对于 Block 有错误的理解(我也不会例外)。请一定保持一颗怀疑的心。
by 酷酷的哀殿

关于Block的认识文章改了许多遍,每回都以为自己已经得其精髓,但一次一次证明自己还是太浅薄.本系列文章也只是抛砖引玉,很多东西,还得自己去看去理解!


参考文献:
Block技巧与底层解析 by tripleCC

上一篇下一篇

猜你喜欢

热点阅读