Objective-C中的Block

1.相關(guān)概念

在這篇筆記開始之前竹伸,我們需要對以下概念有所了解尘颓。

1.1 操作系統(tǒng)中的棧和堆

注:這里所說的堆和棧與數(shù)據(jù)結(jié)構(gòu)中的堆和棧不是一回事瓦堵。

我們先來看看一個(gè)由C/C++/OBJC編譯的程序占用內(nèi)存分布的結(jié)構(gòu):


內(nèi)存分布結(jié)構(gòu)
內(nèi)存分布結(jié)構(gòu)
  • 棧區(qū)(stack):由系統(tǒng)自動分配狼电,一般存放函數(shù)參數(shù)值贿衍、局部變量的值等浪默。由編譯器自動創(chuàng)建與釋放牡直。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧,即后進(jìn)先出浴鸿、先進(jìn)后出的原則井氢。

例如:在函數(shù)中申明一個(gè)局部變量int b;系統(tǒng)自動在棧中為b開辟空間。

  • 堆區(qū)(heap):一般由程序員申請并指明大小岳链,最終也由程序員釋放花竞。如果程序員不釋放,程序結(jié)束時(shí)可能會由OS回收。對于堆區(qū)的管理是采用鏈表式管理的约急,操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表零远,當(dāng)接收到程序分配內(nèi)存的申請時(shí),操作系統(tǒng)就會遍歷該鏈表厌蔽,遍歷到一個(gè)記錄的內(nèi)存地址大于申請內(nèi)存的鏈表節(jié)點(diǎn)牵辣,并將該節(jié)點(diǎn)從該鏈表中刪除,然后將該節(jié)點(diǎn)記錄的內(nèi)存地址分配給程序奴饮。

例如:在C中malloc函數(shù)
char *p1;
p1 = (char *)malloc(10);
但是p1本身是在棧中的纬向。

鏈表:是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),一般分為單向鏈表戴卜、雙向鏈表逾条、循環(huán)鏈表。以下為單向鏈表的結(jié)構(gòu)圖:

單向鏈表
單向鏈表

單向鏈表是鏈表中最簡單的一種投剥,它包含兩個(gè)區(qū)域师脂,一個(gè)信息域和一個(gè)指針域。信息域保存或顯示關(guān)于節(jié)點(diǎn)的信息江锨,指針域儲存下一個(gè)節(jié)點(diǎn)的地址吃警。
上述的空閑內(nèi)存地址鏈表的信息域保存的就是空閑內(nèi)存的地址。

  • 全局區(qū)/靜態(tài)區(qū):顧名思義啄育,全局變量和靜態(tài)變量存儲在這個(gè)區(qū)域酌心。只不過初始化的全局變量和靜態(tài)變量存儲在一塊,未初始化的全局變量和靜態(tài)變量存儲在一塊灸撰。程序結(jié)束后由系統(tǒng)釋放谒府。
  • 文字常量區(qū):這個(gè)區(qū)域主要存儲字符串常量。程序結(jié)束后由系統(tǒng)釋放浮毯。
  • 程序代碼區(qū):這個(gè)區(qū)域主要存放函數(shù)體的二進(jìn)制代碼完疫。

下面舉一個(gè)前輩寫的例子:

//main.cpp
int a = 0; // 全局初始化區(qū)
char *p1; // 全局未初始化區(qū)
main {
    int b; // 棧
    char s[] = "abc"; // 棧
    char *p2; // 棧
    char *p3 = "123456"; // 123456\0在常量區(qū),p3在棧上
    static int c =0债蓝; // 全局靜態(tài)初始化區(qū)
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20); // 分配得來的10和20字節(jié)的區(qū)域就在堆區(qū)
    strcpy(p1, "123456"); // 123456\0在常量區(qū)壳鹤,這個(gè)函數(shù)的作用是將"123456" 這串字符串復(fù)制一份放在p1申請的10個(gè)字節(jié)的堆區(qū)域中。
    // p3指向的"123456"與這里的"123456"可能會被編譯器優(yōu)化成一個(gè)地址饰迹。
}

