美文网首页
OC中的Block

OC中的Block

作者: 晓先生ol | 来源:发表于2020-03-24 18:04 被阅读0次

    OC中的Block是什么

    带自动变量值的匿名函数 --《Objective-c高级编程iOS与OSX多线程和内存管理》

         int main() {
             void(^blk)(void) = ^{printf("Block\n")};
             blk();
             return 0;
         }
         通过clang(LLVM编译器) “clang -rewrite-objc 源代码文件名” 将上述代码变换为以下形式:
    
         static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_gc_5fkhcz0n6px48vzc744hmp6c0000gn_T_main_eef954_mi_0);
         }
         
         struct __main_block_impl_0 {//block结构体,block的本质就是一指针结构体。
            struct __block_impl impl;//内容部分
            struct __main_block_desc_0* Desc;//附加描述信息部分
         };
         
         struct __block_impl {
            void *isa;//用于实现对象相关功能,指向这个block的类型。_NSConcreteGlobalBlock、_NSConcreteStackBlock、_NSConcreteMallocBlock
            int flags;//标志量,用于按 bit 位表示一些 block 的附加信息,block copy 的时候会在内部操作中进行使用
            int reserved;//保留变量
            void *funcPtr;//函数指针,指向block具体实现的函数地址
            ...Imported variables...;//捕获过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中
         
            /*(__block是怎么修改外部变量值的?)
            如果是值传递,就会复制一份该变量值放到Imported variables这里;
            如果是引用传递,Imported variables这里会增加一个名为 __Block_byref_i_0 的结构体,它内部有个__forwarding指针指向自己(达到修改外部变量的作用)。__Block_byref_i_0 结构体中带有 isa,说明它也是一个对象。所以需要负责 __Block_byref_i_0 结构体相关的内存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。*/
         }
         
         struct __Block_byref_i_0 {
            void *__isa;
            __Block_byref_i_0 *__forwarding;
            int __flags;
            int __size;
            int i;
         };
         
        
         //用于描述当前这个 block 的附加信息的,包括结构体的大小,需要 capture 和 dispose 的变量列表等。结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __main_block_impl_0 这个结构体中,使其体积变大。
         staticstruct __main_block_desc_0 {
            size_t reserved;//保留变量
            size_t Block_size;//大小
            void (*copy)(void *dst, void *src);//copy函数指针
            void (*dispose)(void *);//dispose函数指针
         }
    

    Working with Blocks 原文地址

    Blocks are a language-level feature added to C, Objective-C and C++, which allow you to create distinct(唯一的、独特的) segments(片段) of code that can be passed around(传递) to methods of functions as if they were values. They also have the ability to capture values from the enclosing scope(作用域), making them similar to closure or lambdas in the other programming languages.

    Block是一种加在语言(C、Objective-c和C++)层面的功能,允许在函数方法中创建一个代码片段当成参数来传递。Block也能在作用域捕获值,类似其他语言的closure(Swift)和lambda(C#)。

    - (void)testMethod {
        int anInteger = 42;
        void (^testBlock)(void) = ^{
            NSLog(@"Integer is: %i", anInteger);//Integer is: 42
        };
        testBlock();
    }
    //`anInteger` is declared outside of the block, but the value is captured when the block is defined. The value captured by the block is unaffected(不受影响的). It also means that the block cannot change the value of the original variable, or even the captured value (it’s captured as a const variable). anInteger的值在block被定义时被捕获,且以常量的形式。
    
    __block int anInteger = 42;
    __block Book *aBook = nil;
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is: %i", anInteger);
        anInteger = 100;
        aBook = [Book new];
    };
    anInteger = 84;
    testBlock();
    //use the `__block` storage type modifier(存储类型修饰符) on the original variable declaration to share storage. The block can modify the original value. 使用存储类型修饰符__block来共享存储,block就可以修改原值。
    //A Block Should Always Be the Last Argument to a Method. block应该总是放在方法的最后一个参数位置上。
    
    typedef void (^XYZSimpleBlock)(void);
    
    
    XYZSimpleBlock anotherBlock = ^{
            ...
    };
    //Use Type Definitions to Simplify Block Syntax. 用类型定义来简化block语法。
    
    
    void (^(^complexBlock)(void (^)(void)))(void) = ^ (void (^aBlock)(void)) {
        ...
        return ^{
            ...
        };
    };
    
    
    XYZSimpleBlock (^betterBlock)(XYZSimpleBlock) = ^ (XYZSimpleBlock aBlock) {
        ...
        return ^{
            ...
        };
    };
    //Custom type definitions are particularly(特别、非常) useful when dealing with blocks that return blocks or take other blocks as arguments. 在处理传递block时自定义类型定义就会变得很有用。
    
    @interface XYZObject : NSObject
    @property (copy) void (^blockProperty)(void);
    @end
    //a block needs to be copied to keep track of its captured state(data) outside of the original scope. block需要被拷贝来保持对捕获信息的跟踪。
    

    在ARC下由此(copy修饰)而导致的行为请参考 Blocks Programming Topics

    @interface XYZBlockKeeper : NSObject
    @property (copy) void (^block)(void);
    @end
      
    
    @implementation XYZBlockKeeper
    - (void)configureBlock {
        XYZBlockKeeper * __weak weakSelf = self;
        self.block = ^{
            [weakSelf doSomething];    // capture the weak reference to avoid the reference cycle
        };
    }
    ...
    @end
    //Avoid Strong Reference Cycles when Capturing self. 捕获self时避免强引用循环。
    
    [array enumerateObjectsWithOptions:NSEnumerationConcurrent
                            usingBlock:^ (id obj, NSUInteger idx, BOOL *stop) {
            ...
    }];
    //This flag,NSEnumerationConcurrent,indicates that the enumeration block invocations may be distributed across(分布在) multiple threads, offering a potential(潜在的) performance increase(提升) if the block code is particularly(特别是) processor intensive(密集). Note that the enumeration order is undefined when using this option. 线程安全,但执行顺序不定
    

    Blocks Programming Topics 原文地址

    Block objects are a C-level syntactic and runtime feature.

    The blocks runtime is open source and can be found in LLVM’s compiler-rt subproject repository. Blocks have also been presented to(提交) the C standards working group(C标准工作组) as N1370: Apple’s Extensions to C. As Objective-C and C++ are both derived(衍生) from C, blocks are designed to work with all three languages (as well as Objective-C++).

    block对象是C级别的语法和运行时功能。

    block运行时是开源的,可以在LLVM’s compiler-rt subproject repository中查看。block也已经被提交到C标准工作组作为苹果对C的拓展N1370: Apple’s Extensions to C

    Block Functionality(功能)

    A block is an anonymous(匿名的) inline(内联函数) collection of code that:

    • Has a typed argument list just like a function 有类型化参数列表就像函数一样
    • Has an inferred or declared return type 有返回类型
    • Can capture state from the lexical scope(词法作用域) within which it is defined 可以捕获其作用域里的数据
    • Can optionally modify the state of the lexical scope 可以修改捕获的数据
    • Can share the potential(潜在的) for modification(修改) with other blocks defined within the same lexical scope 可以与同作用域的其他block共享这种修改
    • Can continue to share and modify state defined within the lexical scope (the stack frame, 堆栈帧,在堆栈中为当前运行的函数分配的区域) after the lexical scope (the stack frame) has been destroyed 可以继续共享和修改作用域范围内的捕获数据,即便是原数据的堆栈帧已经被销毁

    support types of variable: 支持的变量类型

    • Global variables, including static locals 全局变量,包括静态局部变量
    • Global functions (which aren’t technically variables) 全局函数(不是专门的变量)
    • Local variables and parameters from an enclosing scope 作用域里的局部变量和参数
    • At function level are __block variables 在函数级别__block变量
    • const imports 汇入常量

    The following rules apply to variables used within a block:

    1. Global variables are accessible, including static variables that exist within the enclosing lexical scope. 全局变量可以直接访问(读写),包括作用域内的静态局部变量

    2. Parameters passed to the block are accessible (just like parameters to a function). 传递到block内的参数可以直接访问

    3. Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. 作用域内的栈变量(非静态,局部变量)被捕获为常量

      Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope. 它们的值是在程序中block表达式处获取的,在嵌套block中,从最近的作用域捕获值。

    4. Variables local to the enclosing lexical scope declared with the __block storage modifier are provided by reference and so are mutable. __block存储修饰符声明的变量,为引用提供 所以是可变的。

      Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in The __block Storage Type. 在作用域内任何的变化都会反映出来,包括同作用域定义的其他block。

    5. Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function. block作用域内声明的局部变量,与函数内的局部变量表现一致。

      Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const or by-reference variables in blocks enclosed within the block. block的每次调用都提供变量的一份新拷贝。

    You can specify that an imported variable be mutable—that is, read-write— by applying the __block storage type modifier. __block storage is similar to, but mutually exclusive of, the register, auto, and static storage types for local variables.

    通过使用__block存储类型修饰符可以指定导入的变量为可变的(读写)。__block是排它的,它和register, auto, static 类似。register 变量请求存储于CPU;auto 变量存储在程序的栈中 默认属性;static 变量存储在程序静态区中。

    OC代码

    Block截获变量:

    • 局部变量
      • 基本数据类型 截获值
      • 对象类型 截获值(连同修饰符)
    • 局部静态变量 截获引用,指针形式
    • 全局变量 不截获
    • 全局静态变量 不截获
    extern NSInteger CounterGlobal = 1;
    static NSInteger CounterStatic = 11;
    
    - (void)viewDidLoad {    
        NSInteger localCounter = 42;
        __block char localCharacter;
        NSLog(@"--init--");
        NSLog(@"localCounter=%d; %p; %p", localCounter, localCounter, &localCounter);
        NSLog(@"localCharacter=%c; %p; %p", localCharacter, localCharacter, &localCharacter);
        NSLog(@"CounterGlobal=%d; %p; %p", CounterGlobal, CounterGlobal, &CounterGlobal);
        NSLog(@"CounterStatic=%d; %p; %p", CounterStatic, CounterStatic, &CounterStatic);
        /*
         2020-03-24 17:01:54.990480+0800  --init--
         2020-03-24 17:01:54.990580+0800  localCounter=42; 0x2a; 0x7ffee5e22bc8
         2020-03-24 17:01:54.990641+0800  localCharacter=; 0x0; 0x7ffee5e22bc0
         2020-03-24 17:01:54.990706+0800  CounterGlobal=1; 0x1; 0x109ddcea0
         2020-03-24 17:01:54.990768+0800  CounterStatic=11; 0xb; 0x109ddcea8
         */
        void (^aBlock)(void) = ^(void) {
            NSLog(@"--aBlock--begin");
            NSLog(@"localCounter=%d; %p; %p", localCounter, localCounter, &localCounter);
            NSLog(@"localCharacter=%c; %p; %p", localCharacter, localCharacter, &localCharacter);
            NSLog(@"CounterGlobal=%d; %p; %p", CounterGlobal, CounterGlobal, &CounterGlobal);
            NSLog(@"CounterStatic=%d; %p; %p", CounterStatic, CounterStatic, &CounterStatic);
            localCharacter = 'a';
            ++CounterGlobal;
            ++CounterStatic;
            NSLog(@"--aBlock--modify");
            NSLog(@"localCounter=%d; %p; %p", localCounter, localCounter, &localCounter);
            NSLog(@"localCharacter=%c; %p; %p", localCharacter, localCharacter, &localCharacter);
            NSLog(@"CounterGlobal=%d; %p; %p", CounterGlobal, CounterGlobal, &CounterGlobal);
            NSLog(@"CounterStatic=%d; %p; %p", CounterStatic, CounterStatic, &CounterStatic);
            /*
             2020-03-24 17:01:54.991396+0800  --aBlock--begin
             2020-03-24 17:01:54.991738+0800  localCounter=42; 0x2a; 0x600001c00e68    //值传递 0x2a
             2020-03-24 17:01:55.014342+0800  localCharacter=b; 0x62; 0x60000126c878   //引用传递 0x60000126c878
             2020-03-24 17:01:55.014428+0800  CounterGlobal=2; 0x2; 0x109ddcea0        //引用传递 0x109ddcea0
             2020-03-24 17:01:55.014500+0800  CounterStatic=12; 0xc; 0x109ddcea8       //引用传递 0x109ddcea8
             2020-03-24 17:01:55.014551+0800  --aBlock--modify
             2020-03-24 17:01:55.014609+0800  localCounter=42; 0x2a; 0x600001c00e68
             2020-03-24 17:01:55.014655+0800  localCharacter=a; 0x61; 0x60000126c878
             2020-03-24 17:01:55.014722+0800  CounterGlobal=3; 0x3; 0x109ddcea0
             2020-03-24 17:01:55.014769+0800  CounterStatic=13; 0xd; 0x109ddcea8
             */
        };
        
        ++localCounter;
        localCharacter = 'b';
        ++CounterGlobal;
        ++CounterStatic;
        NSLog(@"--modify--");
        NSLog(@"localCounter=%d; %p; %p", localCounter, localCounter, &localCounter);
        NSLog(@"localCharacter=%c; %p; %p", localCharacter, localCharacter, &localCharacter);
        NSLog(@"CounterGlobal=%d; %p; %p", CounterGlobal, CounterGlobal, &CounterGlobal);
        NSLog(@"CounterStatic=%d; %p; %p", CounterStatic, CounterStatic, &CounterStatic);
        /*
         2020-03-24 17:01:54.990832+0800  --modify--
         2020-03-24 17:01:54.990892+0800  localCounter=43; 0x2b; 0x7ffee5e22bc8    //还是引用 0x7ffee5e22bc8
         2020-03-24 17:01:54.990937+0800  localCharacter=b; 0x62; 0x60000126c878   //内容引用都变了
         2020-03-24 17:01:54.990978+0800  CounterGlobal=2; 0x2; 0x109ddcea0        //还是引用 0x109ddcea0
         2020-03-24 17:01:54.991070+0800  CounterStatic=12; 0xc; 0x109ddcea8       //还是引用 0x109ddcea8
         */
        
        aBlock();
    }
    

    看着打印的0x地址,一下明白了各自内存中所处的位置,比较直观了,下面手绘一张内存布局:


    内存布局.png

    到这里又联想到了苹果空间地址布局随机化技术(ASLR,Address space layout randomization),内核将Mach-O的段位置平移某个随机数,以避免代码/指令攻击。

    参考资料:
    In-Memory Layout of a Program (Process)
    iOS 内存布局&内存管理方案

    相关文章

      网友评论

          本文标题:OC中的Block

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