22、iOS底层分析 - Block (一)

block是在iOS开发中用的比较多的,我对block的理解也不是很透彻,学习一下进行分析分析。

Block

1、block有几种?

一般我们能用到的知道的block有三种:

  1. NSGlobalBlock (全局block )
  2. NSStackBlock (栈block )
  3. NSMallocBlock(堆block)

但是其实block总共有6种,剩下的3种是系统级别的:

通过查看源码  libclosure-73   ->  data.c文件可以知道

void * _NSConcreteStackBlock[32] = { 0 };         栈block
void * _NSConcreteMallocBlock[32] = { 0 };        堆block
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };        全局block
void * _NSConcreteWeakBlockVariable[32] = { 0 };

常用到的三种block验证

1.1、 堆block

访问外部变量的时候  堆block

    int a = 10;
    void (^block)(void)=^{
        NSLog(@"1233 - %d",a);
    };
    block();
    NSLog(@"%@",block);
// <__NSMallocBlock__: 0x600002d6a0d0> 堆block

1.2、全局block

不访问外部变量    全局block

    void (^block)(void)=^{
        NSLog(@"1233");
    };
    block();
    NSLog(@"%@",block);
// <__NSGlobalBlock__: 0x10ade6150> 全局block

1.3、栈block

堆block  copy之前是栈block

    NSLog(@"%@",^{
        NSLog(@"1233 - %d",a);
    });
//<__NSStackBlock__: 0x7ffeeeed41b8>

   全局block  copy之前还是全局block

    NSLog(@"%@",^{
        NSLog(@"1233");
    });
//<__NSGlobalBlock__: 0x10a356190>

注:上面所说的copy 与 copy之前的解释如下。

1、
    void (^block)(void)=^{
        NSLog(@"1233");
    };
    block();
    NSLog(@"%@",block);
    //这个地方打印的 block = ^{ }; 这个 =(等号)就是copy了一份,所以这样打印是打印的copy之后的block
    
2、
    NSLog(@"%@",^{
        NSLog(@"1233");
    });
    //这样直接打印 ^{ }; 这样打印是没有copy 的block。

 

2、block的用法

     1、block作为返回值的链式传参
         masonry
         mark.width.equal(100); 链式编程
         mark.left.top.height.width.mas_equalTo(100);
         不断的进行调用,同时还能传值。这个地方block 做为返回值。
         masonry mas_equalTo 封装了equalTo()
         - (MASConstraint * (^)(id))equalTo  带了一个返回值id
         这样做的目的是可以在 .mas_equalTo(100) 之后继续追加操作,例如:make.top.equalTo(loginView.mas_top).with.offset(0);


     2、block 参数
         函数式 可以设计成 y = f(x) - y=f(f(x)) 传的是一个表达式 封装block比较灵活
         响应式编程 RAC

3、循环引用

#import "LJLBlockViewController.h"

@protocol LJLDelegate <NSObject>
-(void)showName;
@end

typedef void(^LJLBlock)(LJLBlockViewController *);

@interface LJLBlockViewController ()

@property(nonatomic, assign) int age;
@property(nonatomic, copy) LJLBlock ljlBlock;
@property(nonatomic, copy) NSString * name;
@property(nonatomic, strong) NSObject * obj;
@property(nonatomic, weak) id <LJLDelegate>delegate;

@end

这个地方不构成循环引用,但是如果嵌套层次深的话就会有问题。
 详见 RAC weakly

    self.name = @"LJL";
    [UIView animateWithDuration:1 animations:^{
        NSLog(@"%@",self.name);
    }];

1、强弱共舞(中介者)

中介者模式
持有情况说明    stongWeakSelf -> weakSelf -> self -> ljlBlock -> stongWeakSelf
weakSelf加在了弱引用表中,持有了self(weakSelf弱引用,引用计数不处理,只是指针指向)
因为self 引用计数不处理,所以退出的时候会调用析构函数dealloc ,self会置为nil。

一般情况下会直接使用weakSelf即可,但是如下代码中加了一个dispatch_after 的等待,如果进入到该页面直接返回,这个时候如果到该页面后2秒内直接返回的话,先调用了dealloc 析构,这个时候self先释放了,那么weakSelf也就为nil了,那么weakSelf.name就成null了,提前释放了。为了处理这个问题需要再强引用一下weakSlef。由于有stongWeakSelf持有weakSelf,所以在self 释放的时候,weakSelf并不会立即释放,而是等stongWeakSelf 释放的时候才被置为nil。这样就能正常方访问name了。
//     strong-weak-dance 强弱共舞

    self.name = @"LJL";
    __weak typeof(self) weakSelf = self;
    self.ljlBlock = ^(LJLBlockViewController * vc) {
//     2秒后调度
        __strong typeof(weakSelf) stongWeakSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"stongWeakSelf.name = %@",stongWeakSelf.name);
        });
    };
    self.ljlBlock(nil);

2、中介者

中介者模式,用一个中介者来代替self,用完之后手动置为nil。
持有关系:    VC -> self -> block -> VC

    self.name = @"LJL";
    __block LJLBlockViewController * vc = self;
    self.ljlBlock = ^(LJLBlockViewController * myVC) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.ljlBlock(nil);

3、传参
通过传参的形式,这样就不构成持有
传参过来的值是在内部进行了copy,在block释放的时候就会释放。

    self.name = @"LJL";
    self.ljlBlock = ^(LJLBlockViewController * myVC) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",myVC.name);
        });
    };
    self.ljlBlock(self);


扩展:

1、NSObject

这个地方的obj不能用weak修饰,obj创建出来之后因为没有人使用所以引用计数不变就会直接释放掉了,没有持有关系,这个时候打印出来的结果就是null。
那为什么xib 拖过来的变量是用weak 修饰的呢?因为xib 已经对这个变量持有,所以拖过来之后的要使用弱引用。

当然了,在底层分析的时候会发现(后面说到埋点的时候会遇到)NSObject有用weak修饰的,因为会造成循环引用互相持有,所以具体用什么修饰需要根据实际情况来确定。

self.obj = [[NSObject alloc] init];
NSLog(@"%@",self.obj);

2、delegate 需要使用weak
weak 和 assign 区别:

weak在释放的时候会自动置为nil assign 不会置为nil,所以assign一般用来修饰基本数据类型。因为基本数据类型一般分配在栈上,栈的内存由系统自动处理。如果用来修饰其他会出现释放后不置为nil,存在野指针。
self -> delegate -> self  如果不使用weak 就会造成循环引用,所以必须self 不持有delegate
self --> delegate -> self

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104707801