strcpy函數(shù)
原型聲明:extern char *strcpy(char* dest, const char *src);
功能:把從src地址開始且含有NULL結(jié)束符的字符串復(fù)制到以dest開始的地址空間芳誓。

1.2 結(jié)構(gòu)體(Struct)

在C語言中,結(jié)構(gòu)體(struct)指的是一種數(shù)據(jù)結(jié)構(gòu)啊鸭。結(jié)構(gòu)體可以被聲明為變量锹淌、指針或數(shù)組等,用以實(shí)現(xiàn)較復(fù)雜的數(shù)據(jù)結(jié)構(gòu)赠制。結(jié)構(gòu)體同時(shí)也是一些元素的集合赂摆,這些元素稱為結(jié)構(gòu)體的成員(member),且這些成員可以為不同的類型,成員一般用名字訪問烟号。
我們來看看結(jié)構(gòu)體的定義:

struct tag { member-list } variable-list;
  • struct:結(jié)構(gòu)體關(guān)鍵字绊谭。
  • tag:結(jié)構(gòu)體標(biāo)簽。
  • member-list:結(jié)構(gòu)體成員列表汪拥。
  • variable-list:為結(jié)構(gòu)體聲明的變量列表达传。

在一般情況下,tag迫筑,member-list宪赶,variable-list這三部分至少要出現(xiàn)兩個(gè)。以下為示例:

// 該結(jié)構(gòu)體擁有3個(gè)成員脯燃,整型的a逊朽,字符型的b,雙精度型的c
// 并且為該結(jié)構(gòu)體聲明了一個(gè)變量s1
// 該結(jié)構(gòu)體沒有標(biāo)明其標(biāo)簽
struct{
    int a;
    char b;
    double c;
} s1;

// 該結(jié)構(gòu)體擁有同樣的三個(gè)成員
// 并且該結(jié)構(gòu)體標(biāo)明了標(biāo)簽EXAMPLE
// 該結(jié)構(gòu)體沒有聲明變量
struct EXAMPLE{
    int a;
    char b;
    double c;
};

//用EXAMPLE標(biāo)簽的結(jié)構(gòu)體曲伊,另外聲明了變量t1、t2追他、t3
struct EXAMPLE t1, t2[20], *t3;

以上就是簡單結(jié)構(gòu)體的代碼示例坟募。結(jié)構(gòu)體的成員可以包含其他結(jié)構(gòu)體,也可以包含指向自己結(jié)構(gòu)體類型的指針邑狸。結(jié)構(gòu)體的變量也可以是指針懈糯。

下面我們來看看結(jié)構(gòu)體成員的訪問。結(jié)構(gòu)體成員依據(jù)結(jié)構(gòu)體變量類型的不同单雾,一般有2種訪問方式赚哗,一種為直接訪問,一種為間接訪問硅堆。直接訪問應(yīng)用于普通的結(jié)構(gòu)體變量屿储,間接訪問應(yīng)用于指向結(jié)構(gòu)體變量的指針。直接訪問使用結(jié)構(gòu)體變量名.成員名渐逃,間接訪問使用(*結(jié)構(gòu)體指針名).成員名或者使用結(jié)構(gòu)體指針名->成員名够掠。相同的成員名稱依靠不同的變量前綴區(qū)分。

struct EXAMPLE{
    int a;
    char b;
};

//聲明結(jié)構(gòu)體變量s1和指向結(jié)構(gòu)體變量的指針s2
struct EXAMPLE s1, *s2;

//給變量s1和s2的成員賦值,注意s1.a和s2->a并不是同一成員
s1.a = 5;
s1.b = 6;
s2->a = 3;
s2->b = 4;

最后我們來看看結(jié)構(gòu)體成員存儲茄菊。在內(nèi)存中疯潭,編譯器按照成員列表順序分別為每個(gè)結(jié)構(gòu)體成員分配內(nèi)存。如果想確認(rèn)結(jié)構(gòu)體占多少存儲空間面殖,則使用關(guān)鍵字sizeof竖哩,如果想得知結(jié)構(gòu)體的某個(gè)特定成員在結(jié)構(gòu)體的位置,則使用offsetof宏(定義于stddef.h)脊僚。

