iOS Block 最全解答


什么是Block

Block 又稱為“塊” 或 “代碼塊”,作用是用來保存代碼,保存在其內部的代碼塊 如果Block不被調用 這段代碼就不會執行

在OC中Block的基本格式是這樣的

返回值類型  (^block名)  (參數類型 和 數量) = ^(形參 和 數量){   
    //code 
};

Block的本質

Block的本質上也是一個OC對象 它內部也有個isa指針

Block是封裝了函數調用以及函數調用環境(比如參數和返回值)的OC對象

Block是封裝了函數及其上下文的OC對象

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void (^block)(void) = ^{
            NSLog(@"Hello, World!");
        };

       block();
    }
    return 0;
}

如果我們定義了上面的一個最簡單的Block,那么我們查看底層代碼會是什么樣的呢?

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __block_impl {
    void *isa;//Block的類型
    int Flags;
    int Reserved;
    void *FuncPtr; //封裝代碼塊的地址
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 構造函數(類似於OC的init方法),返回結構體對象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到 上面就是Block的本質了 就是__block_impl 包含了isa指針和代碼塊的執行地址  __main_block_desc_0 包含了block的大小。和一個相關Block的構造函數

下面就是main函數中編譯過的底層代碼

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        // 定義block變量
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA
                                                   );

        // 執行block內部的代碼
        block->FuncPtr(block);
    }
    return 0;
}

通過main函數 我們可以看到定義了一個block變量 並且執行了它。

局部變量和Block

那么如果我們的block需要一個參數呢 如下的Block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        block = ^{
            // age的值捕獲進來(capture)
            NSLog(@"age is %d, ", age);
        };
        age = 20;
        block();
    }
    return 0;              
}

那么clang編譯后的底層代碼是怎么樣的呢?

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到在block內部多了一個成員變量_age 並且在構造此Block的時候 會把傳進去的age直接賦值給_age即_age = age; 即block內部的_age = 10;初始化完成后 里面的age和外面的age沒有關系了,所以外面的無論怎么改變,都不影響里面的。所以會打印10.

在我們OC中 局部變量可以分為自動變量 關鍵字為auto(值傳遞 可以省略 默認的 離開自己的作用域就會被銷毀) 和 靜態變量 關鍵字 static(指針傳遞)

auto變量 會被捕獲到block內部 而且傳遞方式為值傳遞 相當於Block在捕獲這個變量的時候 直接傳遞的這個變量的值 所以這種變量被捕獲后 在Block外部改變 不會影響Block內部的執行 就是上面的那種情況,如果我們再添加一個static局部變量呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // auto:自動變量,離開作用域就銷毀
        auto int age = 10;
        static int height = 10;

        void (^block)(void) = ^{
            // age的值捕獲進來(capture)
            NSLog(@"age is %d, height is %d", age, height); // 10  20
        };

        age = 20;
        height = 20;

        block();

    }
    return 0;
}

我們編譯后的結果是:

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  int *height;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//定義的時候
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
//執行的時候
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
    }

 

可以看到我們Block內部有一個age變量 和 *height的成員變量 定義的時候 age傳的是值 而height傳遞的是指針地址,所以執行的時候age10 height20

在局部變量中 auto局部變量出了作用域就會銷毀 為了避免執行block時 變量不存在 所以auto變量在Block內部被直接賦值為當前值。而static變量一直存在於內存中不必有此擔憂,所以存儲的是變量指針,可以隨時取出最新值。

全局變量和Block

int age_ = 10;
static int height_ = 10;
void (^block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d", age_, height_);
        };

        age_ = 20;
        height_ = 20;

        block();
    }
    return 0;
}

上面代碼Block的被編譯后的源碼是什么樣的呢?

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 構造函數(類似於OC的init方法),返回結構體對象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//調用函數的時候
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age_, height_);
    }

