你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 周全解析Objective-C中的block代碼塊的應用

周全解析Objective-C中的block代碼塊的應用

編輯:IOS開發綜合

1.相干概念

在這篇筆記開端之前,我們須要對以下概念有所懂得。

1.1 操作體系中的棧和堆

注:這裡所說的堆和棧與數據構造中的堆和棧不是一回事。

我們先來看看一個由C/C++/OBJC編譯的法式占用內存散布的構造:

201511492644909.jpg (844×367)

棧區(stack):由體系主動分派,普通寄存函數參數值、部分變量的值等。由編譯器主動創立與釋放。其操作方法相似於數據構造中的棧,即落後先出、先輩後出的准繩。

例如:在函數中聲名一個部分變量int b;體系主動在棧中為b開拓空間。

堆區(heap):普通由法式員請求並指明年夜小,終究也由法式員釋放。假如法式員不釋放,法式停止時能夠會由OS收受接管。關於堆區的治理是采取鏈表式治理的,操作體系有一個記載余暇內存地址的鏈表,當吸收到法式分派內存的請求時,操作體系就會遍歷該鏈表,遍歷到一個記載的內存地址年夜於請求內存的鏈表節點,並將該節點從該鏈表中刪除,然後將該節點記載的內存地址分派給法式。

例如:在C中malloc函數

char p1;
p1 = (char )malloc(10);

然則p1自己是在棧中的。

鏈表:是一種罕見的基本數據構造,普通分為單向鏈表、雙向鏈表、輪回鏈表。以下為單向鏈表的構造圖:

201511492710858.jpg (428×73)

單向鏈表是鏈表中最簡略的一種,它包括兩個區域,一個信息域和一個指針域。信息域保留或顯示關於節點的信息,指針域貯存下一個節點的地址。

上述的余暇內存地址鏈表的信息域保留的就是余暇內存的地址。

全局區/靜態區:望文生義,全局變量和靜態變量存儲在這個區域。只不外初始化的全局變量和靜態變量存儲在一塊,未初始化的全局變量和靜態變量存儲在一塊。法式停止後由體系釋放。

文字常量區:這個區域重要存儲字符串常量。法式停止後由體系釋放。

法式代碼區:這個區域重要寄存函數體的二進制代碼。

上面舉一個先輩寫的例子:

//main.cpp
int a = 0; // 全局初始化區
char *p1; // 全局未初始化區
main {
    int b; // 棧
    char s[] = "abc"; // 棧
    char *p2; // 棧
    char *p3 = "123456"; // 123456\0在常量區,p3在棧上
    static int c =0; // 全局靜態初始化區
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20); // 分派得來的10和20字節的區域就在堆區
    strcpy(p1, "123456"); // 123456\0在常量區,這個函數的感化是將"123456" 這串字符串復制一份放在p1請求的10個字節的堆區域中。
    // p3指向的"123456"與這裡的"123456"能夠會被編譯器優化成一個地址。
}

strcpy函數

原型聲明:extern char *strcpy(char* dest, const char *src);

功效:把從src地址開端且含有NULL停止符的字符串復制到以dest開端的地址空間。

1.2 構造體(Struct)

在C說話中,構造體(struct)指的是一種數據構造。構造體可以被聲明為變量、指針或數組等,用以完成較龐雜的數據構造。構造體同時也是一些元素的聚集,這些元素稱為構造體的成員(member),且這些成員可認為分歧的類型,成員普通用名字拜訪。

我們來看看構造體的界說:

struct tag { member-list } variable-list;

  • struct:構造體症結字。
  • tag:構造體標簽。
  • member-list:構造體成員列表。
  • variable-list:為構造體聲明的變量列表。

在普通情形下,tag,member-list,variable-list這三部門至多要湧現兩個。以下為示例:

