OC对象 - Block修改变量

OC对象 - Block修改变量

1. 尝试修改

如下代码:

typedef void (^ZSXBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        block();
    }
    return 0;
}

尝试修改age的值,但是编译报错了

1.1 底层实现

将上述代码转成C++

此时会报错,先把age = 20注释掉

直接修改肯定是改不了的

2. 可行修改方法

2.1 static

typedef void (^ZSXBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        block();
    }
    return 0;
}


age增加static修饰符后,可以修改

2.1.1 底层实现


通过指针的方式访问,自然是可以修改了

2.2 全局变量

全局变量,全局都可以直接访问,不经过结构体指针,就可以直接修改的

typedef void (^ZSXBlock)(void);

int no_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            no_ = 30;
            NSLog(@"age is %d", age);
        };
        
        block();
    }
    return 0;
}

2.3 __block

更多时候,我们希望可以修改局部变量,如果还使用static全局变量的方式,那变量就一直在内存中了,我们肯定不希望这样。这时候就可以使用__block修饰符

typedef void (^ZSXBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        block();
    }
    return 0;
}


使用__block修饰符可以正常修改age,并且age还能保持局部变量

2.3.1 底层实现

  • __main_block_impl_0中,原本是int age,现在变成一个结构体__Block_byref_age_0

  • 使用__Block_byref_age_0结构体来包装age
  • 结构体初始化__Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};,传入的参数相当于这样


__forwarding传的是&age,相当于把自己传进去,指针指向自己(__forwarding的用处后面会再讲到)

3. 总结

  • __block可以用于解决block内部无法修改auto变量值的问题
  • __block不能修饰全局变量、静态变量(static)
  • 编译器会将__block变量包装成一个对象


这个对象对应的是结构体struct __Block_byref_age_0

struct __Block_byref_age_0结构体中有个__forwarding指向自身的指针

4. 拓展

4.1 __block修饰对象

我们再声明一个对象类型的变量

观察底层实现:

  • 使用struct __Block_byref_obj_1结构体来包装obj变量
  • 因为obj是对象类型,牵扯内存管理,所以里面多了copydispose方法

4.2 block里面操作NSMutableArray

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSMutableArray *mArr = [NSMutableArray array];
        
        ZSXBlock block = ^ {
            [mArr addObject:@"1"];
            [mArr addObject:@"2"];
        };
        
        block();
        NSLog(@"%@", mArr);
    }
    return 0;
}

NSMutableArray实例对象mArr并没有使用__block等方式修饰,而是在 block 里面直接调用addObject方法,并且添加成功了

这是因为,block里面只是使用mArr,并不是修*mArr的值

这种情况下,就没必要使用__block来修饰mArr,反而使用了使用__block修饰符,会让底层结构复杂化

4.3 探索内存

前面文章讲到,使用__block修饰变量后,编译器会将__block变量包装成一个对象,如下

typedef void (^ZSXBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        block();
    }
    return 0;
}

将使用struct __Block_byref_age_0包装age变量

那么,怎么确定age = 20;修改的是__Block_byref_age_0结构体中的int age

4.3.1 打印比对内存地址

我们将打印age的地址值NSLog(@"%p", &age);,与block结构体里面的age做对比

为了方便调试,我们拷贝底层实现的代码,模拟把block转成结构体形式

typedef void (^ZSXBlock)(void);

struct __Block_byref_age_0 {
  void *__isa;
 struct __Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

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*);
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  struct __Block_byref_age_0 *age; // by ref
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        ZSXBlock block = ^ {
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        struct __main_block_impl_0 *blockIml = (__bridge struct __main_block_impl_0 *)block;
        
        NSLog(@"%p", &age);
    }
    return 0;
}

此时如果直接打印age发现没有直接打印出内存地址

我们先打印结构体实现的包装类age,也就是:


接着往下执行,打印age的地址值

会发现地址值不一样

  • 0x00006000020810c0((__Block_byref_age_0 *) age)
  • 0x6000020810d8 (&age)

仔细观察并计算struct __Block_byref_age_0的成员

__Block_byref_age_0里的age地址值就是0x00006000020810d8

因此,age = 20;修改的是__Block_byref_age_0结构体中的int age

苹果这么做,还是为了屏蔽内部实现,开发者age = 20;的操作,看起来就是在访问前面定义的int a = 10;

4.3.2 lldb方式打印

@oubijiexi

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值