欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS进阶教程-Block内部修改变量

程序员文章站 2024-01-22 09:04:34
1 为什么加了__block就能在内部修改变量 __block前缀,把a变量的地址传递过去了 。 我们看下实际发生了什么 2 block存放在哪里 block存放在程序代码区,内存管理则分三种[...

1 为什么加了__block就能在内部修改变量

__block前缀,把a变量的地址传递过去了 。

我们看下实际发生了什么

iOS进阶教程-Block内部修改变量

iOS进阶教程-Block内部修改变量

2 block存放在哪里

block存放在程序代码区,内存管理则分三种[
根据isa指针,block一共有3种类型的block
_nsconcreteglobalblock 全局静态 如果你block没有调用外部变量 : 比如你仅仅在block里面写一个打印语句
_nsconcretestackblock 保存在栈中,出函数作用域就销毁 : [声明时没有用copy修饰又引用外部变量]
_nsconcretemallocblock 保存在堆中,retaincount == 0销毁:[copy修饰] ]

程序分为 栈区、堆区、全局区、文字常量区、程序代码区

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由os回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。 - 程序结束后由释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

3 block为什么用copy修饰

我们将代码声明位copy,是因为block一开始是在栈上,我们要copy到堆上.

4 block不是线程安全

我们在调用block的时候,有没有另一个线程去造成block,如果你确定不会发生这种情况的话,属性选择nonatomic
不确定选择 atomic ,原子性不能保证线程安全,atomic只能保证本身原子性,不能保证线程安全
正确的书写方式:
arc

myblock block =self.block
if(block){
block(123)
}

mrc

myblock block =[self.block retatin];
if(block ){
block(123);
}
[block release]

而不是

if(self.block){
self.block(123);
//走到这里,可能已经被置空crash,因为不知道什么时候会被操作.
}

5 循环引用

循环应用是相互持有造成的
对一个block属性,在bloc里面引用别的属性,造成block持有self

[self.teacher requestdata:^(nsdata *data) {
    self.name = @"case";
}];

self -> teacher -> block -> self 造成循环引用

解决方法1 __weak typeof(self) weakself = self;

__weak typeof(self) weakself = self;
    [self.teacher requestdata:^(nsdata *data) {
        typeof(weakself) strongself = weakself;
       strongself.name = @"case";
    }];
  self -> teacher -> block -> weakself 弱引用不会造成循环

一般会在block回调里再强引用一下weakself(typeof(weakself) strongself = weakself;),因为__weak修饰的都是存在栈内,可能随时会被系统释放,造成后面调用weakself时weakself可能已经是nil了,后面用weakself调用任何代码都是无效的。strongself可以保证线程内都被持有不释放.线程外[block外]用完可以置为nil

//情况一

- (void)case1 {
    nslog(@"case 1 click");
    dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(0.3 * nsec_per_sec)), dispatch_get_main_queue(), ^{
        self.name = @"case 1";
    });
}

//情况二

- (void)case2 {
    nslog(@"case 2 click");
    __weak typeof(self) weakself = self;
    [self.teacher requestdata:^(nsdata *data) {
        typeof(weakself) strongself = weakself;
       strongself.name = @"case 2";
    }];
}

//情况三

- (void)case3 {
    nslog(@"case 3 click");
    [self.teacher requestdata:^(nsdata *data) {
        self.name = @"case 3";
    }];
}

//情况四

- (void)case4 {
    nslog(@"case 4 click");
    [self.teacher requestdata:^(nsdata *data) {
        self.name = @"case 4";
        self.teacher = nil;
    }];
}

//情况五

- (void)case5 {
    nslog(@"case 5 click");
    teacher *t = [[teacher alloc] init];
    [t requestdata:^(nsdata *data) {
        self.name = @"case 5";
    }];
}

//情况六

- (void)case6 {
    nslog(@"case 6 click");
    [self.teacher callcase6blackevent];
    self.teacher.case6block = ^(nsdata *data) {
        self.name = @"case 6";
        //下面两句代码任选其一
        self.teacher = nil;
//        self.teacher.case6block = nil;
    };
}

情况一:执行了dealloc,不泄露,此情况虽然是block,但未形成保留环block -> self
情况二:执行了dealloc,不泄露,此情况就是内存泄漏后的一般处理了 self ->teacher ->block ->strongself,后面那个strongself和原来的self并没有直接关系,因为strongself是通过weakself得来的,而weakself又没有强引用原来的self
情况三:未执行dealloc,内存泄漏,此情况就是最典型的循环引用了,形成保留环无法释放,self ->teacher ->block ->self
情况四:执行了dealloc,不泄露,虽然也是保留环,但通过最后一句,使self不再强引用teacher,打破了保留环
情况五:执行了dealloc,不泄露,未形成保留环 t ->block ->self
情况六:执行了dealloc,不泄露,最后两句代码任选其一即可防止内存泄漏,self.teacher 或者 case6block 置为空都可以打破 retain cycle

ps: 虽然情况四、情况六的写法都可以防止内存泄漏,不过为了统一,个人建议最好还是按照普通写法即情况二的写法
参考demo [https://github.com/yuedong56/blockretaincycledemo]

5.2 循环引用 -为什么一下情况不需要考虑循环引用

**gcd
self 没有强引用gcd** block 会在结束时释放引用对象
uiview 的animation
self 没有强引用 uiview 的animation
以上都是需要没有在当前控制器调用,自然不会存在强引用.
anf
anf调用不是在当前控制器。