OC中的Block

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

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

我們先來看看一個由C/C++/OBJC編譯的程序占用內(nèi)存分布的結(jié)構(gòu):
內(nèi)存分布
  • 棧區(qū)(stack):由系統(tǒng)自動分配,一般存放函數(shù)參數(shù)值叉谜、局部變量的值等。由編譯器自動創(chuàng)建與釋放踩萎。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧停局,即后進先出、先進后出的原則香府。

    • 例如:在函數(shù)中申明一個局部變量int b, 系統(tǒng)自動在棧中為b開辟空間董栽。
  • 堆區(qū)(heap):一般由程序員申請并指明大小,最終也由程序員釋放企孩。如果程序員不釋放锭碳,程序結(jié)束時可能會由OS回收。對于堆區(qū)的管理是采用鏈表式管理的勿璃,操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表擒抛,當(dāng)接收到程序分配內(nèi)存的申請時,操作系統(tǒng)就會遍歷該鏈表补疑,遍歷到一個記錄的內(nèi)存地址大于申請內(nèi)存的鏈表節(jié)點歧沪,并將該節(jié)點從該鏈表中刪除,然后將該節(jié)點記錄的內(nèi)存地址分配給程序莲组。

    • 例如:在C中malloc函數(shù)
      char *p1;
      p1 = (char *)malloc(10);
      但是p1本身是在棧中的诊胞。
  • 鏈表:是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),一般分為單向鏈表胁编、雙向鏈表厢钧、循環(huán)鏈表。以下為單向鏈表的結(jié)構(gòu)圖:

    單向鏈表是鏈表中最簡單的一種嬉橙,它包含兩個區(qū)域早直,一個信息域和一個指針域。信息域保存或顯示關(guān)于節(jié)點的信息市框,指針域儲存下一個節(jié)點的地址霞扬。
    上述的空閑內(nèi)存地址鏈表的信息域保存的就是空閑內(nèi)存的地址。

  • 全局區(qū)/靜態(tài)區(qū):顧名思義枫振,全局變量和靜態(tài)變量存儲在這個區(qū)域喻圃。只不過初始化的全局變量和靜態(tài)變量存儲在一塊,未初始化的全局變量和靜態(tài)變量存儲在一塊粪滤。程序結(jié)束后由系統(tǒng)釋放斧拍。

  • 文字常量區(qū):這個區(qū)域主要存儲字符串常量。程序結(jié)束后由系統(tǒng)釋放杖小。

  • 程序代碼區(qū):這個區(qū)域主要存放函數(shù)體的二進制代碼肆汹。

下面舉一個前輩寫的例子:

//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ū)昂勉,這個函數(shù)的作用是將"123456" 這串字符串復(fù)制一份放在p1申請的10個字節(jié)的堆區(qū)域中。 
// p3指向的"123456"與這里的"123456"可能會被編譯器優(yōu)化成一個地址扫腺。}

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

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