// 該構造體具有3個成員,整型的a,字符型的b,雙精度型的c
// 而且為該構造體聲清楚明了一個變量s1
// 該構造體沒有標明其標簽
struct{
    int a;
    char b;
    double c;
} s1;
// 該構造體具有異樣的三個成員
// 而且該構造體標清楚明了標簽EXAMPLE
// 該構造體沒有聲明變量
struct EXAMPLE{
    int a;
    char b;
    double c;
};
//用EXAMPLE標簽的構造體,別的聲清楚明了變量t1、t2、t3
struct EXAMPLE t1, t2[20], *t3;

以上就是簡略構造體的代碼示例。構造體的成員可以包括其他構造體,也能夠包括指向本身構造體類型的指針。構造體的變量也能夠是指針。

上面我們來看看構造體成員的拜訪。構造體成員根據構造體變量類型的分歧,普通有2種拜訪方法,一種為直接拜訪,一種為直接拜訪。直接拜訪運用於通俗的構造體變量,直接拜訪運用於指向構造體變量的指針。直接拜訪應用構造體變量名.成員名,直接拜訪應用(*構造體指針名).成員名或許應用構造體指針名->成員名。雷同的成員稱號依附分歧的變量前綴辨別。

struct EXAMPLE{
    int a;
    char b;
};
//聲明構造體變量s1和指向構造體變量的指針s2
struct EXAMPLE s1, *s2;
//給變量s1和s2的成員賦值,留意s1.a和s2->a其實不是統一成員
s1.a = 5;
s1.b = 6;
s2->a = 3;
s2->b = 4;

最初我們來看看構造體成員存儲。在內存中,編譯器依照成員列表次序分離為每一個構造體成員分派內存。假如想確認構造體占若干存儲空間,則應用症結字sizeof,假如想得知構造體的某個特定成員在構造體的地位,則應用offsetof宏(界說於stddef.h)。

struct EXAMPLE{
    int a;
    char b;
};
//取得EXAMPLE類型構造體所占內存年夜小
int size_example = sizeof( struct EXAMPLE );
//取得成員b絕對於EXAMPLE貯存地址的偏移量
int offset_b = offsetof( struct EXAMPLE, b );

1.3 閉包(Closure)

閉包就是一個函數,或許一個指向函數的指針,加上這個函數履行的非部分變量。

說的淺顯一點,就是閉包許可一個函數拜訪聲明該函數運轉高低文中的變量,乃至可以拜訪分歧運轉上文中的變量。

我們用劇本說話來看一下:

function funA(callback){
    alert(callback());
}
function funB(){
    var str = "Hello World"; // 函數funB的部分變量,函數funA的非部分變量
    funA(
        function(){
            return str;
        }
    );
}

經由過程下面的代碼我們可以看出,按慣例思想來講,變量str是函數funB的部分變量,感化域只在函數funB中,函數funA是沒法拜訪到str的。然則上述代碼示例中函數funA中的callback可以拜訪到str,這是為何呢,由於閉包性。

2.blcok基本常識

block現實上就是Objective-C說話對閉包的完成。

2.1 block的原型及界說

我們來看看block的原型:

NSString * ( ^ myBlock )( int );

下面的代碼聲清楚明了一個block(^)原型,名字叫做myBlock,包括一個int型的參數,前往值為NSString類型的指針。

上面來看看block的界說:

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

下面的代碼中,將一個函數體賦值給了myBlock變量,其吸收一個名為paramA的參數,前往一個NSString對象。

留意:必定不要忘卻block前面的分號。

界說好block後,便可以像應用尺度函數一樣應用它了:

myBlock(7);

因為block數據類型的語法會下降全部代碼的浏覽性,所以常應用typedef來界說block類型。例如,上面的代碼創立了GetPersonEducationInfo和GetPersonFamilyInfo兩個新類型,如許我們便可以鄙人面的辦法中應用加倍有語義的數據類型。

// Person.h
#import // 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

我們用一張年夜師文章裡的圖來總結一下block的構造:

https://www.ios5.online/ios/UploadFiles_8070/201703/2017031615451724.png (1140×420)

2.2 將block作為參數傳遞

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