struct EXAMPLE{
    int a;
    char b;
};

//獲得EXAMPLE類型結(jié)構(gòu)體所占內(nèi)存大小
int size_example = sizeof( struct EXAMPLE );

//獲得成員b相對于EXAMPLE儲存地址的偏移量
int offset_b = offsetof( struct EXAMPLE, b );

1.3 閉包(Closure)

閉包就是一個(gè)函數(shù)相叁,或者一個(gè)指向函數(shù)的指針,加上這個(gè)函數(shù)執(zhí)行的非局部變量。
說的通俗一點(diǎn)钝荡,就是閉包允許一個(gè)函數(shù)訪問聲明該函數(shù)運(yùn)行上下文中的變量街立,甚至可以訪問不同運(yùn)行上文中的變量。
我們用腳本語言來看一下:

function funA(callback){
    alert(callback());
}

function funB(){
    var str = "Hello World"; // 函數(shù)funB的局部變量埠通,函數(shù)funA的非局部變量
    funA(
        function(){
            return str;
        }
    )赎离;
}

通過上面的代碼我們可以看出,按常規(guī)思維來說端辱,變量str是函數(shù)funB的局部變量梁剔,作用域只在函數(shù)funB中,函數(shù)funA是無法訪問到str的舞蔽。但是上述代碼示例中函數(shù)funA中的callback可以訪問到str荣病,這是為什么呢,因?yàn)殚]包性渗柿。

2.blcok基礎(chǔ)知識

block實(shí)際上就是Objective-C語言對閉包的實(shí)現(xiàn)个盆。

2.1 block的原型及定義

我們來看看block的原型:

NSString * ( ^ myBlock )( int );

上面的代碼聲明了一個(gè)block(^)原型,名字叫做myBlock朵栖,包含一個(gè)int型的參數(shù)颊亮,返回值為NSString類型的指針。

下面來看看block的定義:

myBlock = ^( int paramA )
{
    return [ NSString stringWithFormat: @"Passed number: %i", paramA ];
};

上面的代碼中陨溅,將一個(gè)函數(shù)體賦值給了myBlock變量终惑,其接收一個(gè)名為paramA的參數(shù),返回一個(gè)NSString對象门扇。

注意:一定不要忘記block后面的分號雹有。

定義好block后,就可以像使用標(biāo)準(zhǔn)函數(shù)一樣使用它了:

myBlock(7);

由于block數(shù)據(jù)類型的語法會降低整個(gè)代碼的閱讀性臼寄,所以常使用typedef來定義block類型霸奕。例如,下面的代碼創(chuàng)建了GetPersonEducationInfoGetPersonFamilyInfo兩個(gè)新類型吉拳,這樣我們就可以在下面的方法中使用更加有語義的數(shù)據(jù)類型铅祸。

// Person.h
#import <Foundation/Foundation.h>

// Define a new type for the block
typedef NSString * (^GetPersonEducationInfo)(NSString *);
typedef NSString * (^GetPersonFamilyInfo)(NSString *);

@interface Person : NSObject

- (NSString *)getPersonInfoWithEducation:(GetPersonEducationInfo)educationInfo
    andFamily:(GetPersonFamilyInfo)familyInfo;

@end

我們用一張大師文章里的圖來總結(jié)一下block的結(jié)構(gòu):

block
block

2.2 將block作為參數(shù)傳遞

// .h
-(void) testBlock:( NSString * ( ^ )( int ) )myBlock;

// .m
-(void) testBlock:( NSString * ( ^ )( int ) )myBlock
{
    NSLog(@"Block returned: %@", myBlock(7) );
}

由于Objective-C是強(qiáng)制類型語言,所以作為函數(shù)參數(shù)的block也必須要指定返回值的類型合武,以及相關(guān)參數(shù)類型临梗。

2.3 閉包性

上文說過,block實(shí)際是Objc對閉包的實(shí)現(xiàn)稼跳。
我們來看看下面代碼:

#import <Cocoa/Cocoa.h>