在C語言中,結(jié)構(gòu)體(struct)指的是一種數(shù)據(jù)結(jié)構(gòu)笆环。結(jié)構(gòu)體可以被聲明為變量攒至、指針或數(shù)組等,用以實現(xiàn)較復(fù)雜的數(shù)據(jù)結(jié)構(gòu)躁劣。結(jié)構(gòu)體同時也是一些元素的集合嗓袱,這些元素稱為結(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-listvariable-list這三部分至少要出現(xiàn)兩個圆裕。以下為示例:

// 該結(jié)構(gòu)體擁有3個成員广鳍,整型的a荆几,字符型的b,雙精度型的c
// 并且為該結(jié)構(gòu)體聲明了一個變量s1
// 該結(jié)構(gòu)體沒有標(biāo)明其標(biāo)簽
struct{ 
      int a; 
      char b; 
      double c;
} s1;
// 該結(jié)構(gòu)體擁有同樣的三個成員
// 并且該結(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)存中砚哆,編譯器按照成員列表順序分別為每個結(jié)構(gòu)體成員分配內(nèi)存独撇。如果想確認(rèn)結(jié)構(gòu)體占多少存儲空間,則使用關(guān)鍵字sizeof躁锁,如果想得知結(jié)構(gòu)體的某個特定成員在結(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 );

閉包(Closure)

閉包就是一個函數(shù)战转,或者一個指向函數(shù)的指針搜立,加上這個函數(shù)執(zhí)行的非局部變量。說的通俗一點槐秧,就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量啄踊,甚至可以訪問不同運行上文中的變量。我們用腳本語言來看一下:

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硼控,這是為什么呢刘陶,因為閉包性。

block原型及定義

我們來看看block的原型:

NSString * ( ^ myBlock )( int );

上面的代碼聲明了一個block(^)原型牢撼,名字叫做myBlock匙隔,包含一個int型的參數(shù),返回值為NSString類型的指針浪默。
下面來看看block的定義:

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

上面的代碼中牡直,將一個函數(shù)體賦值給了myBlock變量缀匕,其接收一個名為paramA的參數(shù)纳决,返回一個NSString對象。
注意:一定不要忘記block后面的分號乡小。

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

myBlock(7);

由于block數(shù)據(jù)類型的語法會降低整個代碼的閱讀性,所以常使用typedef來定義block類型满钟。例如胜榔,下面的代碼創(chuàng)建了GetPersonEducationInfoGetPersonFamilyInfo
兩個新類型,這樣我們就可以在下面的方法中使用更加有語義的數(shù)據(jù)類型湃番。

// Person.h
#import <Foundation/Foundation.h>
// Define a new type for the blocktypedef 
NSString * (^GetPersonEducationInfo)(NSString *);
typedef NSString * (^GetPersonFamilyInfo)(NSString *);
@interface Person : NSObject
- (NSString *)getPersonInfoWithEducation:(GetPersonEducationInfo)educationInfo andFamily:(GetPersonFamilyInfo)familyInfo;
@end

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


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

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

由于Objective-C是強制類型語言夭织,所以作為函數(shù)參數(shù)的block也必須要指定返回值的類型,以及相關(guān)參數(shù)類型吠撮。

閉包性

上文說過尊惰,block實際是Objc對閉包的實現(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ù)中聲明了一個整型泥兰,并賦值42弄屡,另外還聲明了一個block,該block會將42返回鞋诗。然后將block傳遞給logBlock函數(shù)膀捷,該函數(shù)會顯示出返回的值42。即使是在函數(shù)logBlock中執(zhí)行block削彬,而block又聲明在main函數(shù)中全庸,但是block仍然可以訪問到x變量,并將這個值返回融痛。
注意:block同樣可以訪問全局變量糕篇,即使是static

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

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

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

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

編譯器會報錯氓英,表明在block中變量x是只讀的。有時候確實需要在block中處理變量鹦筹,怎么辦铝阐?別著急,我們可以用__block關(guān)鍵字來聲明變量铐拐,這樣就可以在block中修改變量了徘键。基于之前的代碼遍蟋,給x變量添加__block關(guān)鍵字吹害,如下:

__block int x;

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

編譯器中的block

block的數(shù)據(jù)結(jié)構(gòu)定義我們通過大師文章中的一張圖來說明:
C

上圖這個結(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引用計數(shù)奢人、判斷block是否需要執(zhí)行輔助函數(shù)等谓媒。
  • reserved:保留變量,我的理解是表示block內(nèi)部的變量數(shù)何乎。
  • invoke:函數(shù)指針句惯,指向具體的block實現(xiàn)的函數(shù)調(diào)用地址。
  • descriptor:block的附加描述信息宪赶,比如保留變量數(shù)宗弯、block的大小、進行copy或dispose的輔助函數(shù)指針搂妻。
  • variables:因為block有閉包性蒙保,所以可以訪問block外部的局部變量。這些variables就是復(fù)制到結(jié)構(gòu)體中的外部局部變量或變量的地址欲主。

block的類型

block有幾種不同的類型邓厕,每種類型都有對應(yīng)的類,上述中isa
指針就是指向這個類扁瓢。這里列出常見的三種類型:
_NSConcreteGlobalBlock:全局的靜態(tài)block详恼,不會訪問任何外部變量,不會涉及到任何拷貝引几,比如一個空的block昧互。例如:

#include <stdio.h>
int main(){ 
      ^{ printf("Hello, World!\\n"); } (); 
      return 0;
}

_NSConcreteStackBlock:保存在棧中的block,當(dāng)函數(shù)返回時被銷毀。例如:

#include <stdio.h>
int main(){
    char a = 'A'; 
        ^{ printf("%c\\n",a); } (); 
      return 0;
}

_NSConcreteMallocBlock:保存在堆中的block敞掘,當(dāng)引用計數(shù)為0時被銷毀挨务。該類型的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í)行相叁,因為棧中的空間是可重復(fù)使用的遵绰,所以當(dāng)棧中的block執(zhí)行一次之后就被清除出棧了辽幌,所以無法多次使用。
_NSConcreteMallocBlock類型的block有閉包行為椿访,并且該block需要被多次執(zhí)行乌企。當(dāng)需要多次執(zhí)行時,就會把該block從棧中復(fù)制到堆中成玫,供以多次執(zhí)行加酵。

編譯器如何編譯

我們通過一個簡單的示例來說明:

#import <dispatch/dispatch.h>
typedef void(^BlockA)(void);
__attribute__((noinline))
void runBlockA(BlockA block) { 
      block();
}
void doBlockA() { 
      BlockA block = ^{ 
        // Empty block 
      }; 
      runBlockA(block)
;}

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

注意:如果block的創(chuàng)建和調(diào)用都在一個函數(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)容肛响。

copy()和dispose()

上文中提到岭粤,如果我們想要在以后繼續(xù)使用某個block,就必須要對該block進行拷貝操作特笋,即從椞杲剑空間復(fù)制到堆空間。所以拷貝操作就需要調(diào)用Block_copy()函數(shù),block的descriptor中有一個copy()輔助函數(shù)虎囚,該函數(shù)在Block_copy()中執(zhí)行臼寄,用于當(dāng)block需要拷貝對象的時候,拷貝輔助函數(shù)會retain住已經(jīng)拷貝的對象溜宽。

既然有有copy那么就應(yīng)該有release吉拳,與Block_copy()對應(yīng)的函數(shù)是Block_release(),它的作用不言而喻适揉,就是釋放我們不需要再使用的block留攒,block的descriptor中有一個dispose()輔助函數(shù),該函數(shù)在Block_release()中執(zhí)行嫉嘀,負(fù)責(zé)做和copy()輔助函數(shù)相反的操作炼邀,例如釋放掉所有在block中拷貝的變量等。

總結(jié)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杰标,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子彩匕,更是在濱河造成了極大的恐慌腔剂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驼仪,死亡現(xiàn)場離奇詭異掸犬,居然都是意外死亡,警方通過查閱死者的電腦和手機绪爸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門湾碎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人奠货,你說我怎么就攤上這事介褥。” “怎么了仇味?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵呻顽,是天一觀的道長。 經(jīng)常有香客問我丹墨,道長廊遍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任贩挣,我火速辦了婚禮喉前,結(jié)果婚禮上没酣,老公的妹妹穿的比我還像新娘。我一直安慰自己卵迂,他們只是感情好裕便,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著见咒,像睡著了一般偿衰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上改览,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天下翎,我揣著相機與錄音,去河邊找鬼宝当。 笑死视事,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庆揩。 我是一名探鬼主播俐东,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼订晌!你這毒婦竟也來了虏辫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腾仅,失蹤者是張志新(化名)和其女友劉穎乒裆,沒想到半個月后套利,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體推励,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年肉迫,在試婚紗的時候發(fā)現(xiàn)自己被綠了验辞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡喊衫,死狀恐怖跌造,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情族购,我是刑警寧澤壳贪,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站寝杖,受9級特大地震影響违施,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瑟幕,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一磕蒲、第九天 我趴在偏房一處隱蔽的房頂上張望留潦。 院中可真熱鬧,春花似錦辣往、人聲如沸兔院。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坊萝。三九已至,卻和暖如春许起,著一層夾襖步出監(jiān)牢的瞬間屹堰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工街氢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扯键,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓珊肃,卻偏偏與公主長得像荣刑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伦乔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

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

  • 原文地址:Objective-C中的Block 1.相關(guān)概念 在這篇筆記開始之前厉亏,我們需要對以下概念有所了解。 1...
    默默_David閱讀 410評論 0 1
  • .相關(guān)概念 在這篇筆記開始之前烈和,我們需要對以下概念有所了解爱只。 1.1 操作系統(tǒng)中的棧和堆 注:這里所說的堆和棧與數(shù)...
    狼鳳皇閱讀 483評論 0 0
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block招刹、GCD恬试,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,816評論 10 69
  • 1.相關(guān)概念 在這篇筆記開始之前,我們需要對以下概念有所了解疯暑。 1.1 操作系統(tǒng)中的棧和堆 注:這里所說的堆和棧與...
    DevTalking閱讀 3,731評論 3 76
  • 很多人說要活出不一樣的自己训柴,人生不止眼前的茍且,還有詩和遠方妇拯;世界那么大幻馁,我要來一場說走就走的旅行。有人真的這么干...
    夜合蘇閱讀 123評論 0 0