因為Objective-C是強迫類型說話,所以作為函數參數的block也必需要指定前往值的類型,和相干參數類型。

2.3 閉包性

上文說過,block現實是Objc對閉包的完成。

我們來看看上面代碼:

#import 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函數中聲清楚明了一個整型,並賦值42,別的還聲清楚明了一個block,該block會將42前往。然後將block傳遞給logBlock函數,該函數會顯示出前往的值42。即便是在函數logBlock中履行block,而block又聲明在main函數中,然則block依然可以拜訪到x變量,並將這個值前往。

留意:block異樣可以拜訪全局變量,即便是static。

2.4 block中變量的復制與修正

關於block外的變量援用,block默許是將其復制到其數據構造中來完成拜訪的,以下圖:

https://www.ios5.online/ios/UploadFiles_8070/201703/2017031615451784.jpg (319×162)

經由過程block停止閉包的變量是const的。也就是說不克不及在block中直接修正這些變量。來看看當block試著增長x的值時,會產生甚麼:

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

編譯器會報錯,注解在block中變量x是只讀的。

有時刻確切須要在block中處置變量,怎樣辦?別焦急,我們可以用__block症結字來聲明變量,如許便可以在block中修正變量了。

基於之前的代碼,給x變量添加__block症結字,以下:

__block int x;

關於用__block潤飾的內部變量援用,block是復制其援用地址來完成拜訪的,以下圖:

https://www.ios5.online/ios/UploadFiles_8070/201703/2017031615451863.jpg (252×143)

3.編譯器中的block

3.1 block的數據構造界說

我們經由過程年夜師文章中的一張圖來講明:

https://www.ios5.online/ios/UploadFiles_8070/201703/2017031615451804.jpg (510×511)

上圖這個構造是在棧中的構造,我們來看看對應的構造體界說:

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構造體的界說:

isa指針:指向注解該block類型的類。

flags:按bit位表現一些block的附加信息,好比斷定block類型、斷定block援用計數、斷定block能否須要履行幫助函數等。

reserved:保存變量,我的懂得是表現block外部的變量數。

invoke:函數指針,指向詳細的block完成的函數挪用地址。

descriptor:block的附加描寫信息,好比保存變量數、block的年夜小、停止copy或dispose的幫助函數指針。

variables:由於block有閉包性,所以可以拜訪block內部的部分變量。這些variables就是復制到構造體中的內部部分變量或變量的地址。

3.2 block的類型

block有幾種分歧的類型,每品種型都有對應的類,上述中isa指針就是指向這個類。這裡列出罕見的三品種型:

_NSConcreteGlobalBlock:全局的靜態block,不會拜訪任何內部變量,不會觸及就任何拷貝,好比一個空的block。例如:

#include int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}
_NSConcreteStackBlock:保留在棧中的block,當函數前往時被燒毀。例如:

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

_NSConcreteMallocBlock:保留在堆中的block,當援用計數為0時被燒毀。該類型的block都是由_NSConcreteStackBlock類型的block從棧中復制到堆中構成的。例以下面代碼中,在exampleB_addBlockToArray辦法中的block照樣_NSConcreteStackBlock類型的,在exampleB辦法中就被復制到了堆中,成為_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();
}

