iOS技术图谱之再谈Block

Block 最早出现是在 Mac OS X 10.6 和 iOS 4 中,作为对 C 语言的扩展,用来实现匿名函数的特性,在如今 Objective-C 开发的项目中 Block 随处可见。Block 为 Objective-C 提供了强大的函数式编程能力,为日常开发带来了极大的便利。那么对于 Block,你又了解多少?

初识

A Short Practical Guide to Blocks 文章中, Apple 列举了几种在系统框架 API 中 Block 的使用场景:

  • 任务完成回调
  • 通知回调
  • 错误回调
  • 枚举
  • 视图动画以及变换
  • 排序
    在Clang 9的官方文档中,Block的实现规范本文的开头是这样描述Block的:
struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

Block是个包含isa指针的结构体,熟悉Objective-C的同学都知道,Objective-C中的对象也是一个包含isa指针的结构体,所有Block也可以当做是一个对象。通过注释发现Block可以被初始化NSConcreteStackBlock或NSConcreteGlobalBlock。(以下所述内容默认环境为ARC)

类型

区块有以下几种:

// the raw data space for runtime classes for blocks
// class+meta used for stack, malloc, and collectable based blocks
BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// declared in Block.h
// BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
// BLOCK_EXPORT void * _NSConcreteStackBlock[32];

其中NSConcreteAutoBlock,NSConcreteFinalizingBlock,NSConcreteWeakBlockVariable只在GC环境下使用。

NSConcreteGlobalBlock

未捕获任何变量或仅捕获的变量为以下类型的Block是NSConcreteGlobalBlock。

静态变量
整体变量
静态变量

NSLog(@"%@",^(void) {});

NSConcreteStackBlock
只要捕获了以上三种类型以外的变量的Block是NSConcreteStackBlock。

int c;
NSLog(@"%@",^(void) { c; });

NSConcreteMallocBlock

系统不提供直接创建NSConcreteMallocBlock的方式,但是可以对NSConcreteStackBlock进行复制操作来生成NSConcreteMallocBlock。

以下情况,块会进行复制操作:

  • 手动执行copy方法
  • 将Block赋值给__strong修饰符修饰(系统替换)的Block或id对象
  • 作为方法的返回值
  • 系统API中包含usingBlock的方法
int c;
id block = ^(void) {
    c;
};
NSLog(@"%@",block);

生命周期

先看一张内存段分布图:

也就是说 NSConcreteStackBlock 是由编译器自动管理,超过作用域之外就会自动释放了。而 NSConcreteMallocBlock 是由程序员自己管理,如果没有被强引用也会被销毁。NSConcreteGlobalBlock 由于存在于全局区,所以会一直伴随着应用程序。

变量

任意类型的变量都可以在 Block 中被访问,但是能够被修改的变量只有以下三种:

  • 静态变量
  • 全局变量
  • 静态全局变量

全局变量和静态全局变量由于存在于全局区作用域广,所以在 Block 内部能够直接修改。那么对于静态变量是怎么实现修改的?

可以使用 Clang 提供的命令来进一步的分析:

clang -rewrite-objc Hello.m

使用 clang -rewrite-objc 命令对代码的转换并不与实际编译过程相同,但是转换后的代码可读性更高,可以更好的帮助理解 Block 的机制。

该命令可以将 Objective-C 的代码转成 C++ 代码。

int a;
static int b;
- (void)main
{
    static int c;
    int d;
    ^(void) {
        a++;
        b++;
        c++;
        d;
    };
}

以上代码转换后变为:

int a;
static int b;

