iOS Block Part6:block拷贝的实现

1.前导

1.1参考文档

参考文档1:BlocksRuntime/runtime.c
参考文档2:Block_private.h

1.2带入问题

Q1:栈block拷贝生成堆block的具体流程是怎样的?
Q2:为什么栈block拷贝生成堆block,栈block捕获的变量的__forwarding会指向堆上的变量?


block(int any)
struct __main_block_impl_0需要拷贝 

block(NSString * any)
struct __main_block_impl_0需要拷贝 
NSString * any需要拷贝

block(__block int any)
struct __main_block_impl_0需要拷贝 
struct __Block_byref_any_0需要拷贝

block(__block NSString * any)
struct __main_block_impl_0需要拷贝 
struct __Block_byref_any_0需要拷贝
NSString * any需要拷贝

四小类block的编译结果都有struct __main_block_impl_0需要拷贝.struct __main_block_impl_0的拷贝也就是block的拷贝的起点.

2. block的拷贝的起点

NSObject.mm内的objc_retainBlock在block进行赋值(赋__strong变量,__weak变量不适用)的时候就会调用.这也真是block拷贝的开始.

//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}
void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

刨根问底会看到_Block_copy_internal方法,忽略所有的GC代码,来个精简版本:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    //printf("_Block_copy_internal(%p, %x)\n", arg, flags);
    if (!arg) return NULL;

    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    // Its a stack block.  Make a copy.
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
}

功能很清晰,ARC环境下栈block自动拷贝成堆block会走:

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    result->isa = _NSConcreteMallocBlock;
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

以上代码很好的回答了Q1:栈block拷贝生成堆block的具体流程是怎样的?.

_Block_copy_internal内其他部分的代码会和block的内存管理相关,下一篇文章会说.

3. block的拷贝的继续

3.1 拷贝继续,所用方法梳理

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    result->isa = _NSConcreteMallocBlock;
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}
注意代码:

if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
    //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
    (*aBlock->descriptor->copy)(result, aBlock); // do fixup
}

BLOCK_HAS_COPY_DISPOSE是标记struct __main_block_impl_0是否需要针对内部结构再深入拷贝,
如果需要继续调用结构体的拷贝函数进行拷贝.
----------------------------------------------------------------------------------
block(NSString * any)

 NSString * any = ((NSString *(*)(id, SEL, NSString *, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_main_18d483_mi_0);
    
 void (*test)() = (
                   (void (*)())&__main_block_impl_0(
                                                    (void *)__main_block_func_0,
                                                    &__main_block_desc_0_DATA,
                                                    any,
                                                    570425344
                                                    )
                   );
----------------------------------------------------------------------------------
注意570425344.
enum {
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24),
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), /* Helpers have C++ code. */
    BLOCK_IS_GC =             (1 << 27),
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_DESCRIPTOR =    (1 << 29)
};
570425344为(1 << 25 | 1 << 29),所以570425344==>BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR

以上展示的是block(NSString * any)传入的flag.下面直接给出四类Block的结果,有兴趣可以回头看看编译的源码.

block(int any)==>flag=0==>完结
block(NSString * any)==>flag=570425344==>继续内层拷贝
block(__block int any)==>flag=570425344==>继续内层拷贝
block(__block NSString * any)==>flag=570425344==>继续内层拷贝

可以看出除捕获int any的block在经过一层拷贝之后完结外,其他的都仍然要继续.

block(int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
------完结-----

-------------------------------------------------------------

block(NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
NSString * any需要拷贝==>继续

block(__block int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝==>继续 

block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝==>继续 
NSString * any需要拷贝 

(*aBlock->descriptor->copy)指向的方法前面的文章已经有过代码连线+示意图,这里就不再说明了.

  • block捕获对象类型,编译结果中有的对对象类型的拷贝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
  • block捕获__block修饰的数据,编译结果中有的对struct __Block_byref_any_0的拷贝方法
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
  • block捕获__block修饰的对象,编译结果中有的对struct __Block_byref_any_0内部对象的拷贝方法
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

__block NSString * any编译后的结构体 __Block_byref_any_0会比
__block int any编译后的结构体 __Block_byref_any_0多出两个函数指针

struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//多出的
 void (*__Block_byref_id_object_dispose)(void*);//多出的
 NSString *any;
};

__Block_byref_id_object_copy__Block_byref_id_object_copy_131对应

block(NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __main_block_copy_0

block(__block int any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0

block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131

3.2 所用方法内部是什么

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

他们里面调用的都调用了_Block_object_assign.

各位注意:
_Block_object_assign非常非常非常重要,可以说block捕获参数的拷贝就是在_Block_object_assign函数里"绕"!!!

3.3 _Block_object_assign

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}
_Block_object_assign参数flag相关

BLOCK_FIELD_IS_BLOCK (7)
BLOCK_FIELD_IS_BYREF (8)
BLOCK_FIELD_IS_CALLER (128)
BLOCK_FIELD_IS_OBJECT (3)
BLOCK_FIELD_IS_WEAK (16)

再看看先前相关总结的方法调用_Block_object_assign用的都是什么flag

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

3==BLOCK_FIELD_IS_OBJECT
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}

8==BLOCK_FIELD_IS_BYREF
static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

131==BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER;

_Block_object_assign三个参数:
参数1:目标地址
参数2:源对象地址
参数3:flog的不同确定后方走什么路

_Block_object_assign与一系列其他函数的嵌套使用造就了block对捕获参数的拷贝.

4._Block_object_assign内部流程

block(int)
block(NSString * any)
block(__block int)
block(__block NSString * any)

四小类block中后三种都会有对_Block_object_assign的调用,本文就不一一的说了,因为有很多重复的内容.
本文以block(__block NSString * any)的拷贝来说明
__block NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
    NSLog(@"%@",any);
};
test();

4.1 调用__main_block_copy_0

调用方式:
__main_block_copy_0调用_Block_object_assign
实现:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->any, (void*)src->any, 8/*BLOCK_FIELD_IS_BYREF*/);
}
参数1:目标__main_block_impl_0->__Block_byref_any地址
参数2:源__main_block_impl_0->__Block_byref_any地址
参数3: BLOCK_FIELD_IS_BYREF

