美文网首页
[ios] 浅谈Block

[ios] 浅谈Block

作者: extanstory | 来源:发表于2017-02-15 15:37 被阅读0次

    <h6> 1. iOS Block用来封装一段代码块或者传递参数相对于代理使用起来方便,它本质上是一个匿名函数。</h6>

    完整的block的申明: 返回值类型 (^blockName)(参数列表) = ^返回值类型 (参数列表){
    // 表达式;what you want to do;
    };
        void(^blockTest)(void) = ^void(void){
         
            printf("hello world");
           
        };
    

    <h6>2.使用block传值</h6>

    顺传:定义属性
    逆向传值:使用代理、通知、block、单例、全局变量、等传值手段传值
    ---------------------------------
    假设现在有a,b两个页面,a是b的上一级页面,b此刻处于栈顶,a,压在它下面。
    想要把a中的一个name值传到b,顺传,定义一个属性接收。
    想要把b中的一个name值传到a,逆传,此处只说block。
    ---------------------------------
    逆传方法有两种:
    1.b中申明可以带参数的block,在你需要的时机调用,传递参数。a中合适的时机处理传递回来的参数。
    ----------------------------------------------------
    b.h
    @interface BViewController : UIViewController
    @property (nonatomic ,copy) void (^blkTest)(UIColor*);
    @end
    ----------------------------------------------------
    b.m
    #import "BViewController.h"
    
    @interface BViewController ()
    
    @end
    
    @implementation BViewController
    
    - (void)viewDidLoad {
       [super viewDidLoad];
       self.title = @"bbbbb";
       self.view.backgroundColor = [UIColor yellowColor];
     
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
       
       if (self.blkTest) {
           self.blkTest([UIColor yellowColor]);
       }
    }
    
    @end
    ----------------------------------------------------
    a.m
    #import "AViewController.h"
    #import "BViewController.h"
    @interface AViewController ()
    
    @end
    
    @implementation AViewController
    
    - (void)viewDidLoad {
       [super viewDidLoad];
       self.title = @"aaaaa";
       self.view.backgroundColor = [UIColor redColor];
     
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
       BViewController *vc = [[BViewController alloc] init];
       vc.blkTest = ^(UIColor *color){
           self.view.backgroundColor = color;
       };
       [self.navigationController pushViewController: vc animated:true];
    }
    @end
    ----------------------------------------------------
    2个界面都变成黄色的了。
    
    ----------------------------------------------------
    ---------------------分割线----------------------
    ----------------------------------------------------
    ----------------------------------------------------
    
    2.重写b初始化方法传入参数为一个block,在b的初始化方法中实现该block。
    在a中创建b的时候就可以传递参数了。(这么做可以不暴漏属性给外界)
    ----------------------------------------------------
    b.m
    #import "BViewController.h"
    @interface BViewController ()
    @property (nonatomic ,copy) void (^blkTest)(UIColor*);
    @end
    
    @implementation BViewController
    - (instancetype)initWithBlock:(void (^)(UIColor *))blkTest{
       self.blkTest = blkTest;
       return [self init];
    }
    - (void)viewDidLoad {
       [super viewDidLoad];
       self.title = @"bbbbb";
       self.view.backgroundColor = [UIColor yellowColor];
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
       if (self.blkTest) {
           self.blkTest([UIColor yellowColor]);
       }
    }
    
    @end
    
    ----------------------------------------------------
    a.m
    #import "AViewController.h"
    #import "BViewController.h"
    @interface AViewController ()
    @end
    
    @implementation AViewController
    - (void)viewDidLoad {
       [super viewDidLoad];
       self.title = @"aaaaa";
       self.view.backgroundColor = [UIColor redColor];
    }
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
       BViewController *vc = [[BViewController alloc] initWithBlock:^(UIColor * color) {
           self.view.backgroundColor = color;
       }];
       [self.navigationController pushViewController: vc animated:true];
    }
    @end
    ----------------------------------------------------
    

    <h6>3.block的类型</h6>3种:

    _NSConcreteStackBlock 栈
    _NSConcreteGlobalBlock 程序的数据区域(.data 区)
    _NSConcreteMallocBlock 堆

    <h5>__NSConcreteGlobalBlock 程序的数据区域(.data 区)

    1.全局block,在数据区。
    #include <stdio.h>
    
    void(^blockTest)(void) = ^void(void){
        printf("hello world");
    };
    
    int main(int argc, char * argv[]){
      
        blockTest();
        return 0;
        
    }
    
    ---------------------clang后的关键代码-------------------------------
    int a = 0;
    struct __block_impl {
        void *isa;//isa指针 所有对象都有,说明block也是一个对象
        int Flags;//字面意思,标记。记录block的一些附加信息
        int Reserved;//保留变量
        void *FuncPtr;//函数指针,指向具体的block实现的函数调用地址
    };
    struct __blockTest_block_impl_0 {//blockTest通过clang看到的c
    代码就是__blockTest_block_impl_0结构体
      struct __block_impl impl;//定义一个结构体,纪录block的信息。即是上面的定义的结构体:
      struct __blockTest_block_desc_0* Desc;//纪录block大小,保留变量等信息
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {//block的具体实现
        impl.isa = &_NSConcreteGlobalBlock;//isa指向全局block,data区
        impl.Flags = flags;//给其他属性赋值
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {//静态的函数传入实现block-__blockTest_block_impl_0
    
    
        printf("hello world");//函数体
    }
    
    static struct __blockTest_block_desc_0 {//纪录block大小,保留变量等信息
      size_t reserved;//保留变量
      size_t Block_size;//大小
    } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//定义一个结构体变量,给上面属性赋值
    static __blockTest_block_impl_0  __global_blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA);// 给__blockTest_block_impl_0赋值。定义__blockTest_block_impl_0类型的变量__global_blockTest_block_impl_0,传入参数:函数指针 __blockTest_block_desc_0的地址
    void(*blockTest)(void) = ((void (*)())&__global_blockTest_block_impl_0);//把上一步定义的__global_blockTest_block_impl_0复制给blockTest
    

    当block定义在局部代码内,但没有访问外部变量时,也在数据区。(其他c代码基本上都差不太多,篇幅过大,(每一行c代码都有注释)这里不在复制。)
    在block内修改变量a的值会报错。代码自行编译。
    看代码可知道,在block中引用的变量a实际是在申明block时,被复制到main_block_impl_0结构体中的那个变量a。因为这样,我们就能理解,在block内部修改变量a的内容,不会影响外部的实际变量a。
    想要修改要加上__block,这时看代码会发现
    <下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>

    struct __Block_byref_i_0 { 
        void *__isa; 
        __Block_byref_i_0 *__forwarding; 
        int __flags; 
        int __size; 
        int i; 
    }; 
     
    struct __main_block_impl_0 { 
        struct __block_impl impl; 
        struct __main_block_desc_0* Desc; 
        __Block_byref_i_0 *i; // by ref 
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref 
     
        printf("%d\n", (i->__forwarding->i)); 
        (i->__forwarding->i) = 1023; 
    } 
     
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} 
     
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*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 = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; 
     
    int main() 
    { 
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024}; 
        void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344); 
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); 
        return 0; 
    } 
    

    <h6>
    1.增加了一个结构体,__Block_byref_i_0 ,用来保存我们要capture并且修改的变量i。
    2.main_block_impl_0 中引用的是Block_byref_i_0的结构体指针,这样就可以达到修改外部变量的作用。
    3.__Block_byref_i_0结构体中带有isa,说明它也是一个对象。
    4.我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数

    <h6>

    <下面的内容摘录自《Objective-C 高级编程 iOS 与 OSX 多线程与内存管理》。http://vdisk.weibo.com/s/u65p8oUfGH52e>
    除全局block外,定义block的时候,是分配在栈上的,block只在定义它的范围有效。
    栈的生命周期结束,block销毁,此时想要保存数据或者__block变量用结构体成员__forwarding。就要用到堆block。oc中的block是从栈拷贝到堆上的。

    什么时候栈上的 Block 会复制到堆?
    调用 Block 的 copy 实例方法时
    Block 作为函数返回值或者参数时
    将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
    在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递 Block 时

    Snip20170215_11.png
    <下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>
    NSConcreteMallocBlock 类型的block的实现
    NSConcreteMallocBlock类型的block通常不会在源码中直接出现,因为默认它是当一个block被copy的时候,才会将这个block复制到堆中。以下是一个block被copy时的示例代码(来自这里),可以看到,在第8步,目标的block类型被修改为_NSConcreteMallocBlock。
    static void *_Block_copy_internal(const void *arg, const int flags) { 
        struct Block_layout *aBlock; 
        const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; 
     
        // 1 
        if (!arg) return NULL; 
     
        // 2 
        aBlock = (struct Block_layout *)arg; 
     
        // 3 
        if (aBlock->flags & BLOCK_NEEDS_FREE) { 
            // latches on high 
            latching_incr_int(&aBlock->flags); 
            return aBlock; 
        } 
     
        // 4 
        else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
            return aBlock; 
        } 
     
        // 5 
        struct Block_layout *result = malloc(aBlock->descriptor->size); 
        if (!result) return (void *)0; 
     
        // 6 
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first 
     
        // 7 
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed 
        result->flags |= BLOCK_NEEDS_FREE | 1; 
     
        // 8 
        result->isa = _NSConcreteMallocBlock; 
     
        // 9 
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) { 
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup 
        } 
     
        return result; 
    } 
    

    <h6>
    在ARC开启的情况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。原本的NSConcreteStackBlock的block会被NSConcreteMallocBlock类型的block替代。证明方式是以下代码在XCode中,会输出 <NSMallocBlock: 0x100109960>。在苹果的官方文档中也提到,当把栈中的block返回时,不需要调用copy方法了。
    </h6>

    #import <Foundation/Foundation.h> 
     
    int main(int argc, const char * argv[]) 
    { 
        @autoreleasepool { 
            int i = 1024; 
            void (^block1)(void) = ^{ 
                printf("%d\n", i); 
            }; 
            block1(); 
            NSLog(@"%@", block1); 
        } 
        return 0; 
    } 
    

    我个人认为这么做的原因是,由于ARC已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。

    相关文章

      网友评论

          本文标题:[ios] 浅谈Block

          本文链接:https://www.haomeiwen.com/subject/vokqwttx.html