struct __Hello__main_block_impl_0 {
  struct __block_impl impl;
  struct __Hello__main_block_desc_0* Desc;
  int *c;
  int d;
  __Hello__main_block_impl_0(void *fp, struct __Hello__main_block_desc_0 *desc, int *_c, int _d, int flags=0) : c(_c), d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Hello__main_block_func_0(struct __Hello__main_block_impl_0 *__cself) {
  int *c = __cself->c; // bound by copy
  int d = __cself->d; // bound by copy

        a++;
        b++;
        (*c)++;
        d;
    }

static struct __Hello__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __Hello__main_block_desc_0_DATA = { 0, sizeof(struct __Hello__main_block_impl_0)};

static void _I_Hello_main(Hello * self, SEL _cmd) {
    static int c;
    int d;
    ((void (*)())&__Hello__main_block_impl_0((void *)__Hello__main_block_func_0, &__Hello__main_block_desc_0_DATA, &c, d));
}

原 Block 转换为 __Hello__main_block_impl_0 结构体,内部定义了指针 c 以及变量 d。原 main 方法转换为 _I_Hello_main 函数,函数中构造了 __Hello__main_block_impl_0,传入了 c 变量的地址,以及 d 变量的值。这就是为什么局部变量 d 不能被修改,而静态变量 c 可以被修改的原因。c 是指针传递,而 d 是值传递。之所以使用指针传递,是因为作用域的限制,通过指针进行作用域扩展,在 C 语言中是很常见且简单的做法。

那么为什么 d 不使用指针传递,这是因为局部变量是存储在栈上,其生命周期是不稳定,Block 中通过指针访问到的局部变量可能已经销毁了。而静态变量是存储在静态数据存储区的,与应用程序生命周期一致,是可以保证正确访问的变量。

__Hello__main_block_func_0 是 Block 的执行函数与 void (*invoke)(void *, ...) 对应,该函数的入参为 Block 实例。由于 a、b 是全局变量,所以在函数内部直接进行 + 操作,而对 c 进行 + 操作是通过指针来执行的,对于 d 不能进行 + 操作,所以在开发阶段编译器直接进行了报错。

Property 和 Ivar

如果在 Block 内部对 Property 或 Ivar 进行修改,发现是可以修改成功的,实际上 Property 内部操作的还是 Ivar,所以需要了解下为何 Ivar 可以在 Block 内部修改。

{
    int _b;
}

- (void)main
{
    ^(void) {
        _b++;
    };
}

以上代码转换后变为:

extern "C" unsigned long OBJC_IVAR_$_Hello$_b;

struct __Hello__main_block_impl_0 {
  struct __block_impl impl;
  struct __Hello__main_block_desc_0* Desc;
  Hello *self;
  __Hello__main_block_impl_0(void *fp, struct __Hello__main_block_desc_0 *desc, Hello *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Hello__main_block_func_0(struct __Hello__main_block_impl_0 *__cself) {
  Hello *self = __cself->self; // bound by copy

        (*(int *)((char *)self + OBJC_IVAR_$_Hello$_b))++;
    }
static void __Hello__main_block_copy_0(struct __Hello__main_block_impl_0*dst, struct __Hello__main_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Hello__main_block_dispose_0(struct __Hello__main_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Hello__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Hello__main_block_impl_0*, struct __Hello__main_block_impl_0*);
  void (*dispose)(struct __Hello__main_block_impl_0*);
} __Hello__main_block_desc_0_DATA = { 0, sizeof(struct __Hello__main_block_impl_0), __Hello__main_block_copy_0, __Hello__main_block_dispose_0};

static void _I_Hello_main(Hello * self, SEL _cmd) {
    ((void (*)())&__Hello__main_block_impl_0((void *)__Hello__main_block_func_0, &__Hello__main_block_desc_0_DATA, self, 570425344));
}

可以看到这次转换后的代码多了一个 OBJC_IVAR__Hello_b 全局变量,这个全局变量表示变量 b 的内存偏移量,并且在 Block 内部引用了 self(这也是为什么 Block 中使用 Ivar 也会造成循环引用的原因),在 _Hello__main_block_func_0 函数中使用 self 作为基地址 + OBJC_IVAR_Hello_b 偏移量的方式获取到内存地址,然后进行 + 操作。(这里还多了两个函数: copy 和 dispose ,后文会解释)

__block 修饰符

上文中我们对局部变量 d 进行 + 操作时,编译器提示我们需要加 __block 修饰符,这个 __block 修饰符是什么?为什么加上之后就可以在 Block 中对局部变量进行修改?

__block int a = 1;
^(void) {
   a++;
};

以上代码转换后变为:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __Hello__main_block_impl_0 {
  struct __block_impl impl;
  struct __Hello__main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Hello__main_block_impl_0(void *fp, struct __Hello__main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Hello__main_block_func_0(struct __Hello__main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a)++;
    }
static void __Hello__main_block_copy_0(struct __Hello__main_block_impl_0*dst, struct __Hello__main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __Hello__main_block_dispose_0(struct __Hello__main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __Hello__main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Hello__main_block_impl_0*, struct __Hello__main_block_impl_0*);
  void (*dispose)(struct __Hello__main_block_impl_0*);
} __Hello__main_block_desc_0_DATA = { 0, sizeof(struct __Hello__main_block_impl_0), __Hello__main_block_copy_0, __Hello__main_block_dispose_0};

static void _I_Hello_main(Hello * self, SEL _cmd) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    ((void (*)())&__Hello__main_block_impl_0((void *)__Hello__main_block_func_0, &__Hello__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
}

在 __Hello__main_block_impl_0 中,局部变量 a 变成了 __Block_byref_a_0, 这个 struct 中持有 int 类型的变量 a,并且还有 __Block_byref_a_0 类型的指针 __forwarding。

可以发现 __Block_byref_a_0 并没有声明在 __Hello__main_block_impl_0。是因为当有多个 Block 引用了用一个 __block 修饰的变量的情况下,可以复用 __Block_byref_a_0。

在 _I_Hello_main 函数的实现中,先是构建了 __Block_byref_a_0,将 isa 指向 (void*)0 也就是 NULL,将 __forwarding 指向自身,并且将 int 类型的 a 初始化为 1。在构建 __Hello__main_block_impl_0 的时候,将 __Block_byref_a_0 的地址传入了构造函数中,通过指针传递,解决了作用域限制的问题,达到了在 Block 调用函数中使用 __Block_byref_a_0 的目的。

在 __Hello__main_block_func_0 中使用 (a->__forwarding->a)++ 的方式来使局部变量 a 进行 + 操作。第一个 a 指的是 __Block_byref_a_0,由于 __forwarding 指向自身,所以 a->__forwarding 还是 __Block_byref_a_0。第二个 a 就是 __Block_byref_a_0 中的变量 a。


源码

关于 Block 的底层实现源码,可以参考这个 libclosure-67,本文只介绍关于 copy 相关的源码,更多细节读者可以自行研读源码。

_Block_copy

上文提到对 NSConcreteStackBlock 进行 copy 操作后可以生成 NSConcreteMallocBlock ,在 runtime.c 中可以看到 copy 的具体实现:

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;

    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    // 1
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 2
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 3
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

实现中有多个针对 flags 的条件判断,flags 在 Block_private.h是这样定义的:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

其中 BLOCK_REFCOUNT_MASK 表示 NSConcreteStackBlock,BLOCK_NEEDS_FREE 表示 NSConcreteMallocBlock,BLOCK_IS_GLOBAL 表示 NSConcreteGlobalBlock。

1、如果是 NSConcreteMallocBlock,则对引用计数递增并且返回 aBlock。
2、如果是 NSConcreteGlobalBlock 则直接返回 aBlock。
3、如果是 NSConcreteStackBlock ,首先分配一块与原 Block 大小相同的内存,然后使用 memmove() 函数将原 Block 的所有元数据按位复制到 result 上,接着将 result 的引用计数置为 0(注释表示这是不需要的,可能是防止某种异常情况出现),之后将 result 的 flags 置为 BLOCK_NEEDS_FREE,引用计数置为 2(注释表示逻辑引用计数为 1,Block 的引用计数以 2 为单位,每次递增2),再调用 _Block_call_copy_helper 函数,这个函数只在 Block 内引用了对象类型或 __block 修饰变量的情况下才会有作用(这种情况转换后的代码中会生成 copy 和 dispose 函数,这两个函数用来管理对象内存的),对于 Block 中的对象类型的 copy 都是指针 copy,生成一个指针指向原对象,最后将 result 的 isa 置为 NSConcreteMallocBlock。

_Block_byref_copy

前面提到对于 __block 修饰的变量最终会转换成 __Block_byref_a_0。在 Block 由栈 copy 到堆的时候,__Block_byref_a_0 也会有 copy 行为,_Block_byref_copy 的具体实现为:

// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;
    // 1
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        // 2
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    // 3
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // 4
    return src->forwarding;
}

1、如果是栈上的 Block_byref 则分配与原 Block_byref 大小相同的内存,将 isa 置为 NULL,将 copy 后的 Block_byref 置为 BLOCK_BYREF_NEEDS_FREE,引用逻辑计数为 2,调用方和栈各有一份,copy 后的 Block_byref 的 forwarding 指向自己,原 Block_byref 的 forwarding 指向 copy 后的 Block_byref,最后赋值 size。
2、对 Block_byref 是否含有对象类型进行判断,并针对不同情况进行内存管理。
3、如果是堆上的 Block_byref 则对其引用计算递增。



4、返回堆上的 Block_byref。

之所以 __block 修饰的变量可以在 Block 中被修改,是因为在 Block 被 copy 到堆上时, Block_byref 也被 copy 到了堆上,并且栈和堆中的 Block_byref 都指向了堆中的 Block_byref。

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

推荐阅读更多精彩内容

  • Block作为Objective-C中闭包的实现在iOS开发中占有非常重要的地位,尤其是作为回调(callback...
    NotFunGuy阅读 739评论 0 4
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 907评论 1 3
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 829评论 0 5
  • 摘要 Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”,那么block到底是什么东西...
    CholMay阅读 1,148评论 2 10
  • Block概要 Block:带有自动变量的匿名函数。 匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作...
    zweic阅读 501评论 0 2