void logBlock( int ( ^ theBlock )( void ) )
{
    NSLog( @"Closure var X: %i", theBlock() );
}

int main( void )
{
    NSAutoreleasePool * pool;
    int ( ^ myBlock )( void );
    int x;

    pool = [ [ NSAutoreleasePool alloc ] init ];
    x = 42;

    myBlock = ^( void )
    {
        return x;
    };

    logBlock( myBlock );

    [ pool release ];

    return EXIT_SUCCESS;
}

上面的代碼在main函數(shù)中聲明了一個(gè)整型盟庞,并賦值42,另外還聲明了一個(gè)block汤善,該block會將42返回什猖。然后將block傳遞給logBlock函數(shù)票彪,該函數(shù)會顯示出返回的值42。即使是在函數(shù)logBlock中執(zhí)行block不狮,而block又聲明在main函數(shù)中降铸,但是block仍然可以訪問到x變量,并將這個(gè)值返回摇零。

注意:block同樣可以訪問全局變量推掸,即使是static

2.4 block中變量的復(fù)制與修改

對于block外的變量引用驻仅,block默認(rèn)是將其復(fù)制到其數(shù)據(jù)結(jié)構(gòu)中來實(shí)現(xiàn)訪問的谅畅,如下圖:

copy variables
copy variables

通過block進(jìn)行閉包的變量是const的。也就是說不能在block中直接修改這些變量噪服。來看看當(dāng)block試著增加x的值時(shí)毡泻,會發(fā)生什么:

myBlock = ^( void )
{
    x++;

    return x;
};

編譯器會報(bào)錯,表明在block中變量x是只讀的粘优。
有時(shí)候確實(shí)需要在block中處理變量仇味,怎么辦?別著急雹顺,我們可以用__block關(guān)鍵字來聲明變量邪铲,這樣就可以在block中修改變量了。
基于之前的代碼无拗,給x變量添加__block關(guān)鍵字,如下:

__block int x;

對于用__block修飾的外部變量引用昧碉,block是復(fù)制其引用地址來實(shí)現(xiàn)訪問的英染,如下圖:

ref variables
ref variables

3.編譯器中的block

3.1 block的數(shù)據(jù)結(jié)構(gòu)定義

我們通過大師文章中的一張圖來說明:

block struct
block struct

上圖這個(gè)結(jié)構(gòu)是在棧中的結(jié)構(gòu),我們來看看對應(yīng)的結(jié)構(gòu)體定義:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

從上面代碼看出被饿,Block_layout就是對block結(jié)構(gòu)體的定義:

  • isa指針:指向表明該block類型的類四康。
  • flags:按bit位表示一些block的附加信息,比如判斷block類型狭握、判斷block引用計(jì)數(shù)闪金、判斷block是否需要執(zhí)行輔助函數(shù)等。
  • reserved:保留變量论颅,我的理解是表示block內(nèi)部的變量數(shù)哎垦。
  • invoke:函數(shù)指針,指向具體的block實(shí)現(xiàn)的函數(shù)調(diào)用地址恃疯。
  • descriptor:block的附加描述信息漏设,比如保留變量數(shù)、block的大小今妄、進(jìn)行copydispose的輔助函數(shù)指針郑口。
  • variables:因?yàn)閎lock有閉包性鸳碧,所以可以訪問block外部的局部變量。這些variables就是復(fù)制到結(jié)構(gòu)體中的外部局部變量或變量的地址犬性。

3.2 block的類型

block有幾種不同的類型瞻离,每種類型都有對應(yīng)的類,上述中isa指針就是指向這個(gè)類乒裆。這里列出常見的三種類型:

  • _NSConcreteGlobalBlock:全局的靜態(tài)block套利,不會訪問任何外部變量,不會涉及到任何拷貝缸兔,比如一個(gè)空的block日裙。例如:
#include <stdio.h>

int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
} 
  • _NSConcreteStackBlock:保存在棧中的block,當(dāng)函數(shù)返回時(shí)被銷毀惰蜜。例如:
#include <stdio.h>

