OC中的Block

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 内存布局&内存管理方案

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容