OC底層-Block本質(6、block內修改變量的值)

如何修改

分析從代碼入手:ios

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        Block block = ^ {
             age = 20; // 沒法修改,會報錯
            NSLog(@"%d",age);
        };
        block();
    }
    return 0;
}


複製代碼

默認狀況下block不能修改外部的局部變量。經過以前對源碼的分析能夠知道,由於變量的做用於都不在同一個函數中,固然不能去想固然的作修改了,那到底怎麼去修改呢,下面會詳細說道。bash

age是在main函數內部聲明的,說明age的內存存在於main函數的棧空間內部,可是block內部的代碼在__main_block_func_0函數內部。__main_block_func_0函數內部沒法訪問age變量的內存空間,兩個函數的棧空間不同,__main_block_func_0內部拿到的age是block結構體內部的age,所以沒法在__main_block_func_0函數內部去修改main函數內部的變量。iphone

使用static修飾修改變量的值

前面文章有提到過static修飾的age變量傳遞到block內部的是指針傳遞,在__main_block_func_0函數內部就能夠拿到age變量的內存地址,所以就能夠在block內部修改age的值。函數

__block修改

__block用於解決block內部不能修改auto變量值的問題.ui

注意:__block不能修飾靜態變量(static) 和全局變量spa

咱們將上面的代碼作個修改,而後運行起來。命令行

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


複製代碼

經過__block能夠修改auto age的值。這是什麼緣由形成的呢?咱們能夠深刻源碼中去找到答案指針

源碼

一樣經過命令行code

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

來生成咱們的main.cpp文件

/// 經過關鍵字__block生成的age結構體
struct __Block_byref_age_0 {
  void *__isa; // isa指針
__Block_byref_age_0 *__forwarding; //  __forwarding指針指向本身
 int __flags; //默認值,暫時不考慮
 int __size; // 變量佔用的空間大小
 int age; // age 變量
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref   變量age生成的結構體
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref __main_block_impl_0 *__cself 去找到__main_block_impl_0 內部age結構體

             (age->__forwarding->age) = 20;  // 經過age結構體找到__forwarding指針,而後經過__forwarding指向本身在找到age變量去賦值20,作修改
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_6af310_mi_0,(age->__forwarding->age));

        }
/// 內部函數, block copy會調用
static void __main_block_copy_0(struct __main_block_impl_0*dst,struct __main_block_impl_0*src){
    _Block_object_assign((void*)&dst->age,
                                                                                     
                         (void*)src->age,
                                                                                      
                         8/*BLOCK_FIELD_IS_BYREF*/
                         );}
///  block 銷燬會調用
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
       
        /// block內部的函數調用
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
複製代碼

上述源碼中能夠發現

首先被__block修飾的age變量聲明變爲名爲age的__Block_byref_age_0結構體,也就是說加上__block修飾的話捕獲到的block內的變量爲__Block_byref_age_0類型的結構體。

__Block_byref_age_0結構體

__isa指針 :__Block_byref_age_0中也有isa指針也就是說__Block_byref_age_0本質也一個對象。

__forwarding :__forwarding是__Block_byref_age_0結構體類型的,而且__forwarding存儲的值爲(__Block_byref_age_0 *)&age,即結構體本身的內存地址。

__flags :0

__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所佔用的內存空間。

age :真正存儲變量的地方,這裏存儲局部變量10。

接着將__Block_byref_age_0結構體age存入__main_block_impl_0結構體中,並賦值給__Block_byref_age_0 *age;

以後調用block,首先取出__main_block_impl_0中的age,經過age結構體拿到__forwarding指針,__forwarding中保存的就是__Block_byref_age_0結構體自己,這裏也就是age(__Block_byref_age_0),在經過__forwarding拿到結構體中的age(10)變量並修改其值。

__forwarding

__forwarding是指向本身的指針。這樣的作法是爲了方便內存管理,後面會有詳細說明。

到此爲止,__block爲何能修改變量的值已經很清晰了。__block將變量包裝成對象,而後在把age封裝在結構體裏面,block內部存儲的變量爲結構體指針,也就能夠經過指針找到內存地址進而修改變量的值。

__block修飾對象類型

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

複製代碼

源碼

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
    
//相比於int age  這對象person多了兩個函數,是用來管理內存管理的
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong person;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_person_0 *person = __cself->person; // bound by ref


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_1,(person->__forwarding->person));
        }
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, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_n2_0nslhwnj04qg5hyxlg2d8ych0000gn_T_main_7476f0_mi_0,(person.__forwarding->person));
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
複製代碼

經過源碼查看,將對象包裝在一個新的結構體中。結構體內部會有一個person對象,不同的地方是結構體內部添加了內存管理的兩個函數__Block_byref_id_object_copy和__Block_byref_id_object_dispose,這兩個函數會在下一章節內存管理中心詳細說明。

疑問

如下代碼是否能夠正確執行

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *array = [NSMutableArray array];
        Block block = ^{
            [array addObject: @"20"];
            [array addObject: @"30"];
            NSLog(@"%@",array);
        };
        block();
    }
    return 0;
}

複製代碼

答:能夠正確執行,由於在block塊中僅僅是使用了array的內存地址,往內存地址中添加內容,並無修改arry的內存地址,所以array不須要使用__block修飾也能夠正確編譯。

所以當僅僅是使用局部變量的內存地址,而不是修改的時候,儘可能不要添加__block,經過上述分析咱們知道一旦添加了__block修飾符,系統會自動建立相應的結構體,佔用沒必要要的內存空間。

相關文章
相關標籤/搜索