block定义

struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};

从上面代码看出,Block_layout就是对block结构体的定义:

isa指针:指向表明该block类型的类。

flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。

reserved:保留变量,我的理解是表示block内部的变量数。

invoke:函数指针,指向具体的block实现的函数调用地址。

descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

举例,定义一个最简单block 打印hello world:

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

    void (^block)()=^{printf("hello world");};
block();
return ;
}

使用clang指令

clang -rewrite-objc main.m

得到一个cpp文件,编译后,你就会看到什么是block了

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("hello world");
} static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) { void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return ;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { , };

你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。

再看看值捕获的问题

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

    int a=;
//__block int a=10; //__block前缀
void (^block)()=^{printf("打印a=%d",a);};
block(); return ;
}

定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会影响到外面的a变量的。

而加了__block前缀,编译后:

struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
}; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
printf("打印a=%d",(a->__forwarding->a));}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, /*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, /*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 = { , 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_a_0 a = {(void*),(__Block_byref_a_0 *)&a, , sizeof(__Block_byref_a_0), }; void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, ));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return ;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { , };

并不是直接传递a的值了,而是把a的地址(&a)传过去了,所以在block内部便可以修改到外面的变量了。

isa:isa指针,在Objective-C中,任何对象都有isa指针。block 有三种类型:
_NSConcreteGlobalBlock 全局静态,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

#include int main()
{
^{ printf("Hello, World!\n"); } ();
return ;
}

_NSConcreteStackBlock 保存在栈中,出函数作用域就销毁,例如:

#include int main()
{
char a = 'A';
^{ printf("%c\n",a); } ();
return ;
}

_NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁

该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:];
block();
}

总结一下:

_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。

_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。

_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

而ARC和MRC中,还略有不同

题目:下面代码在按钮点击后,在ARC下会发生什么,MRC下呢?为什么?

@property(nonatomic, assign) void(^block)();

- (void)viewDidLoad {
[superviewDidLoad];
int value = ;
void(^blockC)() = ^{
NSLog(@"just a block === %d", value);
}; NSLog(@"%@", blockC);
_block = blockC; } - (IBAction)action:(id)sender {
NSLog(@"%@", _block);
}

在ARC 打印:

mytest[:] test:<__NSMallocBlock__: 0x60000005f3e0>
mytest[:] NSShadow {, -} color = {(null)}

虽然不会crash,第二个是野指针

MRC 会打印:test:<__NSStackBlock__: 0x7fff54941a38> 然后crash

例如:

 NSArray *testArr = @[@"", @""];
NSLog(@"block is %@", ^{
NSLog(@"test Arr :%@", testArr); });//结果:block is <__NSStackBlock__: 0x7fff54f3c808> void (^TestBlock)(void) = ^{
NSLog(@"testArr :%@", testArr);
};
NSLog(@"block2 is %@", TestBlock);//block2 is <__NSMallocBlock__: 0x600000045e80>
//其实上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock
//即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.

循环引用

  Block的循环引用是比较容易被忽视,原本也是相对比较难检查出来的问题。当然现在苹果在XCode编译的层级就已经做了循环引用的检查,所以这个问题的检查就突然变的没有难度了。

  简单说一下循环引用出现的原理:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏。下面举个例子说明一下:

@interface ObjTest () {
NSInteger testValue;
}
@property (copy, nonatomic) void (^block)();
@end @implement ObjTest
- (void)function {
self.block = ^() {
self.testValue = ;
};
}
@end

在这个例子中,ObjTest拥有了一个名字叫block的Block对象;然后在这个Block中,又对ObjTest的一个成员变量testValue进行了赋值。于是就产生了循环引用:ObjTest->block->ObjTest。

  要避免循环引用的关键就在于破坏这个闭合的环。在目前只考虑ARC环境的情况下,笔者所知的只有一种方法可以破坏这个环:在Block内部对拥有者使用弱引用。

@interface ObjTest () {
NSInteger testValue;
}
@property (copy, nonatomic) void (^block)();
@end @implement ObjTest
- (void)function {
__weak ObjTest* weakSelf = self;
self.block = ^() {
weakSelf.testValue = ;
};
}
@end

在单例模式下 Block避免循环引用,如下:

@interface Singleton : NSObject
@property (nonatomic, copy) void(^block)();
+ (instancetype)share;
@end @implementation Singleton
+ (instancetype)share {
static Singleton *singleton;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[Singleton alloc] init];
});
return singleton;
}
@end //============分割线=================
//控制器中代码的实现 @implementation NextViewController - (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf=self; void (^blockTest)()=^(){
// NSLog(@"print %@", self);//会内存泄漏
NSLog(@"print %@", weakSelf);
}; Singleton *singleton = [Singleton share];
singleton.block = blockTest;
}
- (IBAction)btnClick:(UIButton *)sender { [Singleton share].block();
} - (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
@end

为什么iOS中系统的block方法可以使用self

因为:首先循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。当然是强引用。
所以UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。

ARC情况下:
1、如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。

解决方法:新建一个指针(__weak typeof(Target) weakTarget = Target )指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。
2、如果用weak修饰Block,该Block就会存放在栈空间。不会出现循环引用问题。MRC情况下用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。
返回值类型(^block变量名)(形参列表) = ^(形参列表) {};调用Block保存的代码block变量名(实参);默认情况下,,Block内部不能修改外面的局部变量Block内部可以修改使用__block修饰的局部变量

参考 收藏:https://www.zhihu.com/question/30779258/answer/49492783

Objective-C中的Block原理

云端之巅 Objective-C中block的底层原理

iOS学习之block总结及block内存管理(必看)

Block总结以及内存管理

iOS之Block总结以及内存管理的更多相关文章

  1. 看朋友日志发现的一个ios下block相关的内存管理问题,非常奇怪,请大家帮忙一起来回答!

    http://blog.csdn.net/fengsh998/article/details/38090205 这篇文章以下是我的回复.相同的代码仅仅是把变量的定义从局部变量改为类的成员变量就发现了非 ...

  2. iOS经典面试题总结--内存管理

    iOS经典面试题总结--内存管理 内存管理 1.什么是ARC? ARC是automatic reference counting自动引用计数,在程序编译时自动加入retain/release.在对象被 ...

  3. iOS学习笔记之ARC内存管理

    iOS学习笔记之ARC内存管理 写在前面 ARC(Automatic Reference Counting),自动引用计数,是iOS中采用的一种内存管理方式. 指针变量与对象所有权 指针变量暗含了对其 ...

  4. iOS ARC编译器规则和内存管理规则

    iOS 开发当中,自动引用计数已经是标准的内存管理方案.除了一些老旧的项目或者库已经没有人使用手动来管理内存了吧. ARC无疑是把开发者从繁琐的保留/释放引用对象逻辑中解脱出来.但这并不是万事大吉了, ...

  5. 高手写的“iOS 的多核编程和内存管理”

    原文地址:http://anxonli.iteye.com/blog/1097777 多核运算 在iOS中concurrency编程的框架就是GCD(Grand Central Dispatch), ...

  6. iOS开发系列—Objective-C之内存管理

    概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没 ...

  7. iOS学习17之OC内存管理

    1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄 ...

  8. iOS学习之Object-C语言内存管理

    一.内存管理的方式      1.iOS应用程序出现Crash(闪退),90%的原因是因为内存问题.      2.内存问题:      1)野指针异常:访问没有所有权的内存,如果想要安全的访问,必须 ...

  9. iOS技术面试02:内存管理

    怎么保证多人开发进行内存泄露的检查. 如何定位内存泄露? 1> 使用Analyze进行代码的静态分析(检测有无潜在的内存泄露) 2> 通过leak检查在程序运行过程中有无内存泄露 3> ...

随机推荐

  1. 乐视4.14硬件免费日de用户体验

    此贴用于记录2016年4月14日乐视硬件免费日购买X65超级电视的用户体验.后续将动态更新 我是乐视电视的第一批用户,从乐视上市第一批超级电视,我先后帮助家人.同事.朋友买了6台乐视超级电视,也算是乐 ...

  2. 2.HBase In Action 第一章-HBase简介(1.1数据管理系统:快速学习)

    Relational database systems have been around for a few decades and have been hugely successful in so ...

  3. HDU5629:Clarke and tree(DP,Prufer)

    Description Input Output Sample Input Sample Output Solution 题意:给你$n$个点,还有每个点的度数,问你任选$i(1\leq i \leq ...

  4. Spark2.3文档翻阅的一点简略笔记(WaterMarking)

    写本文原因是之前已经将官网文档阅读过几遍,但是后来工作接触spark机会较少所以没有跟进新特性,利用周末一点闲暇时间粗略阅读一篇,将自己之前遇见过的问题解决过的问题印象不深刻的问题做一下记录. 1关于 ...

  5. PAT B1006 换个格式输出整数 (15 分)

    让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<),换个格式来输出任一个不超过 3 位的正整数.例如 234 应该被输出为 BBSSS123 ...

  6. ORA-02291: 违反完整约束条件 - 未找到父项关键字

    由于大意,在设置数据库表时将外键字段的类型与外键表的主键字段类型不一致,造成此错误. 我的情况是: 1.将一个为number(10)的外键设置成了number(19) 2.将外键字段对应的主键表设置成 ...

  7. jqgrid 谈谈给表格设置列头事件、行事件、内容事件

    往往我们需要给显示的jqgrid表格赋予事件功能,比如:列头事件.行事件.内容事件.需要的效果可能如下: 如你所见,以上的超链接和按钮均是绑定的事件.那分别如何实现这些事件的绑定呢? 一.行事件 行事 ...

  8. JS设置cookie、读取cookie、删除cookie(转载)

    JavaScript是运行在客户端的脚本,因此一般是不能够设置Session的,因为Session是运行在服务器端的.而cookie是运行在客户端的,所以可以用JS来设置cookie.假设有这样一种情 ...

  9. Docker中查看Mysql数据库中的各环境参数

    通过官方的文档可以看到运行MySQL容器的命令是: docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=mypwd -d mysql:tag 如:d ...

  10. 2017-2018-2 20155203《网络对抗技术》Exp5 MSF基础应用

    1.实践过程记录 1.1一个主动攻击实践,如ms08_067; msf > search ms08_067_netapi //查看可以用的工具 [!] Module database cache ...