用途:
拷贝结构体__Block_byref_any(结构体内含NSString * any)

else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
_Block_byref_assign_copy内部实现(已经删除GC相关内容),所走的代码已经标注.

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
    //printf("src dump: %s\n", _Block_byref_dump(src));
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {<<4.1入口
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                (void *)&copy->byref_keep,
                (void *)&src->byref_keep,
                src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}

Q2解决:为什么栈block拷贝生成堆block,栈block捕获的变量的__forwarding会指向堆上的变量?下面的代码片段可以很好的解释.

struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copycopy->size = src->size;

到此为为止:

block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0 ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131

4.2 调用__Block_byref_id_object_copy_131

4.2.1 如何调用到__Block_byref_id_object_copy_131
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {<<4.2入口
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}

调用src(_Block_byref结构体)byref_keep函数
_Block_byref结构体又哪来的byref_keep函数?
Block_private.h 与C++编译的对比

Block_private.h内

struct Block_byref {
    void *isa;
struct Block_byref *forwarding;
    int flags; /* refcount; */
int size;
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
/* long shared[0]; */
};
C++编译内

struct __Block_byref_any_0 {
 void *__isa;
 __Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *any;
};

显而易见__Block_byref_id_object_copy就是byref_keep.

__Block_byref_id_object_copy又指向谁呢?

当然是静态函数__Block_byref_id_object_copy_131

4.2.2 __Block_byref_id_object_copy_131具体分析

调用方式:
__Block_byref_id_object_copy_131调用_Block_object_assign

实现:

static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
关于(char*)dst + 40,(char*)src + 40的解释

struct __Block_byref_any_0 {
void *__isa;
__Block_byref_any_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);//多出的
 void (*__Block_byref_id_object_dispose)(void*);//多出的
 NSString *any;
};
4个地址的字节数(32)+2个int的字节数(8)=40字节,所以加40之后刚好指向NSString *any;
参数1:目标__main_block_impl_0->__Block_byref_any->NSString * any
参数2:源__main_block_impl_0->__Block_byref_any->NSString * any
参数3: BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER

用途:
拷贝__Block_byref_any内含的NSString * any(内部层次关系:__main_block_impl_0->__Block_byref_any->NSString * any)

if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
} else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}

到此为止,捕获__block修饰对象数据类型block的拷贝3层,全部梳理完成.让我们再来看看示意图.


block(__block NSString)_map.png
block(__block NSString * any)
struct __main_block_impl_0需要拷贝 所用方法==>static void _Block_copy_internal ==> 分析完毕
struct __Block_byref_any_0需要拷贝 所用方法==>static void __main_block_copy_0 ==> 分析完毕
NSString * any需要拷贝 所用方法==>static void __Block_byref_id_object_copy_131 ==> 分析完毕

在分析block(__block NSString * any)的三层拷贝的过程中,我们也已经对最开始提出的问题进行回答.
其他小类block拷贝的步骤是block(__block NSString * any)的子集,有兴趣可以自己梳理流程.
另外销毁与拷贝是互逆的过程,所有的销毁方法也在BlocksRuntime/runtime.c内有兴趣,可以再看看源码.

  • what's more!

block拷贝其实也是block内存管理的一部分,下一篇将展开讲讲block的内存管理.


参考文献:
Block技巧与底层解析 by tripleCC

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

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,753评论 0 23
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,792评论 10 69
  • Block是iOS开发中一种比较特殊的数据结构,它可以保存一段代码,在合适的地方再调用,具有语法简介、回调方便、编...
    飞鱼湾阅读 4,099评论 0 7
  • 1: 什么是block?1.0: Block的语法1.1: block编译转换结构1.2: block实际结构 2...
    iYeso阅读 829评论 0 5
  • 目录 Block底层解析什么是block?block编译转换结构block实际结构block的类型NSConcre...
    tripleCC阅读 33,138评论 32 388