總結一下:

  1. _NSConcreteGlobalBlock類型的block要末是空block,要末是不拜訪任何內部變量的block。它既不在棧中,也不在堆中,我懂得為它能夠在內存的全局區。
  2. _NSConcreteStackBlock類型的block有閉包行動,也就是有拜訪內部變量,而且該block只且只要有一次履行,由於棧中的空間是可反復應用的,所以當棧中的block履行一次以後就被消除出棧了,所以沒法屢次應用。
  3. _NSConcreteMallocBlock類型的block有閉包行動,而且該block須要被屢次履行。當須要屢次履行時,就會把該block從棧中復制到堆中,供以屢次履行。
  4. 3.3 編譯器若何編譯

    我們經由過程一個簡略的示例來講明:

    #import typedef void(^BlockA)(void);
    __attribute__((noinline))
    void runBlockA(BlockA block) {
        block();
    }
    void doBlockA() {
        BlockA block = ^{
            // Empty block
        };
        runBlockA(block);
    }

    下面的代碼界說了一個名為BlockA的block類型,該block在函數doBlockA中完成,並將其作為函數runBlockA的參數,最初在函數doBlockA中挪用函數runBloackA。

    留意:假如block的創立和挪用都在一個函數外面,那末優化器(optimiser)能夠會對代碼做優化處置,從而招致我們看不到編譯器中的一些操作,所以用__attribute__((noinline))給函數runBlockA添加noinline,如許優化器就不會在doBlockA函數中對runBlockA的挪用做內聯優化處置。

    我們來看看編譯器做的任務內容:

    #import __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);
    }

    下面的代碼聯合block的數據構造界說,我們能很輕易得懂得編譯器外部對block的任務內容。

    3.4 copy()和dispose()

    上文中提到,假如我們想要在今後持續應用某個block,就必需要對該block停止拷貝操作,即從棧空間復制到堆空間。所以拷貝操作就須要挪用Block_copy()函數,block的descriptor中有一個copy()幫助函數,該函數在Block_copy()中履行,用於當block須要拷貝對象的時刻,拷貝幫助函數會retain住曾經拷貝的對象。

    既然有有copy那末就應當有release,與Block_copy()對應的函數是Block_release(),它的感化不問可知,就是釋放我們不須要再應用的block,block的descriptor中有一個dispose()幫助函數,該函數在Block_release()中履行,擔任做和copy()幫助函數相反的操作,例如釋放失落一切在block中拷貝的變量等。

    4.上面來看幾個詳細的運轉示例:
    4.1參數是NSString*的代碼塊

            void (^printBlock)(NSString *x);
            printBlock = ^(NSString* str)
            {
                NSLog(@"print:%@", str);
            };
            printBlock(@"hello world!");

    運轉成果是:print:hello world!
    4.2代碼用在字符串數組排序

            NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];
            NSComparator sortBlock = ^(id string1, id string2)
            {
                return [string1 compare:string2];
            };
            NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];
            NSLog(@"sortArray:%@", sortArray);

    運轉成果:

    sortArray:(
      "abc 05",
      "abc 1",
      "abc 12",
      "abc 13",
      "abc 21"
    )
    

    4.3代碼塊的遞歸挪用
    代碼塊想要遞歸挪用,代碼塊變量必需是全局變量或許是靜態變量,如許在法式啟動的時刻代碼塊變量就初始化了,可以遞歸挪用

            static void (^ const blocks)(int) = ^(int i)
            {
                if (i > 0) {
                    NSLog(@"num:%d", i);
                    blocks(i - 1);
                }
            };
            blocks(3);

    運轉打印成果:

    num:3
    num:2
    num:1
    

    4.4在代碼塊中應用部分變量和全局變量
    在代碼塊中可使用和轉變全局變量

    int global = 1000;
    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
            void(^block)(void) = ^(void)
            {
                global++;
                NSLog(@"global:%d", global);
            };
            block();
            NSLog(@"global:%d", global);
        }
        return 0;
    }

    運轉打印成果:

    global:1001
    global:1001
    

    而部分變量可使用,然則不克不及轉變。

            int local = 500;
            void(^block)(void) = ^(void)
            {
    //            local++;
                NSLog(@"local:%d", local);
            };
            block();
            NSLog(@"local:%d", local);

    在代碼塊中轉變部分變量編譯欠亨過。怎樣在代碼塊中轉變部分變量呢?在部分變量後面加上症結字:__block

            __block int local = 500;
            void(^block)(void) = ^(void)
            {
                local++;
                NSLog(@"local:%d", local);
            };
            block();
            NSLog(@"local:%d", local);

    運轉成果:

    local:501
         local:501
    
    

    【周全解析Objective-C中的block代碼塊的應用】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved