iOS Block Part7:block^内存管理

参考文档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只用了这个方法内的部分代码.

  • 对全局block进行拷贝会怎么样
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
     return aBlock;
}

返回原来的block

  • 对堆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

在日常开发中,用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.

  • 使用型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

  • strong self
#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
  • weak self
#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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容