Blocks原理

博客連接Blocks原理ios

更新日期:2019-07-22面試

Block的實質

咱們先寫一個最基礎的block編程

int main(int argc, const char * argv[]) {
    void (^testBlock)(void) = ^{
        printf("asddasd");
    };
    testBlock();
    
    return 0;
}
複製代碼

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m轉化成C++代碼。其中有關鍵代碼以下:api

int main(int argc, char * argv[]) {
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
複製代碼

將代碼簡化一下:bash

int main(int argc, char * argv[]) {
    // 定義block變量
    void (*testBlock)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
    // 執行block
    testBlock->FuncPtr(testBlock);
    
    return 0;
}
複製代碼

能夠看出咱們定義的testBlock變成了&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA),執行block的時候將testBlock()變成了testBlock->FuncPtr(testBlock)框架

__main_block_impl_0是一個結構體,相關定義以下:iphone

struct __block_impl {
    void *isa; // isa是OC對象特有的標誌
    int Flags;
    int Reserved;
    void *FuncPtr; // 該指針指向block執行函數的地址
};

static struct __main_block_desc_0 {
    size_t reserved;   // 預留的字段
    size_t Block_size; // block所佔內存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// __main_block_impl_0是一個C++結構體能夠聲明函數
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; // 初始化__block_impl結構體的isa成員變量
        impl.Flags = flags;
        impl.FuncPtr = fp; // fp就是__main_block_func_0函數
        Desc = desc;
    }
};

// 該函數封裝了block的執行代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("asddasd");
}
複製代碼

__main_block_impl_0有兩個成員變量,分別是__block_impl impl__main_block_desc_0* Desc,還有一個__main_block_impl_0的構造函數。__block_impl__main_block_desc_0也是兩個結構體,其成員變量做用都寫在代碼註釋中,這裏就再也不說了。函數

最重要的是其構造函數:測試

__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變量的時候,傳入的__main_block_func_0&__main_block_desc_0_DATA分別對應了fp* descfp指向的就是__main_block_func_0函數,它會傳給impl.FuncPtr,另外的isa的初始值是_NSConcreteStackBlock_NSConcreteStackBlock就至關於class_t中的結構體類型的。優化

接着看調用部分,關於block的調用會被轉化成testBlock->FuncPtr(testBlock),它指向的是__main_block_func_0函數的地址。這裏要注意testBlock->FuncPtr(testBlock)是我將強制轉化取消後的代碼,本來應該是(__block_impl *)testBlock->FuncPtr(testBlock),由於__block_impl imp__main_block_impl_0這個結構體的第一個成員變量,因此__block_impl imp的內存地址就是__main_block_impl_0結構體的內存地址。

因此說block的本質就是Objective-C對象,block的調用就是函數指針的調用

截獲自動變量值

根據《Objective-C高級編程》一書中提到,所謂的「截獲自動變量值」意味着在執行Block語法時,Block語法表達式所使用的自動變量被保存到Block的結構體實例(即Block自身中)。

那不一樣類型變量之間的截獲會有區別嗎,使用如下代碼:

#include <stdio.h>

static int globalStaticValue = 1;

int globalValue = 2;

int main(int argc, char * argv[]) {
    int a = 3;
    
    static int b = 4;
    
    void (^testBlock)(void) = ^{
        printf("%d", globalStaticValue);
        printf("%d", globalValue);
        printf("%d", a);
        printf("%d", b);
    };
    
    testBlock();
    
    return 0;
}
複製代碼

轉化成C++代碼:

static int globalStaticValue = 1;
int globalValue = 2;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    int *b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int *b = __cself->b; // bound by copy
    
    printf("%d", globalStaticValue);
    printf("%d", globalValue);
    printf("%d", a);
    printf("%d", (*b));
}

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, char * argv[]) {
    int a = 3;
    
    static int b = 4;
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
複製代碼

從上面的代碼能夠看出:

  • 局部變量會被block直接截獲
  • 局部靜態變量會被block直接截獲其指針,經過指針進行方法
  • 全局變量和全局靜態變量並不會被截獲,而是直接使用

截獲對象

block是如何截獲對象變量的,使用以下代碼:

#import <Foundation/Foundation.h>

static NSObject *globalObjc;

int main(int argc, char * argv[]) {    
    NSObject *object = [[NSObject alloc] init];
    __weak NSObject *weakObject = object;
    
    void (^testBlock)(void) = ^{
        NSLog(@"%@", object);
        NSLog(@"%@", weakObject);
        NSLog(@"%@", globalObjc);
    };
    
    testBlock();
    
    return 0;
}
複製代碼

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m命令,轉化成C++代碼後:

static NSObject *globalObjc;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    NSObject *__strong object;
    NSObject *__weak weakObject;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _object, NSObject *__weak _weakObject, int flags=0) : object(_object), weakObject(_weakObject) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSObject *__strong object = __cself->object; // bound by copy
    NSObject *__weak weakObject = __cself->weakObject; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_0, object);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_1, weakObject);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2p_n_j9tbqx6lqb2bqv110q6d300000gn_T_main_a7d9fa_mi_2, globalObjc);
}

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
    _Block_object_dispose((void*)src->weakObject, 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};

int main(int argc, char * argv[]) {
    NSObject *object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    __attribute__((objc_ownership(weak))) NSObject *weakObject = object;

    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, object, weakObject, 570425344));

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

    return 0;
}
複製代碼

若是截獲的變量是對象類型的,會將對象變量及其全部權修飾符一併截獲。再看一下__main_block_copy_0__main_block_dispose_0

  • 當block進行一次copy操做的時候,__main_block_copy_0函數內部調用_Block_object_assign函數,它根據對象的類型產生強引用或者弱引用;

  • 當block從堆中移除的時候,__main_block_dispose_0函數內部調用_Block_object_dispose函數,它會自動釋放掉引用的變量。

    關於截獲對象的實驗是在ARC環境下執行的,testBlock是_NSConcreteMallocBlock類型的。若是在非ARC環境下,testBlock是_NSConcreteStackBlock類型的即Block在棧上,則不會對截獲的對象變量進行強引用

__block修飾符

當咱們須要對被Block截獲的局部變量進行賦值操做的話,須要添加一個__block這個說明符,那__block到底有什麼做用呢?

先看一段代碼:

#include <stdio.h>

int main(int argc, char * argv[]) {
    __block int a = 1;
    void (^testBlock)(void) = ^{
        a = 100;
        printf("%d", a);
    };
    testBlock();
    
    return 0;
}
複製代碼

因爲使用了__block關鍵字,能夠修改變量a的值,因此輸出結果是100。

先說明一下爲何不使用__block就不能修改值。執行block就是調用__main_block_func_0函數,amain函數中的局部變量,咱們不可能在一個函數中去修改另外一個函數的局部變量。

將上面代碼轉換成C++代碼,先看一下__main_block_impl_0這個結構體

struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
複製代碼

__main_block_impl_0結構體中多了一個__Block_byref_a_0 *a而不是int a__Block_byref_a_0也是一個結構體,裏面也有一個isa指針,所以咱們也能夠將它當作一個對象。另外還有一個__Block_byref_a_0 *類型的__forwarding指針和變量a。關於__forwarding指針的做用咱們會在後面提到。

接着是主函數:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 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[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
複製代碼

在主函數中咱們能夠看到__block int a = 1;轉化成了__Block_byref_a_0的一個結構體,其中__forwarding指針指向這個結構體本身。

訪問__block變量

最後是Block執行的時候即調用__main_block_func_0函數:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  (a->__forwarding->a) = 100;
  printf("%d", (a->__forwarding->a));
}
複製代碼

原來的a = 100;則被轉換成__Block_byref_a_0 *a = __cself->a;(a->__forwarding->a) = 100;。這兩句代碼的意思是先獲取結構體中的a,經過a結構體的__forwarding指針指向成員變量a賦值爲100。

因此__block的本質就是**__block將變量包裝成一個對象,將截獲到的值存在這個對象中,經過對截獲的值進行賦值而更改原有的值**。

Block存儲域

block有下面三種類型

設置對象的存儲域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的數據區域
_NSConcreteMallocBlock

那麼如何肯定block的類型?下面這個實驗須要在MRC環境下進行測試,代碼以下:

int main(int argc, char * argv[]) {
    int a = 1;
    
    void (^block1)(void) = ^{
        NSLog(@"%s - %d", __func__, a);
    };
    
    void (^block2)(void) = ^{
        NSLog(@"%s", __func__);
    };
    
    void (^block3)(void) = [block1 copy];
    
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [block3 class]);
    
    return 0;
}
複製代碼

結果以下:

block類型

經過打印block的類型能夠知道

環境
_NSConcreteStackBlock 訪問了自動變量值
_NSConcreteGlobalBlock 沒有訪問自動變量值
_NSConcreteMallocBlock _NSConcreteStackBlock使用了copy

copy操做對不一樣類型block的影響:

Block的類 副本源的配置存儲域 複製效果
_NSConcreteStackBlock 從棧複製到堆
_NSConcreteGlobalBlock 程序的數據區域 什麼也不作
_NSConcreteMallocBlock 引用計數增長

block的copy操做能保證block不容易被銷燬。可是在ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上,即咱們打印出來的block是_NSConcreteMallocBlock類型的。

這些狀況有:

  • block做爲返回值的時候;
  • block賦值給strong指針的時候;
  • block做爲Cocoa API中方法名含有usingBlock的方法參數的時候;
  • block做爲GCD API的參數的時候;

因此若是將上面代碼放在ARC環境下執行的話,則block1__NSMallocBlock__類型的。

release操做會在其內部自動執行。

__block變量的存儲域

__block變量的存儲域 Block從棧複製到堆時的影響
從棧複製到堆並被Block持有
被Block持有

既然__block對應的結構體或者對象是在Block內部使用,那麼Block就須要對這個對象的生命週期進行負責。接着咱們從源碼裏面進行理解

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 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[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
    void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    
    return 0;
}
複製代碼

咱們須要注意兩個函數__main_block_copy_0__main_block_dispose_0

  • 當block進行一次copy操做的時候,__main_block_copy_0函數內部調用_Block_object_assign函數,它會對__block變量生成強引用;
  • 當block從堆中移除的時候,__main_block_dispose_0函數內部調用_Block_object_dispose函數,它會自動釋放掉引用的__block變量。

上面的代碼是在ARC環境下的,若是Block在棧上,並不會對__block變量產生引用

這裏還要解決一個前面遺留的問題,既然__Block_byref_a_0結構體中已經有變量a,爲何還須要使用__forwarding指針對a賦值呢或者說__forwarding存在的意義是什麼?

關於__forwarding的做用《Objective-C高級編程》一書中的第111頁、第112頁中有很明確的說到:__block變量的結構體成員變量__forwarding能夠實現不管__block變量配置在棧上仍是堆上都可以正確地訪問__block變量。

沒有進行copy操做的時候:

棧上的block
進行copy操做之後:
block拷貝

這裏分爲兩種狀況:

  1. 若是咱們沒有對棧上的Block執⾏copy操做,修改被__block修飾的變量其實是經過其__forwarding指針指向的自身,對其中的變量進行修改。

  2. 若是咱們執行過copy操做,那麼棧上的Block的__forwarding指針,實際是指向堆上的__block修飾的變量,⽽堆上的__forwarding指針則指向⾃自身的__block修飾的變量。在變量做用域結束的時候,棧上的__block變量和Block被廢棄掉,可是堆上的__block變量和Block不受影響。

Block的循環引用

咱們在使用Block的時候,若是有一個對象持有了這個Block,而在Block內部又使用了這個對象,就會形成循環引用。如如下寫法:

#import <Foundation/Foundation.h>

typedef void (^TestBlock) (void);

@interface AObject: NSObject {
    TestBlock testBlock;
}

@end

@implementation AObject

- (instancetype)init {
    if (self = [super init]) {
        testBlock = ^{
            NSLog(@"self = %@", self);
        };
    }
    
    return self;
}

- (void)dealloc {
    NSLog(@"%@ dealloc", self.class);
}

@end

int main(int argc, char * argv[]) {
    
    AObject *a = [[AObject alloc] init];
    NSLog(@"%@", a);
    
    return 0;
}

複製代碼

能夠看到,上面這段代碼發生了循環引用,致使AObject對象沒法被釋放。一般作法是使用__weak關鍵字在Block外部聲明一個弱引用。從新修改下init方法:

- (instancetype)init {
    if (self = [super init]) {
        __weak AObject *weakSelf = self;
        testBlock = ^{
            NSLog(@"self = %@", weakSelf);
        };
    }
    
    return self;
}
複製代碼

前面在講述截獲自動變量值的時候,咱們知道Block在截獲對象類型的時候,會連同對象的全部權修飾符一塊兒截獲,這裏截獲的是__weak修飾的weakSelf,所以不會發生循環引用。

在爲避免循環引用而使用__weak修飾符的時候,還要注意有可能在Block執行的時候,對象在中途被釋放掉了。這個時候須要在Block內部聲明一個局部變量強持有對象,這個局部變量會在到Block執行結束時自動釋放,不會形成循環引用,而對象也會在Block執行結束後被釋放。

是否是全部的Block都須要在外部聲明使用__weak修飾呢?答案是否認的。所謂「引用循環」是指雙向的強引用,因此那些「單向的強引用」(block強引用self)沒有問題。例如:

[UIView animateWithDuration:duration
                 animations:^{
                     [self.superview layoutIfNeeded];
                 }];
複製代碼

但若是你使用一些參數中可能含有ivar的系統的api,如NSNotificationCenter就要當心一點。

__weak __typeof__(self) weakSelf = self;
 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                               object:nil
                                                                queue:nil
                                                           usingBlock:^(NSNotification *note) {
    //使用self會循環引用,可是使用weakSelf或者strongSelf則沒有問題                                                      
     __typeof__(self) strongSelf = weakSelf;
     [strongSelf dismissModalViewControllerAnimated:YES];
 }];
複製代碼

能夠看出self --> _observer --> block --> self這顯然是一個循環引用。總而言之,只有當self直接或間接的持有 Block,而且在Block內部又使用了self的時候,才應該使weakSelf

總結

  1. block的本質是一個封裝了函數調用以及函數調用環境的OC對象;
  2. block截獲自動變量值的規則:
    1. 局部變量會被直接截獲;
    2. 局部靜態變量會被截獲其指針;
    3. 全局變量並不會被截獲,而是直接使用;
  3. block截獲對象的規則:
    1. block位於棧上,則不會對截獲的對象變量進行強引用;
    2. block從棧上覆制到堆上,調用copy函數,對截獲的變量進行強/弱引用;
    3. block從堆上移除,調用dispose函數,自動釋放引用的變量;
  4. block使用copy屬性的緣由:在MRC下,訪問了自動變量的block處於棧上,容易被釋放,使用copy能夠將其複製到堆上,放在堆上即可以本身去控制Block的生命週期;在ARC下,對Block作了優化;
  5. block的循環引用:一個對象持有了這個Block,而在Block內部又使用了這個對象,就會形成循環引用;
  6. __block的做用:解決block內部沒法修改自動變量值的問題;
  7. __block的注意點:不能修飾全局變量和靜態變量;
  8. __block的實質:編譯器將__block變量包裝成一個對象,將截獲到的值存在這個對象中,經過對截獲的值進行賦值而更改原有的值;
  9. __forwarding指針的做用:能夠實現不管__block變量配置在棧上仍是堆上都可以正確地訪問__block變量;

Sunnyxx的Block面試題

sunnyxx_block

代碼以下:

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

// Block定義
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
};

#pragma mark - 第一題

static void test_1__main_block_func_0(struct __main_block_impl_0 *__cself){
    NSLog(@"Hello,world!");
}

void HookBlockToPrintHelloWorld(id block){
    //    struct __block_impl *tmp = (__bridge struct __block_impl *)block;
    //    tmp->FuncPtr = &test_1__main_block_func_0;
    // 或者
    struct __main_block_impl_0 *tmp2 = (__bridge struct  __main_block_impl_0 *)block;
    ((struct __block_impl *)tmp2)->FuncPtr = &test_1__main_block_func_0;
}

#pragma mark - 第二題

static void (*original_func)(struct __main_block_impl_0 *, int, NSString *);

static void test_2__main_block_func_0(struct __main_block_impl_0 *__cself, int a, NSString *b) {
    NSLog(@"%d %@", a, b);
    original_func(__cself, a, b);
}

void HookBlockToPrintArguments(id block) {
    struct __block_impl *tmp = (__bridge struct __block_impl *)block;
    original_func = tmp->FuncPtr;
    tmp->FuncPtr = &test_2__main_block_func_0;
}

#pragma mark - 第三題

static id (*original__Block_copy)(id);

id new__Block_copy(id block) {
    id tmp = original__Block_copy(block);
    HookBlockToPrintArguments(tmp);
    
    return tmp;
}

void HookEveryBlockToPrintArguments() {
    struct rebinding open_rebinding = {
        "_Block_copy",
        new__Block_copy,
        (void *)&original__Block_copy
    };
    
    rebind_symbols((struct rebinding[1]){open_rebinding}, 1);
}

int main(int argc, char * argv[]) {
    // 第一題
    void(^block1)(void) = ^{
        NSLog(@"%s", __func__);
    };
    
    HookBlockToPrintHelloWorld(block1);
    
    block1();
    
    // 第二題
    void (^block2)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block invoke");
    };
    
    HookBlockToPrintArguments(block2);
    
    block2(123, @"aaa");
    
    // 第三題
    HookEveryBlockToPrintArguments();
    
    void (^block3)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block3 invoke");
    };
    
    block3(1, @"dsadas");
    
    void (^block4)(int a, NSString *b) = ^(int a, NSString *b) {
        NSLog(@"block4 invoke");
    };
    
    block4(222, @"ddd");
    
    return 0;
}
複製代碼

第一題分析

想要替換原有的block實現,咱們須要知道Block是如何執行代碼的。經過上面的原理分析,咱們知道在執行block的代碼就至關於調用__main_block_func_0函數。該函數指針被保存在__block_impl結構體的FuncPtr中。咱們知道將FuncPtr指向咱們本身的函數就能夠完成切換。

第二題分析

知道第一題如何解答以後,第二題相對來講簡單一點,其實就是在咱們自定義函數的時候,這個函數須要包括咱們傳入的參數,以及原有實現。

那麼經過一個函數指針能夠保存原有實現,在自定義函數中經過函數指針調用原有函數便可,而參數是自己__main_block_func_0函數就有的,咱們只要確保咱們自定義函數的參數列表與__main_block_func_0函數的參數列表一致就行。

第三題分析

這道題目說白了就是在一個適當的時機去調用HookBlockToPrintArguments。經過fishhook這個框架,咱們能夠動態的修改C語言函數。那如何肯定這個C語言函數呢?這個函數須要有這麼一個功能,它能夠拿到原來的block對象,通過替換再從新返回。

在ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上,它會調用一個copy函數即_Block_copy,因此咱們須要動態修改這個函數。

相關文章
相關標籤/搜索