可以看到如果是全局變量 Block使用時不用捕獲 直接訪問 所以得到的也全部都是最新值。

局部變量之所以需要捕獲 因為我們是跨函數使用的 聲明和使用不是在一個作用域內

值得注意的是self這個變量也是局部變量 也會被block捕獲,因為在OC中所有的方法編譯后都是自帶兩個參數 一個是實例對象 一個是SEL _cmd

比如

- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}

MJPerson類中的這個方法 會被編譯成

static instancetype _I_MJPerson_initWithName_(MJPerson * self, SEL _cmd, NSString *name) {
    if (self = ((MJPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJPerson"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
    }
    return self;
}

可以看到有兩個默認的參數MJPerson * self, SEL _cmd 在OC中參數也為局部變量,所以Block中如果用到self,self也會被捕獲。且Block內部的self屬性 指向我們傳進去的self

所以 我們也能理解為什么會造成循環引用了吧。

Block的類型

void (^block)(void) = ^{
        NSLog(@"Hello");
    };
    
    NSLog(@"%@", [block class]);
    NSLog(@"%@", [[block class] superclass]);
    NSLog(@"%@", [[[block class] superclass] superclass]);
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
2020-11-09 22:24:30.042560+0800 Interview01-Block的本質[1489:30598] __NSGlobalBlock__
2020-11-09 22:24:30.043005+0800 Interview01-Block的本質[1489:30598] __NSGlobalBlock
2020-11-09 22:24:30.043103+0800 Interview01-Block的本質[1489:30598] NSBlock
2020-11-09 22:24:30.043163+0800 Interview01-Block的本質[1489:30598] NSObject

很神奇 block的最終的父類竟然是NSObject 也從側面說明了Block的本質是一個對象

其實Block分為三種類型

__NSGlobalBlock__(_NSConcreteGlobalBlock)(存放在數據區) (不訪問auto變量的block 即便是訪問了static局部變量 或者全局變量)

__NSStackBlock__(_NSConcreteStackBlock)(存放在棧區 系統管理內存)(訪問了auto變量)

__NSMallocBlock__(_NSConcreteMallocBlock) (存放在堆區 程序員管理內存) (__NSStackBlock__調用了copy)

應用程序的內存分配:

程序區域 .text區(代碼段)  數據區域(.data區)【一般存放全局變量】   堆區【alloc出來的對象 動態分配內存 需要程序員申請內存 也需要程序員管理內存】 棧區【存放局部變量 系統自動分配內存 自動銷毀內存】

其中_NSConcreteGlobalBlock 存放在數據區域   _NSConcreteStackBlock 存放在棧區  _NSConcreteMallocBlock存放在堆區

如果我們的訪問了auto的Block 不使用copy進行修飾的話 也就是單純的使用__NSStaticBlock__會出現什么問題呢?

#import <Foundation/Foundation.h>
#import "MJPerson.h"

void (^block)(void);
void test2()
{
    
    // NSStackBlock
    int age = 10;
    block = ^{
        NSLog(@"block---------%d", age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test2();
        block();
    }
    return 0;
}

上面就是直接使用棧Block 當我們執行了test2()這個函數后 由於是系統自動管理內存 這個Block會被釋放 那么他內部的__block_impl impl,__test_block_desc_0  捕獲的變量都會被釋放 我們在執行block的時候 會發現age是亂碼。

這也是為什么我們使用copy讓棧Block變成堆Block的原因,變成堆Block后捕獲的變量也會存儲在堆區,只要我們不釋放,就不會消失。雖然增加了我們管理內存的難度,但是也避免了調用時,數據釋放的情況。

Block的copy

_NSConcreteStackBlock copy 變成 _NSConcreteMallocBlock

_NSConcreteGlobalBlock copy 還是全局block 什么也不做

_NSConcreteMallocBlock copy 引用計數加1

在ARC環境下 編譯器會根據情況自動將棧上的Block復制到堆上 相當於對棧上的Block進行copy操作。

1.Blcok 作為函數返回值的時候 會自動copy

2.將Block賦值給__strong強指針的時候 也會自動做copy操作

3.Block作為GCD的參數時 也會被copy到堆上

4.Foundation框架下 block作為參數且方法名含有usingBlock時 會被自動copy

Block捕捉對象的auto變量

比如 我們有一個Person類,其有一個屬性age, 我們在Block里訪問了這個age屬性 如果在ARC的情況下 這個Block會被拷貝到堆上 而且Block會對這個對象有一個強引用 即Block不被釋放Person對象也釋放不了 在MRC的情況下由於Block不會拷貝到堆上 Block還是一個棧Block 所以不會對Person對象有retain操作,Person可先於Block釋放。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            MJPerson *person = [[MJPerson alloc] init];
            person.age = 10;
            
//            __weak MJPerson *weakPerson = person;
            int age = 10;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
        }
        
        NSLog(@"------");
    }
    return 0;
}

我們一起 看看這個Block的結構吧

//Block 底層結構
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__strong person; //捕獲的對象 可以看出是一個強引用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//可以看到相比於捕獲普通變量 捕獲對象的變量多了兩個函數 copy函數和dispose函數
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

//copy函數實現 這個函數會根據外面的指針類型判斷是做強引用還是弱引用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
//dispose函數實現 執行這個函數會斷開block對捕獲對象的引用關系
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

棧Block在copy到堆上的時候 會調用copy函數 copy函數會調用_Block_object_assign這個函數 這個函數會根據要捕獲變量的指針類型 來確定是對捕獲變量是強引用還是弱引用。

堆上的Block銷毀的時候會調用dispose函數 dispose函數執行后 會斷開對捕獲變量的引用關系 也就是捕獲對象變量Block內部多出來的這兩個函數是用來管理內存的。下面我們會降到__block修飾的對象 他們被封裝成特定對象的時候相較於普通變量也會多出來這兩個函數

總結來說 棧Block不會對對象有強引用或者retain操作 而堆上的Block會對對象進行強引用或者retain操作。

總結:

只要Block在棧上 不管是ARC 還是 MRC 都不會對捕獲的auto變量進行強引用或者retain操作

如果Block被拷貝到堆上:

1.會調用block內部的copy函數 在block

2.copy函數內部調用_Block_object_assign函數 這個函數會根據auto變量的修飾符(__strong __weak)作出相應的操作 是強引用還是弱引用

如果Block從堆上移除 會調用block內部的dispose函數 dispose函數內部會調用_Block_object_dispose函數 這個函數會自動斷開引用的auto變量(斷開這個引用) 相當於release

block內部的copy函數和dispose函數 只會在捕獲對象auto變量的時候才有(因為對象需要內存管理) 捕獲簡單的數據變量比如Int的時候 是沒有的

Block修改變量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        block = ^{
            // age = 20;
            NSLog(@"age is %d, ", age);
        };
        block();
    }
    return 0;              
}

我相信大家都知道age = 20 這行代碼是報錯的,也就是說在Block內部不能修改這個age變量 那么是為什么呢?我們可以從本質上入手

//block
struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int age;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//執行的時候
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}

//main函數
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        int age = 10;
        // 定義block變量
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA
                                                   ,age);

        // 執行block內部的代碼
        block->FuncPtr(block);
    }
    return 0;
}

可以看到執行時候main函數中的age根本不在block執行的代碼塊內,也就是在Block方法的作用域中是訪問不到main函數中的age的。main函數和Block的代碼塊是兩個獨立的函數

變量不能互相訪問。Block內部的age和main函數的age地址都不一樣的。不能訪問就不能改嘍。(在另外一個函數中修改另外一個函數中聲明的變量)