int main()
{
    char a = 'A';
    ^{ printf("%c\n",a); } ();
    return 0;
}
  • _NSConcreteMallocBlock:保存在堆中的block昂拂,當(dāng)引用計(jì)數(shù)為0時(shí)被銷毀。該類型的block都是由_NSConcreteStackBlock類型的block從棧中復(fù)制到堆中形成的抛猖。例如下面代碼中惊橱,在exampleB_addBlockToArray方法中的block還是_NSConcreteStackBlock類型的,在exampleB方法中就被復(fù)制到了堆中引颈,成為_NSConcreteMallocBlock類型的block:
void exampleB_addBlockToArray(NSMutableArray *array) {
    char b = 'B';
    [array addObject:^{
            printf("%c\n", b);
    }];
}
 
void exampleB() {
    NSMutableArray *array = [NSMutableArray array];
    exampleB_addBlockToArray(array);
    void (^block)() = [array objectAtIndex:0];
    block();
} 

總結(jié)一下:

  • _NSConcreteGlobalBlock類型的block要么是空block沦童,要么是不訪問任何外部變量的block。它既不在棧中撑教,也不在堆中朝墩,我理解為它可能在內(nèi)存的全局區(qū)。
  • _NSConcreteStackBlock類型的block有閉包行為伟姐,也就是有訪問外部變量收苏,并且該block只且只有有一次執(zhí)行,因?yàn)闂V械目臻g是可重復(fù)使用的愤兵,所以當(dāng)棧中的block執(zhí)行一次之后就被清除出棧了鹿霸,所以無法多次使用。
  • _NSConcreteMallocBlock類型的block有閉包行為秆乳,并且該block需要被多次執(zhí)行懦鼠。當(dāng)需要多次執(zhí)行時(shí),就會把該block從棧中復(fù)制到堆中屹堰,供以多次執(zhí)行肛冶。

3.3 編譯器如何編譯

我們通過一個(gè)簡單的示例來說明:

#import <dispatch/dispatch.h>

typedef void(^BlockA)(void);

__attribute__((noinline))
void runBlockA(BlockA block) {
    block();
}

void doBlockA() {
    BlockA block = ^{
        // Empty block
    };
    runBlockA(block);
}

上面的代碼定義了一個(gè)名為BlockA的block類型,該block在函數(shù)doBlockA中實(shí)現(xiàn)扯键,并將其作為函數(shù)runBlockA的參數(shù)淑趾,最后在函數(shù)doBlockA中調(diào)用函數(shù)runBloackA

注意:如果block的創(chuàng)建和調(diào)用都在一個(gè)函數(shù)里面忧陪,那么優(yōu)化器(optimiser)可能會對代碼做優(yōu)化處理扣泊,從而導(dǎo)致我們看不到編譯器中的一些操作近范,所以用__attribute__((noinline))給函數(shù)runBlockA添加noinline,這樣優(yōu)化器就不會在doBlockA函數(shù)中對runBlockA的調(diào)用做內(nèi)聯(lián)優(yōu)化處理延蟹。

我們來看看編譯器做的工作內(nèi)容:

#import <dispatch/dispatch.h>

__attribute__((noinline))
void runBlockA(struct Block_layout *block) {
    block->invoke();
}

void block_invoke(struct Block_layout *block) {
    // Empty block function
}

void doBlockA() {
    struct Block_descriptor descriptor;
    descriptor->reserved = 0;
    descriptor->size = 20;
    descriptor->copy = NULL;
    descriptor->dispose = NULL;

    struct Block_layout block;
    block->isa = _NSConcreteGlobalBlock;
    block->flags = 1342177280;
    block->reserved = 0;
    block->invoke = block_invoke;
    block->descriptor = descriptor;

    runBlockA(&block);
}

上面的代碼結(jié)合block的數(shù)據(jù)結(jié)構(gòu)定義评矩,我們能很容易得理解編譯器內(nèi)部對block的工作內(nèi)容。

3.4 copy()和dispose()

上文中提到阱飘,如果我們想要在以后繼續(xù)使用某個(gè)block斥杜,就必須要對該block進(jìn)行拷貝操作,即從椓ば伲空間復(fù)制到堆空間蔗喂。所以拷貝操作就需要調(diào)用Block_copy()函數(shù),block的descriptor中有一個(gè)copy()輔助函數(shù)高帖,該函數(shù)在Block_copy()中執(zhí)行缰儿,用于當(dāng)block需要拷貝對象的時(shí)候,拷貝輔助函數(shù)會retain住已經(jīng)拷貝的對象散址。

既然有有copy那么就應(yīng)該有release乖阵,與Block_copy()對應(yīng)的函數(shù)是Block_release(),它的作用不言而喻预麸,就是釋放我們不需要再使用的block瞪浸,block的descriptor中有一個(gè)dispose()輔助函數(shù),該函數(shù)在Block_release()中執(zhí)行吏祸,負(fù)責(zé)做和copy()輔助函數(shù)相反的操作对蒲,例如釋放掉所有在block中拷貝的變量等。

4.總結(jié)

以上內(nèi)容是我學(xué)習(xí)各大師的文章后對自己學(xué)習(xí)情況的一個(gè)記錄贡翘,其中有部分文字和代碼示例是來自大師的文章蹈矮,還有一些自己的理解,如有錯誤還請大家勘誤床估。

本文首發(fā)地址:Objective-C中的Block

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诱渤,隨后出現(xiàn)的幾起案子丐巫,更是在濱河造成了極大的恐慌,老刑警劉巖勺美,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件递胧,死亡現(xiàn)場離奇詭異,居然都是意外死亡赡茸,警方通過查閱死者的電腦和手機(jī)缎脾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來占卧,“玉大人遗菠,你說我怎么就攤上這事联喘。” “怎么了辙纬?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵豁遭,是天一觀的道長。 經(jīng)常有香客問我贺拣,道長蓖谢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任譬涡,我火速辦了婚禮闪幽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涡匀。我一直安慰自己盯腌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布渊跋。 她就那樣靜靜地躺著腊嗡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拾酝。 梳的紋絲不亂的頭發(fā)上燕少,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機(jī)與錄音蒿囤,去河邊找鬼客们。 笑死,一個(gè)胖子當(dāng)著我的面吹牛材诽,可吹牛的內(nèi)容都是我干的底挫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脸侥,長吁一口氣:“原來是場噩夢啊……” “哼建邓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起睁枕,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤官边,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后外遇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體注簿,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年跳仿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诡渴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菲语,死狀恐怖妄辩,靈堂內(nèi)的尸體忽然破棺而出惑灵,到底是詐尸還是另有隱情,我是刑警寧澤恩袱,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布泣棋,位于F島的核電站,受9級特大地震影響畔塔,放射性物質(zhì)發(fā)生泄漏潭辈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一澈吨、第九天 我趴在偏房一處隱蔽的房頂上張望把敢。 院中可真熱鬧,春花似錦谅辣、人聲如沸修赞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柏副。三九已至,卻和暖如春蚣录,著一層夾襖步出監(jiān)牢的瞬間割择,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工萎河, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荔泳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓虐杯,卻偏偏與公主長得像玛歌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子擎椰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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

  • .相關(guān)概念 在這篇筆記開始之前支子,我們需要對以下概念有所了解。 1.1 操作系統(tǒng)中的棧和堆 注:這里所說的堆和棧與數(shù)...
    狼鳳皇閱讀 483評論 0 0
  • 原文地址:Objective-C中的Block 1.相關(guān)概念 在這篇筆記開始之前达舒,我們需要對以下概念有所了解值朋。 1...
    默默_David閱讀 410評論 0 1
  • Apple從OS X 10.4和iOS 4以后開始支持block,相對于delegate休弃,block有很多便捷之處...
    HK_Hank閱讀 12,462評論 1 46
  • block 是 Objective-C 中的重要技術(shù)吞歼。本文主要是寫了一點(diǎn)對 block 應(yīng)用的探究圈膏。 什么是 Bl...
    Elenion閱讀 581評論 0 51
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C語言的擴(kuò)充功能——“帶有自動變量(即局部...
    SkyMing一C閱讀 2,340評論 6 18