iOS Block Part6:block拷貝的實現(xiàn)

1.前導(dǎo)

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的編譯結(jié)果都有struct __main_block_impl_0需要拷貝.struct __main_block_impl_0的拷貝也就是block的拷貝的起點.

2. block的拷貝的起點

NSObject.mm內(nèi)的objc_retainBlock在block進行賦值(賦__strong變量,__weak變量不適用)的時候就會調(diào)用.這也真是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環(huán)境下棧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內(nèi)其他部分的代碼會和block的內(nèi)存管理相關(guān),下一篇文章會說.

3. block的拷貝的繼續(xù)

3.1 拷貝繼續(xù),所用方法梳理

// 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是標(biāo)記struct __main_block_impl_0是否需要針對內(nèi)部結(jié)構(gòu)再深入拷貝,
如果需要繼續(xù)調(diào)用結(jié)構(gòu)體的拷貝函數(shù)進行拷貝.
----------------------------------------------------------------------------------
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的結(jié)果,有興趣可以回頭看看編譯的源碼.

block(int any)==>flag=0==>完結(jié)
block(NSString * any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝
block(__block int any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝
block(__block NSString * any)==>flag=570425344==>繼續(xù)內(nèi)層拷貝

可以看出除捕獲int any的block在經(jīng)過一層拷貝之后完結(jié)外,其他的都仍然要繼續(xù).

block(int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
------完結(jié)-----

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

block(NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
NSString * any需要拷貝==>繼續(xù)

block(__block int any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續(xù) 

block(__block NSString * any)
struct __main_block_impl_0需要拷貝 所用方法==>static void _Block_copy_internal ==> 分析完畢
struct __Block_byref_any_0需要拷貝==>繼續(xù) 
NSString * any需要拷貝 

(*aBlock->descriptor->copy)指向的方法前面的文章已經(jīng)有過代碼連線+示意圖,這里就不再說明了.

  • block捕獲對象類型,編譯結(jié)果中有的對對象類型的拷貝方法
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修飾的數(shù)據(jù),編譯結(jié)果中有的對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修飾的對象,編譯結(jié)果中有的對struct __Block_byref_any_0內(nèi)部對象的拷貝方法
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編譯后的結(jié)構(gòu)體 __Block_byref_any_0會比
__block int any編譯后的結(jié)構(gòu)體 __Block_byref_any_0多出兩個函數(shù)指針

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對應(yīng)

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 所用方法內(nèi)部是什么

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);
}

他們里面調(diào)用的都調(diào)用了_Block_object_assign.

各位注意:
_Block_object_assign非常非常非常重要,可以說block捕獲參數(shù)的拷貝就是在_Block_object_assign函數(shù)里"繞"!!!

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參數(shù)flag相關(guān)

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)

再看看先前相關(guān)總結(jié)的方法調(diào)用_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三個參數(shù):
參數(shù)1:目標(biāo)地址
參數(shù)2:源對象地址
參數(shù)3:flog的不同確定后方走什么路

_Block_object_assign與一系列其他函數(shù)的嵌套使用造就了block對捕獲參數(shù)的拷貝.

4._Block_object_assign內(nèi)部流程

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

四小類block中后三種都會有對_Block_object_assign的調(diào)用,本文就不一一的說了,因為有很多重復(fù)的內(nèi)容.
本文以block(__block NSString * any)的拷貝來說明
__block NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
    NSLog(@"%@",any);
};
test();

4.1 調(diào)用__main_block_copy_0

調(diào)用方式:
__main_block_copy_0調(diào)用_Block_object_assign
實現(xiàn):

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*/);
}
參數(shù)1:目標(biāo)__main_block_impl_0->__Block_byref_any地址
參數(shù)2:源__main_block_impl_0->__Block_byref_any地址
參數(shù)3: BLOCK_FIELD_IS_BYREF

用途:
拷貝結(jié)構(gòu)體__Block_byref_any(結(jié)構(gòu)體內(nèi)含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內(nèi)部實現(xiàn)(已經(jīng)刪除GC相關(guān)內(nèi)容),所走的代碼已經(jīng)標(biāo)注.

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 調(diào)用__Block_byref_id_object_copy_131

4.2.1 如何調(diào)用到__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);
}

調(diào)用src(_Block_byref結(jié)構(gòu)體)byref_keep函數(shù)
_Block_byref結(jié)構(gòu)體又哪來的byref_keep函數(shù)?
Block_private.h 與C++編譯的對比

Block_private.h內(nèi)

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++編譯內(nèi)

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又指向誰呢?

當(dāng)然是靜態(tài)函數(shù)__Block_byref_id_object_copy_131

4.2.2 __Block_byref_id_object_copy_131具體分析

調(diào)用方式:
__Block_byref_id_object_copy_131調(diào)用_Block_object_assign

實現(xiàn):

static void __Block_byref_id_object_copy_131(void *dst, void *src)
{
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
關(guān)于(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個地址的字節(jié)數(shù)(32)+2個int的字節(jié)數(shù)(8)=40字節(jié),所以加40之后剛好指向NSString *any;
參數(shù)1:目標(biāo)__main_block_impl_0->__Block_byref_any->NSString * any
參數(shù)2:源__main_block_impl_0->__Block_byref_any->NSString * any
參數(shù)3: BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER

用途:
拷貝__Block_byref_any內(nèi)含的NSString * any(內(nèi)部層次關(guān)系:__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修飾對象數(shù)據(jù)類型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)的三層拷貝的過程中,我們也已經(jīng)對最開始提出的問題進行回答.
其他小類block拷貝的步驟是block(__block NSString * any)的子集,有興趣可以自己梳理流程.
另外銷毀與拷貝是互逆的過程,所有的銷毀方法也在BlocksRuntime/runtime.c內(nèi)有興趣,可以再看看源碼.

  • what's more!

block拷貝其實也是block內(nèi)存管理的一部分,下一篇將展開講講block的內(nèi)存管理.


參考文獻:
Block技巧與底層解析 by tripleCC

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末壁袄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子娇斑,更是在濱河造成了極大的恐慌敷鸦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妒峦,死亡現(xiàn)場離奇詭異重斑,居然都是意外死亡,警方通過查閱死者的電腦和手機肯骇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門窥浪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笛丙,你說我怎么就攤上這事漾脂。” “怎么了若债?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵符相,是天一觀的道長。 經(jīng)常有香客問我蠢琳,道長啊终,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任傲须,我火速辦了婚禮蓝牲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泰讽。我一直安慰自己例衍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布已卸。 她就那樣靜靜地躺著佛玄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪累澡。 梳的紋絲不亂的頭發(fā)上梦抢,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音愧哟,去河邊找鬼奥吩。 笑死,一個胖子當(dāng)著我的面吹牛蕊梧,可吹牛的內(nèi)容都是我干的霞赫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼肥矢,長吁一口氣:“原來是場噩夢啊……” “哼端衰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旅东,失蹤者是張志新(化名)和其女友劉穎惕味,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玉锌,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年疟羹,在試婚紗的時候發(fā)現(xiàn)自己被綠了主守。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡榄融,死狀恐怖参淫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愧杯,我是刑警寧澤涎才,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站力九,受9級特大地震影響耍铜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跌前,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一棕兼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抵乓,春花似錦伴挚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜈出,卻和暖如春田弥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掏缎。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工皱蹦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眷蜈。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓沪哺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酌儒。 傳聞我的和親對象是個殘疾皇子辜妓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,768評論 0 23
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block籍滴、GCD酪夷,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,810評論 10 69
  • Block是iOS開發(fā)中一種比較特殊的數(shù)據(jù)結(jié)構(gòu),它可以保存一段代碼孽惰,在合適的地方再調(diào)用晚岭,具有語法簡介、回調(diào)方便勋功、編...
    飛魚灣閱讀 4,117評論 0 7
  • 1: 什么是block坦报?1.0: Block的語法1.1: block編譯轉(zhuǎn)換結(jié)構(gòu)1.2: block實際結(jié)構(gòu) 2...
    iYeso閱讀 836評論 0 5
  • 目錄 Block底層解析什么是block?block編譯轉(zhuǎn)換結(jié)構(gòu)block實際結(jié)構(gòu)block的類型NSConcre...
    tripleCC閱讀 33,179評論 32 388