而如果我們把變量改成 staic int age = 10 內不就可以修改了,如果你認真的讀了這邊文章,你應該知道static局部變量被捕獲的是指向變量的指針,如果在block內部修改,相當於訪問的是同一片地址,所以可以修改。全局變量也是能改的 因為block不會捕獲全局變量 無論在哪修改 都可以。

但是我們不可能總是使用全局變量或者static修飾的變量 因為他們總是在內存中 不易被銷毀,我們怎么讓auto變量也能在Block內部修改呢,其實我們可以使用__block  來修飾這個變量

而且__block 只能修飾auto變量 那么為什么使用__block修飾后就能改變呢 其實是被修飾的變量被捕獲后 變成了一個對象

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
//main函數中__Block_byref_age_0的實現 可以看到__forwarding 指向的就是自己這個對象的地址 相當於__block int age = 10 被包裝成了這個
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
//main函數中 block的定義
block = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, p, &age, 570425344));
//執行代碼的Block
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
    (age->__forwarding->age) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
    
}

可以看到捕獲的age 變成了一個 __Block_byref_age_0類型的對象 Blcok內部有個指向這個對象的指針 而這個對象擁有age變量(而且這個對象擁有的內存地址和我們聲明的age的內存地址是一樣的) 被執行Block的時候 通過被包裝對象的__forwarding對象 訪問到age 進行修改

 __block的內存管理

當block在棧上的時候 並不會對__block變量產生強引用(ARC)或者retain操作(MRC)

當block被拷貝到堆上時:

1.會調用Block內部copy函數 這個函數內部會調用_Block_object_assign函數

2._Block_object_assign函數會對__block變量形成強引用(retain) (__block修飾的變量也會被拷貝到堆上)

3.只要是被__block修飾的變量 copy后都是強引用  如果沒被__block修飾的對象 會根據對象的修飾符來確定是強引用還是弱引用

當Block從堆中移除的時候:

1.會調用Block內部的dispose函數 這個函數內部會調用_Block_object_dispose函數

2._Block_object_dispose 這個函數會斷開對__block修飾的變量的強引用(realse)

block內部使用了被__block修飾的對象的話 那么block內部的 Desc 描述指針也會多兩個函數 這兩個函數就是用於內存管理的copy函數和dispose函數

__block修飾對象類型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block MJPerson *person = [[MJPerson alloc] init];
        
        MJBlock block = ^{
            NSLog(@"%p", person);
        };
        block();
    }
    return 0;
}

底層代碼

 

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // Block內部對轉化后的person對象就是強引用和外部修飾符無關
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


struct __Block_byref_person_0 {
  void *__isa; // 8
__Block_byref_person_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__strong person; //指向我們外部的person 強引用還是弱引用 取決於 外部person的修飾符
};

 

可以看到和__block修飾的普通變量差不多 但是包裝成的對象多了兩個函數 copy函數和dispose函數 這兩個和Block描述中的copy函數和dispose函數還不一樣,當Block拷貝的到堆上的時候 被__block修飾的對象(person)轉化的對象(__Block_byref_person_0)也會被拷貝到堆上,這個時候會觸發被包裝對象的copy函數 而這個函數會根據外部__block修飾對象(person)的引用計數修飾符來決定對外部對象(person)是強引用還是弱引用。當Block從堆中移除的時候 會調用內部的dispose函數 這個函數會斷開對轉化對象(__Block_byref_person_0)的引用關系 如果計數為0 就直接釋放 釋放后會調用自己的dispose函數 對自己內部對象(MJPerson) 斷開引用關系 如果此時MjPerson的引用計數為0 就會被釋放掉。(轉化對象持有的person和我們外部聲明的person對象有同一片內存地址 是同一個對象)

注意:上面的分析 都是ARC的情況下。如果是MRC轉化對象(__Block_byref_person_0) 對外部對象(person) 一直都是弱引用 不管是什么情況。這也是為什么在MRC下我們使用__block來解決循環引用的原因

Block與循環引用

