ios Block底層詳解、框架結構:捕獲、對象類型、__block、__forwarding、循環引用

1:捕獲

2:block類型

2.1:問題 :mrc環境下  下面講的都是mrc環境下,會真實很多

2.2:在arc下,block 自動加上copy的情況---:返回block。

2.3:在arc下,block 自動加上copy的情況---:強指針__block;

2.4:arc環境下:方法名中有usingBlock的方法參數時,也會進行copy操作。

2.5:在arc下,block 自動加上copy的情況---:block作爲GCD的方法參數。

3:對象類型的auto變量

3.1:假設在mrc環境下

3.2:arc:對上面做一個原因解釋

4:如何修改block內部的變量:__block、static、全局、指針

4.1:__block的內存管理

4.2:__forwarding指針原理

4.3:問題:blok內部會對下面兩個對象有什麼不同麼

4.4:被__block修飾的對象類型:arc環境下

5:循環引用

 

下面以一個簡單的例子開始講起

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

        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

通過 (這裏解釋一下,編譯如果xcode路徑出問題可能導致編譯失敗,結果辦法點擊

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

把main.m -> main.cpp文件。

查看上面block的源碼

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

整理一下就是

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
          
        int age = 10; // 自動變量auto,離開作用域便自動銷燬 c語言特性

        // 定義block變量

        void (* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        // 上面相當於是
        void (* block)(void) = &__main_block_impl_0(
                                                    __main_block_func_0,
                                                    &__main_block_desc_0_DATA
                                                    , age);
        
        age = 20;
       // 執行block內部的代碼
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        // 上面相當於是:
        block->FuncPtr(block);
        
        

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__main_block_impl_0這個函數。


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
    // 構造函數(類似oc的init方法),返回結構體對象 這個也就是block指向的 c++的特性 傳進來的_age會自動賦值給age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 封裝了block執行邏輯的函數:block裏面的函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy 取出block裏面的age

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_main_ff1ca7_mi_0,age);
        }

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;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

1:捕獲

捕獲:block內部會專門新增一個成員來外面變量的值,這個操作稱之爲捕獲。

auto只存在於局部變量裏,不能存在於全局變量中,因爲它出了作用域就會被銷燬。值傳遞,相當於把10傳遞進去。(auto是自動銷燬的,所以必須要傳值。)

static,址傳遞。看下面

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        int age = 10;
        static int height = 10;
        void (^ block)(void) = ^{
            NSLog(@"block:%d  height:%d",age,height);
        };
        age = 20;
        height = 20;
        block();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

所以上面的age捕獲的是值,所以block內部的age跟外面的int age並不是一個,block裏面的age是重新創建的,只是這裏是把int age的值賦值給了block裏面的age。只是因爲這種情況下age無法在block裏面進行賦值操作,所以age是不是改變的就不重要了但是也要注意,這種在block裏面操作取值,長時間的操作,會有不準確性。(這種情況是局部變量age情況下)

上面的static height 跟block裏面的*height是一個,因爲__main_block_impl_0()這個函數傳進去的是height指針,所以,這裏block裏面的*height修改了,同樣外面的值也就修改了。

如果是全局變量


int age = 10;
static int height = 10;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
       
        void (^ block)(void) = ^{
            NSLog(@"block:%d  height:%d",age,height);
        };
        age = 20;
        height = 20;
        block();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

這個打印出來

2018-08-16 22:44:17.640 newxc[16609:5312874] block:20  height:20

源碼

 

因爲是全局變量,所以不論在哪裏都能夠訪問,所以沒有必要捕獲。

(這種情況下,block內部的height和age,就是拿的全局變量的指針進行操作的,這本就是一個數據進行的操作。)

局部變量需要捕獲,是因爲局部變量的作用域只僅限在當前函數作用域,如果block函數和調用不在一個函數作用域,那麼就會跨函數訪問局部變量,所以需要捕獲。

總結如下。

問:如果block內部調用self,會捕獲麼

#import "Person.h"

@implementation Person

- (void)test
{
    void (^ block)(void) = ^{
        NSLog(@"self:%p",self);
    };
    
    block();
    
}
@end

答案會捕獲。

因爲test在c語言中的寫法是

參數就是局部變量,所以會被捕獲。

person的test的c語言函數就是下面這樣,Person * self, SEL _cmd這兩個參數是默認必傳的參數。

static void _I_Person_test(Person * self, SEL _cmd) {
    void (* block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}

// 看這裏還有load方法
static void _C_Person_load(Class self, SEL _cmd) {

}

看一下上面的源碼

// 看這裏,裏面有一個Person *self 參數裏的self就會自動賦值給*self。
struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 這裏是block內部函數,在訪問self。
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_Person_4d5a32_mi_0,self);
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};


// 這裏是test函數 __Person__test_block_impl_0這個函數傳的值是self,上面是__Person__test_block_impl_0函數可以看到函數內部創建了一個person,來保存這個self指針。
static void _I_Person_test(Person * self, SEL _cmd) {

    void (* block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));

// 調用block
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

}



struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

問題:如果是訪問_name呢?


- (void)test
{
    void (^ block)(void) = ^{
        NSLog(@"self:%p",_name);
    };
    
    block();
    
}

答案是會捕獲,這個相當於是self->_age; 這個self是在block的局部的,這個捕獲的是self,然後訪問的self裏面的_age成員變量。並不是單獨對_age進行捕獲,而是對self進行捕獲。看源碼

問題:如果是[self name];

這個也是捕獲的,因爲先要拿到self,才能發消息

這裏需要知道一個問題:self 是局部變量,不是全局變量!!!

 

2:block類型

從下面代碼的isa可以看出block是有類型的

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock; // block的類型
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block就是一個oc對象。我們可以通過[block class] 來查看一下它的類型。

我們來打印一下 這裏是arc環境下的,跟mrc還是不一樣的

int main(int argc, char * argv[]) {
    @autoreleasepool {
        void (^ block)(void) = ^{
        };
        
        block();
        
        NSLog(@"%@", [block class]);
        NSLog(@"%@", [[block class] superclass]);
        NSLog(@"%@", [[[block class] superclass] superclass]);
        NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

結果 :印證了 block是一個oc對象,isa本質上來說是從NSObject對象中來的。

// __NSGlobalBlock__ : __NSGlobalBlock : NSBlock : NSObject

2018-08-17 20:04:39.088 newxc[17439:5526253] __NSGlobalBlock__
2018-08-17 20:04:39.089 newxc[17439:5526253] __NSGlobalBlock
2018-08-17 20:04:39.090 newxc[17439:5526253] NSBlock
2018-08-17 20:04:39.090 newxc[17439:5526253] NSObject

block到底有哪幾種呢

        // Global:沒有訪問auto變量。包括static 都是global
        void (^ block)(void) = ^{
            NSLog(@"self:");
        };
        
        // Stack:訪問了auto變量。 :這個需要把arc關掉。
        int ne = 10;
        void (^ block1)(void) = ^{
            NSLog(@"self:%d",ne);
        };
        
        NSLog(@"%@ %@ %@",[block class], [block1 class],[^{
            NSLog(@"%d",ne);
        } class]);
        
        block();
2018-08-17 20:12:12.046 newxc[17492:5539840] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

但是通過編譯到.cpp文件中查看跟上面的類型不一致。isa都是_NSConcreteStackBlock類型

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};

struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int ne;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _ne, int flags=0) : ne(_ne) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

struct __main_block_impl_2 {
  struct __block_impl impl;
  struct __main_block_desc_2* Desc;
  int ne;
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _ne, int flags=0) : ne(_ne) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

不一致原因:

一切以運行時結果爲準。

有時候通過clang 轉成的c++代碼,有時候並不是真正轉成的c++代碼。(蘋果的llvm1.0不再是生成c++文件,而是生成中間代碼,但是大致上相同,只有少數不同。llvm編譯器的一部分就是clang,就是clang屬於llvm編譯器裏面的一部分)從上到下是高地址到低地址。

2.1:問題 :mrc環境下  下面講的都是mrc環境下,會真實很多

先看一下block的類型

 

看下面的問題

void (^block)(void);

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


int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        test();
        block();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

顯然:這個block的結果會有問題

2018-08-18 20:50:15.110 newxc[18572:6009580] block------272632489

那爲什麼會這樣呢? 是因爲這個block函數在棧上,block在全局變量區,但是這個block代碼塊內的所有包括這個age都是在棧上,棧有一個特點就是隨時可能就會被釋放,所以,這個打印的結果是不確定的。那如何解決呢?把這個block放在堆裏。也即是把這個block變成malloc類型。調用copy。下面這個就是在堆上的就是block。

void test() {
    // stack
    int age = 10;
    block = [^{
        NSLog(@"block------%d",age);
    } copy];
    
}

那如果是global類型調用copy呢 是什麼類型呢

      block = [^{
            //NSLog(@"block------%d",age);
        } copy];
        
        block();
        NSLog(@"block:%@",[block class]);

答案還是global類型

2018-08-18 21:04:59.322 newxc[18618:6028439] block:__NSGlobalBlock__

那上面stack調用兩次copy呢?(調用一次在堆上,再調用一次呢)

 block = [[^{
        NSLog(@"block------%d",age);
    } copy] copy];

答案還是malloc:也即是malloc調用copy還是在堆區,但是並不會再開闢內存了,而是引用計數器+1,但是這種的記住要自己管理內存。

2018-08-18 21:07:08.597 newxc[18641:6032580] block:__NSMallocBlock__

2.2:在arc下,block 自動加上copy的情況---:返回block。


typedef void (^block)(void);

block test() {
    // stack
    int age = 19;
    return ^{
        NSLog(@"block------%d",age);
    };  
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        block bloc = test();
        bloc();
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

因爲test函數內return的block是棧上的block,一旦函數作用域消失,那這個block就消失了,所以不是很妥當。如果是arc環境下,會自動給返回的這個block加了一個copy操作。在你函數作用域消失的時候,會自動給你加上release操作。

2.3:在arc下,block 自動加上copy的情況---:強指針__block;

在arc環境下,只要被強指針持有,就是malloc。就會自動給你這個block加copy。

        int age = 10;
        
        block bloccc = ^{
            NSLog(@"---%d",age);
        };
        NSLog(@"%@",[bloccc class]);
2018-08-19 17:10:03.248 newxc[19403:6283487] __NSMallocBlock__

但是在mrc環境下 就是

2018-08-19 17:12:09.420 newxc[19450:6287484] __NSStackBlock__

2.4:arc環境下:方法名中有usingBlock的方法參數時,也會進行copy操作。

NSArray *array = @[];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];

有usingBlock 這個“^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }”block也會搬到堆上面去。

2.5:在arc下,block 自動加上copy的情況---:block作爲GCD的方法參數。

  static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
            
        });

只要是CGD裏面的block,“ ^{ }”這個block就會自動添加copy操作。

so:小總結

在mrc下一定要用copy,在arc下可以用strong也可以用copy,用strong是因爲有強指針,就會被自動添加copy,但是爲了項目在哪裏都能用,所以建議用copy。

 

3:對象類型的auto變量

arc環境

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        block bloccc = ^{
            NSLog(@"---%d",person.age);
        };
        NSLog(@"-----=====");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

源碼是

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

這裏是auto變量是什麼捕捉的就是什麼。如果是 static Person *person; 那麼源碼中捕捉到的就是Person **person.

因爲整個block在堆上,而內部是對person的指針指引,所以只要block不釋放,對person的指針就不會釋放,person也就不會被釋放。

3.1:假設在mrc環境下

int main(int argc, char * argv[]) {
    @autoreleasepool {
        block blo;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            blo = ^{
                NSLog(@"---%d",person.age);
            };            
            [person release];
        }
        NSLog(@"========");
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}


#import "Person.h"
@implementation Person

- (void)dealloc
{
    [super dealloc];
    NSLog(@"person-dealloc----");
}
@end
2018-08-19 17:58:05.680 newxc[19773:6348592] person-dealloc----
2018-08-19 17:58:05.681 newxc[19773:6348592] ========

爲啥呢:因爲這個block是在棧上,而且棧空間上的是不會持有對象的。如果是堆空間,是有能力保住這個對象的。所以person就dealloc了。

如果對這個block copy 那麼person就不會dealloc了,因爲block相當於對person做了retain操作。

假設在arc環境下----------這裏2.1.1會做詳細解釋,從這裏做案例開始.

nt main(int argc, char * argv[]) {
    @autoreleasepool {
        block blo;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            blo = ^{
                NSLog(@"---%d",person.age);
            };
        }
        
        NSLog(@"========");
        
        
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}
2018-08-19 18:12:54.016 newxc[19963:6373020] ========
2018-08-19 18:12:54.017 newxc[19963:6373020] person-dealloc----

會先出了作用域,block釋放,然後block對其person的指針也沒了,所以person釋放。

假設用__weak來修飾person實例

int main(int argc, char * argv[]) {
    @autoreleasepool {
        block blo;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *wekaPerson = person;
            blo = ^{
                NSLog(@"---%d",wekaPerson.age);
            };
        }
        
        NSLog(@"========");
        
        
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}
2018-08-19 18:15:29.656 newxc[19986:6377817] person-dealloc----
2018-08-19 18:15:29.660 newxc[19986:6377817] ========

看結果不一樣了,爲啥呢,

3.2:arc:對上面做一個原因解釋

弱引用:需要運行時的支持的。

            Person *person = [[Person alloc] init];
            person.age = 10;
            
            __weak Person *wekaPerson = person;
            blo = ^{
                NSLog(@"---%d",wekaPerson.age);
            };

你會發現,當你寫了__weak,在去調用下面的命令時會報錯

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
/var/folders/27/tr3mg74j3nq_zxgdyg1rj6480000gn/T/main-6a8820.mi:47464:28: error: 
      cannot create __weak reference because the current deployment target does
      not support weak references
            __attribute__((objc_ownership(weak))) Person *wekaPerson = person;
                           ^
1 error generated.

你會發現上述報錯,只是靜態的編譯會報錯,所以需要我們指定是runtime並且指定arc環境。

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

發現,不一樣的地方,就是person弱引用了

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak wekaPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _wekaPerson, int flags=0) : wekaPerson(_wekaPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

那如果是不加__weak呢 ,這裏注意要接着用上面的指令編譯,發現結果,是strong.

typedef void (*Block)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong per;  //注意這裏,是strong類型,這是沒有加__weak的情況,同樣也沒有加__strong
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

那如果是加了__strong的情況呢?接着使用上指令 結果可以看到依然是__strong修飾

          Block blo;
        {
          
            __strong Person *per = [[Person alloc] init];
            per.age = 100;
            
            blo = ^{
                NSLog(@"---%d",per.age);
            };
        };

typedef void (*Block)(void);


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong per;  // 主要看這裏,是用__strong來修飾的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *__strong per = __cself->per; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_main_a9de8d_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)per, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->per, (void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};

好總結一下,

如果block在棧上(不論是arc還是mrc),都不會對block內部的產生強引用。(因爲自己都是棧上的,隨時會被銷燬,所以不會對裏面的對象產生強引用。)

可以看到這個有對象的block跟以前的block不太一樣。不太理解不一樣的,可以看1.1捕獲上面的源碼跟這個的區別,是多了兩個函數,請看下面。

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->per, (void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}

同樣可以看到這個函數也有賦值

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};

__main_block_copy_0賦值給copy函數,__main_block_dispose_0賦值給dispose函數。

也就是說,上面的blo一旦進行copy操作,要copy到堆上,就會自動調用copy函數,這個函數會調用裏面的_Block_object_assign函數,這個函數做的事情是:會根據外部auto變量傳進來的是strong還是weak,來對__main_block_impl_0裏面的Person指針進行對外部的person對象是strong還是weak。如果是傳進來的是strong,則__main_block_impl_0裏面的person指針會強引用着外部的person對象,如果是weak也是一樣。

那dispose什麼時候調用呢? 當堆上的blo 進行銷燬時,就會自動調用block內部的dispose,就會釋放引用的那些函數

看總結圖

看題

蘋果會認爲block內部有強引用的時候,不會釋放,等到強引用釋放完了纔會釋放。弱引用沒有影響。

 

額外提示一下,什麼情況下才會出現copy和dispose這兩個函數呢?

一旦block內部訪問的是對象類型的,就會出現copy和dispose函數,因爲訪問的是對象,想擁有這個對象,所以就會有內存對象管理。

 

4:如何修改block內部的變量:__block、static、全局、指針

如果我們想對block裏面的變量進行修改,怎麼做?

a:static

b:弄成全局變量  

c:用__block.

d:數據類型用指針指向(參考static使用的block內部原理)

 

1:先看d也就是static使用的block的內部原理

typedef void(^Block)(void);

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

        static int height = 10;

        Block bl = ^{
            height = 199;
        };
        bl();
    }
    return 0;
}
typedef void(*Block)(void);


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *height;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_height, int *__newhei, int flags=0) : height(_height){
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *height = __cself->height; // bound by copy
            (*height) = 199;

        }

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)};
int main(int argc, const char * argv[]) {

        static int height = 10;

        Block bl = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &height));

        ((void (*)(__block_impl *))((__block_impl *)bl)->FuncPtr)((__block_impl *)bl);

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

從上面可以看出來 傳進去的是height的指針,從__main_block_impl_0對象結構體中也可以知道,是拿int *height;來接收的,在__main_block_func_0函數中可以看到賦值時候用的是int *_newhei = __cself->_newhei;   *_newhei = 20; .所以我們可以這麼做


typedef void(^Block)(void);

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

        int age = 10;
        
        int *_hei = &age;

        int *_newhei = _hei;

        *_newhei = 304;

        Block bl = ^{
            *_newhei = 20;

        };
        bl();
        NSLog(@"在這裏打印的話會發現age已經變爲20 了 並且*_newhei 也是20")
        
    }
    return 0;
}

 

當然 這種方式是麻煩了點,而且只針對基本數據類型,對於對象類型不適用。那爲什麼不適用呢,請看下面


        Person *per = [[Person alloc] init];

        NSInteger df = 19;

        static int lage = 100;

        Block bl = ^{

            lage = 300;
            NSLog(@"%@",per.name);

        };
        bl();

這個底層源碼是 我們只看這一段

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *lage = __cself->lage; // bound by copy
  Person *per = __cself->per; // bound by copy


            (*lage) = 300;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_70_0rj_h7dx3zv20xx9ln3x0d8h0000gn_T_main_3efd6b_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)per, sel_registerName("name")));

        }

注意到,對於基本數據類型,這裏取值都用的是*lage 而不是lage,也就是這是取值,並不是取的指針。

但是對於對象來說,取的是per,也就是(id)per,而不是*per,(這個*per在oc中也不代表什麼)。所以對於oc對象來說,同樣跟c語言一樣有指針,但是方式卻不一樣。 所以oc對象來說 應該方式都是這樣的下面包裝一層的方式

 

2:看一個例子 用__block

typedef void (^Block)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
            
        Block blo = ^{
            
                NSLog(@"---%d",age);
            };
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

typedef void (*Block)(void);

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding; // 看下面的引用知道,這個指向自己
 int __flags;
 int __size;
 int age;
};

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, __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

            (age->__forwarding->age) = 20;

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_main_0dc06e_mi_0,(age->__forwarding->age));
            }
// 要對__main_block_impl_0對象進行內存管理,所有有copy和dispose
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*/);}

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, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

// 這裏是 __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
};

        Block blo = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));

    }
    return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));

}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

// 要對__Block_byref_age_0對象進行內存管理,所有有copy和dispose

看這個結構體 這個是調用age,能看出來第二個(__Block_byref_age_0 *)&age賦值給了__Block_byref_age_0結構體的forwarding指針,也就是把自己的地址賦值給了自己結構體中的forwarding。

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {

(void*)0,

(__Block_byref_age_0 *)&age, 

0, 

sizeof(__Block_byref_age_0),

10};  
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

接着看這個,看blo這個結構體把age的地址:&age 傳進去了,賦值給了第三個 __Block_byref_age_0 *age; // by ref

Block blo = (
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, 
&__main_block_desc_0_DATA, 
(__Block_byref_age_0 *)&age, 
570425344));
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, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

所以,block內部會有一個指針(這裏是*age)指向__Block_byref_age_0結構體,這個結構體內部又有age這個值,存儲這個值,那是如何修改這個值的呢?

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_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_main_acce32_mi_0,(age->__forwarding->age));
            }

看這個__cself從__main_block_impl_0這個結構體中拿到age也就是__Block_byref_age_0結構體,然後用這個結構體先訪問自己內部的forwarding,因爲forwarding指向的就是自己,所以再拿age,再賦值,就賦值成功啦。那到此肯定有疑問,這個改變的不就是block內部的age這個值麼,而且這個age是在內部創建的,驗證。打印一下

   __block int age = 10;
        Block blo = ^{
            age = 20;
            NSLog(@"---%d",age);
        };
        blo();

        NSLog(@"外面:%d",age);
2018-09-07 22:01:44.772 newxc[31713:10543714] ---20
2018-09-07 22:02:16.602 newxc[31713:10543714] 外面:20

那是爲啥呢?看這個,不加block,只用__block修飾,再看源碼

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

        __block int age = 10;

        NSLog(@"外面:%d",age);
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
int main(int argc, 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};

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_27_tr3mg74j3nq_zxgdyg1rj6480000gn_T_main_80e809_mi_0,(age.__forwarding->age));

    }
    return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));

}

請看,有__Block_byref_age_0結構體,就是說只要加上__block,就會被包裝秤這個對象結構體.在nslog中訪問的就是__Block_byref_age_0結構體中的age,所以是能修改的,這個原來船進入的是10,並不是10的地址,而是這個結構體中的age是新生成的age變量,這只是造成了一個假象,顯示這個age被修改了,其實是修改的這個__Block_byref_age_0結構體對象裏面的age變量,驗證如下 (就如同,外面是一個person變量,有一個age的屬性,在block內部給age賦值,是修改成功的)。


當使用了__block之後,age的地址就發生了變化,這裏因爲arc環境下,block已經copy到堆上,但是原來__block修飾的age的那個對象還在棧上,而最後age= 20,這個age,是訪問的堆上的age,(因爲已經從棧到堆了。)

總結

那如果再加上一個person對象呢用__block修飾?

typedef void(^Block)(void);

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

        __block Person *per = [[Person alloc] init];
        __block int age = 10;

        Block bl = ^{

           NSString *a =  per.name;
            per.name = @"34";
            age = 300;
        };
        bl();
    }
    return 0;
}
typedef void(*Block)(void);

struct __Block_byref_per_0 {
  void *__isa;
__Block_byref_per_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *per;
};
struct __Block_byref_age_1 {
  void *__isa;
__Block_byref_age_1 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_per_0 *per; // by ref
  __Block_byref_age_1 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_per_0 *_per, __Block_byref_age_1 *_age, int flags=0) : per(_per->__forwarding), 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_per_0 *per = __cself->per; // bound by ref
  __Block_byref_age_1 *age = __cself->age; // bound by ref


           NSString *a = ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(per->__forwarding->per), sel_registerName("name"));
            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(per->__forwarding->per), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_70_0rj_h7dx3zv20xx9ln3x0d8h0000gn_T_main_b0ec3f_mi_0);
            (age->__forwarding->age) = 300;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->per, (void*)src->per, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->per, 8/*BLOCK_FIELD_IS_BYREF*/);_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_per_0 per = {(void*)0,(__Block_byref_per_0 *)&per, 33554432, sizeof(__Block_byref_per_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"))};
        __attribute__((__blocks__(byref))) __Block_byref_age_1 age = {(void*)0,(__Block_byref_age_1 *)&age, 0, sizeof(__Block_byref_age_1), 10};

        Block bl = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_per_0 *)&per, (__Block_byref_age_1 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)bl)->FuncPtr)((__block_impl *)bl);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

看上面就會生成兩個__main_block_impl_結構體,並且最後nslog裏,訪問的也是__main_block_impl_結構體裏面的forwarding裏的per或者age。

 

4.1:__block的內存管理

(如果是對象修飾用__block的話,記住兩個copy是不同的,__main_block_copy_0和__Block_byref_id_object_copy。作用對象也不同)

強引用__Block_byref_age_0 *age; 對象,對這個強引用(copy)

銷燬對__Block_byref_age_0 *age;對象進行release。

(這個內存管理對沒有修飾的對象類型的copy是一樣的,如果加上了__block, 那麼就是再加上一層內存管理,也就是__block變量內部對對象類型的內存管理。)

4.2:__forwarding指針原理

那上面間接的可以解釋爲什麼有__forwarding指針,而不是age直接指向age

(age->__forwarding->age) = 20;

因爲這個__Block_byref_age_0對象剛開始都是在棧上,當block被copy到堆上的時候,自動也就copy了一份__Block_byref_age_0對象,這個時候,第一個age是棧上的,而它的__forwarding指針是指向堆上的它自己,所以需要用__forwarding的age來取值或者賦值,但是如果block是在棧上,那麼這些都不會被copy到堆上,所以__forwarding還是指向自己,所以,這不論是在棧還是再堆都實現了指向自己,所以可以賦值或者取值。(copy到堆上了,地址肯定就發生變化了。)

4.3:問題:blok內部會對下面兩個對象有什麼不同麼

Person *per = [[Person alloc] init];
        __block int age = 10;

不同點在這裏

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_object_assign((void*)&dst->per, (void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}

一旦用__block,這個assign函數就會對這個對象產生強引用,而沒有用__block的對象,會根據是strong還是weak來修飾的,再對它產生強或者弱引用。

那相同點是什麼呢?

 

4.4:被__block修飾的對象類型:arc環境下

這裏着重看一下下面兩個

        __block  Person *weakPerson = per;
        __block  __weak Person *weakPerson = per;

這兩個在代碼上有需要注意的地方,先看下__block  Person *weakPerson = per;的源碼

typedef void (^Block)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        Person *per = [[Person alloc] init];
        
        __block  Person *weakPerson = per;
        Block blok = ^{
            weakPerson.age = 20;
        };
        blok();
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}   // 看這裏assign 對 person這個對象進行copy,也就是進行131,根據外面傳進來的操作。

static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}


typedef void (*Block)(void);

struct __Block_byref_weakPerson_0 {
  void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__strong weakPerson;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__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_weakPerson_0 *weakPerson = __cself->weakPerson; // bound by ref

            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(weakPerson->__forwarding->weakPerson), sel_registerName("setAge:"), 20);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 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, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

        __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {
(void*)0,
(__Block_byref_weakPerson_0 *)&weakPerson, 
33554432, 
sizeof(__Block_byref_weakPerson_0), 
__Block_byref_id_object_copy_131, 
__Block_byref_id_object_dispose_131, 
per};   // 這裏的封裝好的weakPerson放在下面裏面.

        Block blok = (
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, 
&__main_block_desc_0_DATA, 
(__Block_byref_weakPerson_0 *)&weakPerson, 
570425344));

        ((void (*)(__block_impl *))((__block_impl *)blok)->FuncPtr)((__block_impl *)blok);

    }
    return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));

}

看這個用__block修飾的對象跟__block修飾的普通數據類型不一樣的地方,在__Block_byref_weakPerson_0對象裏多了兩個參數, void (*__Block_byref_id_object_copy)(void*, void*);和 void (*__Block_byref_id_object_dispose)(void*);那這兩個的作用是什麼呢?就是管理__Block_byref_per_0中_Person *__strong weakPerson;的內存的,也就是是對象就需要的內存管理。

接着分析這個過程,剛開始把weakPerson封裝好給blok賦值,從__Block_byref_weakPerson_0結構中可以看到,賦值的內容:isa是指針,

__forwarding是賦值的地址&weakPerson,

__flags賦值的是33554432,

__size賦值的是sizeof(__Block_byref_weakPerson_0),

__Block_byref_id_object_copy賦值的是 __Block_byref_id_object_copy_131,

__Block_byref_id_object_dispose賦值的是__Block_byref_id_object_dispose_131,

Person *__strong weakPerson;賦值的是per。

所以__forwarding指向的就是自己。而__Block_byref_id_object_copy_131看這個函數內是這個_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); 這個是這個結構體指針的地址加40,也就是__Block_byref_weakPerson_0的地址加40 就是 Person *__strong weakPerson;,對它進行的copy和dispose操作。

所以 過程是:

當block被copy到堆上的時候,(arc環境下),block會調用__main_block_copy_0函數,來對__main_block_impl_0對象內部的 __Block_byref_weakPerson_0 *weakPerson;進行copy,進行強指針引用。同時__Block_byref_weakPerson_0對象也被拷貝到堆上,在這個copy的時候,__Block_byref_weakPerson_0對象就會調用自己內部的__Block_byref_id_object_copy對自己內部的對象 Person *__strong weakPerson進行copy;根據前面的修飾符進行強引用還是弱引用,這裏因爲是strong,所以是強引用。同時注意一下,如果是mrc環境下,那麼這個對person的引用就永遠不會是強引用。

好下面看

typedef void (^Block)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        Person *per = [[Person alloc] init];
        
        __block __weak Person *weakPerson = per;
        Block blok = ^{
            weakPerson.age = 20;
        };
        blok();
    }
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

}
typedef void (*Block)(void);

struct __Block_byref_weakPerson_0 {
  void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak weakPerson;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__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_weakPerson_0 *weakPerson = __cself->weakPerson; // bound by ref

            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(weakPerson->__forwarding->weakPerson), sel_registerName("setAge:"), 20);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 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, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

        __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, per};
        Block blok = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blok)->FuncPtr)((__block_impl *)blok);
    }
    return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));

}

跟上面不同的點就在於__Block_byref_weakPerson_0裏面的 Person *__weak weakPerson;這裏是weak修飾,同樣形象化的以下圖解釋

證明是強指針:

證明有弱指針

在mrc環境下,證明copy也不會產生強指針。

arc下不論block裏經過幾層對person對象,都會有強弱指針。

block的內存管理,調用_Block_object_assign的時候傳進去的值要麼是3要麼是8,但是__block修飾的 傳進去的是__Block_byref_id_object_copy_131  這個 是131,這個管理的還是不同的。

總結:

 

5:循環引用

typedef void (^Block)(void);

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        Person *per = [[Person alloc] init];
        per.age = 10;
        per.block = ^{
            
            NSLog(@"-----%d",per.age);
        };
    }
    NSLog(@"-----000000000");

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
// 我們只看這一節

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong per;  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _per, int flags=0) : per(_per) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

看到上面是__strong,也就是下面這個圖

導致最後person對Person這個類的指針釋放了,上面兩個卻一直相互引用,無法釋放。

 

5.1:解決循環引用問題

arc:

__weak __unsafe_unretained解決  和__block

        Person *per = [[Person alloc] init];
        
        
        __weak typeof(per) weakPer = per;
        per.age = 10;
        per.block = ^{
            
            NSLog(@"-----%d",weakPer.age);
        };
Person *per = [[Person alloc] init];
        
        // __weak 不會產生強引用
        // __unsafe_unretained 不會產生強引用 不安全
        __unsafe_unretained typeof(per) weakPer = per;
        per.age = 10;
        per.block = ^{
            
            NSLog(@"-----%d",weakPer.age);
        };

weak會把指向的對象銷燬時會自動置讓指針爲nil,所以weak是安全的。

__block

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        __block Person *per = [[Person alloc] init];
        
        // __weak 不會產生強引用
        // __unsafe_unretained 不會產生強引用 不安全
        per.age = 10;
        per.block = ^{
            
            NSLog(@"-----%d",per.age);
            
            per = nil;  // 這兩個必須要寫
        };
        per.block();   // 這兩個必須要寫 可能有內存泄漏,
    }
    NSLog(@"-----000000000");

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}

mrc環境下

__unsafe_unretained 和__block

mrc不支持__weak。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        
        __unsafe_unretained Person *per = [[Person alloc] init];
        
        per.age = 10;
        per.block = [^{
            
            NSLog(@"-----%d",per.age);
            
        } copy];

        [per release];
    }
    NSLog(@"-----000000000");

    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
2018-09-10 19:42:27.889 newxc[34540:11722615] person-dealloc----
2018-09-10 19:42:27.890 newxc[34540:11722615] -----000000000

__block在mrc下不會產生強引用對立面的對象。

__block Person *per = [[Person alloc] init];
        
        per.age = 10;
        per.block = [^{
            
            NSLog(@"-----%d",per.age);
            
        } copy];

        [per release];

 

大總結:面試

1:block的原理是怎樣的,本質是什麼?

封裝了函數調用以及調用環境OC對象。

 

2:__block的作用?有什麼使用的注意點?

封裝成了一個對象,在mrc環境下,不會產生強引用對立面的對象

 

3:block的屬性修飾詞爲什麼是copy?使用block有哪些使用注意?

block沒有copy,就不會在堆上,copy到堆上,就可以進行內存管理了,使用注意:循環引用

 

4:block在修改NSMUtablearray,需不需要添加__block?

不需要,

 

- (void)test
{
    __weak typeof(self) weakSelf = self;
    
    self.age = 10;
    self.block = ^{        
        
        NSLog(@"-----%d",weakSelf->_age);
        
    };

}

這個會報錯,可能是會空值,不能產生強引用,可能會空值

所以就要用一個強指針,來騙編譯器,這是編譯器行爲

- (void)test
{
    __weak typeof(self) weakSelf = self;
    
    self.age = 10;
    self.block = ^{
        
        __strong typeof(weakSelf) myself = weakSelf;
        
        
        NSLog(@"-----%d",myself->_age);
        
    };

}

另外 用這個強指針 可以確保不會保住這個對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章