循環引用的原因就是互相引用 導致雙方都不能釋放 造成內存泄漏 block內部使用實例對象 如果實例對象的修飾符是強引用 那么Block在拷貝到堆上時內部對實例對象也會產生一個強引用 如果這個實例對象再持有了這個block 那么一定會產生循環引用的。

解決循環引用

1.在ARC的情況下 我們常常使用__weak __unsafe__unretained來解決

我們使用__weak 和 __unsafe__unretained 都有一個共同的作用 就是使Block持有的對象指向我們聲明的對象的指針式弱引用,那么我們外部指向實例對象的指針一旦被釋放 實例對象就會被釋放 那么Block就會被釋放 從而解決循環引用。區別就是如果我們使用的是__weak 一旦我們的實例對象被釋放,block內部持有的指向我們實例對象的指針會被指nil 不會產生野指針,但是__unsafe__unretained 不會有這一步操作 所有如果使用__unsafe__unretained block內部持有的指向我們實例對象的指針會還會指向我們已經釋放的實例對象的地址 產生野指針。這也是它不安全的原因。所以我們一般使用__weak來解決循環引用

2.在MRC的情況下 我們使用__unsafe__unretained __block來解決 因為不支持weak指針

這兩個都可以使Block內部的指向我們實例對象的指針進行retain操作

 

 

1.Block的原理是怎樣的?本質是什么?

Block是封裝了函數調用以及函數調用環境的OC對象 本質上也是一個OC對象

Block內部包含了一個__block_imp1 這個指針內部包含了一個isa指針(也證明了本質是個對象) 和 block內部封裝的代碼塊的執行地址 以及捕獲的變量的指針

和一個__main_block_desc_0 里面包含了block的大小等 如果內部使用了實例對象或者__block修飾的變量 會包含一個copy函數和dispose函數.

2.__block的作用是什么?有什么使用注意點?

被__block修飾的變量 在block內部可以被改變,因為在block內部被__block修飾的變量會被包裝成一個__Block_byref_xxx_0的對象,這個對象內部有一個指向我們聲明的變量的指針 指向的地址和我們聲明的變量是同一個地址,修改的時候也是通過這個對象拿到指針做修改。block內部對這個對象是強引用的。而如果被修飾的變量是實例對象 那么這個對象內部會添加一個copy函數和dispose函數,在被copy到堆上時,這個對象的copy函數會根據被修飾實例對象的關鍵字來確定對實例對象是強引用還是弱引用。再block釋放的時候,會調用dispose函數 來斷開對實例對象的引用關系。需要注意的是__block只能修飾實例對象 還有一些內存管理問題 比如說修飾實例對象且該實例對象擁有該Block 會造成循環引用。而且在MRC下使用__block修飾的實例對象 不會被__Block_byref_xxx_0的對象retain操作。對象可能會被提前釋放。

3.Block的屬性修飾詞為什么是copy? 使用Block有哪些注意?

如果block不被copy 就是存儲在棧空間上,我們就控制不了block的生命周期,可能我們使用的時候已經被釋放了或者我們使用的時候 其內部捕獲的變量已經釋放了 導致程序錯誤。而拷貝到堆上,我們可以方便的控制其生命周期。雖然增加了管理內存的一些成本。但是可以減少錯誤。在ARC的情況下 如果有一個強引用指向Block 內部也會copy到堆上。使用strong也行。但是我們習慣使用copy。 需要注意的是不能引起循環引用。我們可以使用__weak來避免這個問題

4.Block修改NSMutableArray 需不需要添加__block?

不需要,如果是對數組進行增刪改查 我們不需要對其添加__block 因為不是改變數組的指針指向 只是操作數組。添加__block反而會增加開銷。

 

在使用clang 轉換OC為C++時 可能會遇到以下問題

cannot create __weak reference in file using manual reference

解決方法

xcrun -sdk iphones clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM