• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Objective-C中的Block

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

可以用一句话来表示Block:带有自动变量(局部变量)匿名函数

在iOS中使用“^”来声明一个Block。Block的内容是包含在“{}”中的,并且和C语言一样用“;”来表示语句的结束,标准语法如下所示:

//完整语法
^ 返回值类型 参数列表 表达式

//省略返回值
^ 参数列表 表达式

//省略参数列表
^ 返回值类型 表达式

//省略返回值和参数列表
^ 表达式

从上面可以看到,Block和函数很相似,具体体现在这些方面:

  1. 可以保存代码(对应表达式);
  2. 有返回值(对应返回值类型);
  3. 有形参(对应参数列表);
  4. 调用方式一样。

我们通常使用如下形式将Block赋值给Block类型变量,示例代码如下:

int  multiplier = 7;

int (^myBlock)(int) = ^(int num){ 
    return multiplier * num; 
};

NSLog(@"%d",myBlock(3));

采用这种方式在函数参数或返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以使用typedef来解决该问题。

示例1:没有使用typedef

- (void)loadDataFromUrl:(void(^)(NSString *))retData
{
}

示例2:使用typedef

typedef void(^RetDataHandler)(NSString *);
- (void)loadDataFromUrl:(RetDataHandler)retData
{
    
}

从上面的代码可以看到,使用typedef声明之后,在方法中传递block参数时,更容易理解。

Block的强大之处是:在声明它的范围里,所有变量都可以为其所捕获。下面我们来看看自动变量。

2.自动变量

从上面Block语法的介绍中,我们可以理解“带有自动变量(局部变量)匿名函数”中的匿名函数。那么“带有自动变量(局部变量)”是什么呢?这个在Block中表现为“截获自动变量值”。示例如下:

int iCode = 10;
NSString *strName = @"Tom";
       
void (^myBlock)(void) = ^{
   // 结果:My name is Tom,my code is 10
   NSLog(@"My name is %@,my code is %d", strName, iCode);
};
        
iCode = 20;
strName = @"Jim";
       
myBlock();
// 结果:My name is Jim,my code is 20
NSLog(@"My name is %@,my code is %d", strName, iCode);

从代码中可以看到,Block表达式截获所使用的自动变量iCode和strName的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中所用的自动变量的值也不会影响Block执行时自动变量的值,这就是自动变量值的截获。

如果我们想在Block中修改截获的自动变量值,会有什么结果?咱们做个尝试: 

从上面可以看到,该源代码会产生编译错误。若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符,示例如下:

__block NSString *strName = @"Tom";
        
void (^myBlock)(void) = ^{
   strName = @"Sky";
};
strName = @"Jim";
      
myBlock();
// 结果:My name is Sky
NSLog(@"My name is %@",strName);

需要说明的是,对于截获的自动变量,调用变更该对象的方法是没有问题的:即赋值给截获的自动变量会产生编译错误,但使用截获的自动变量的值却不会有任何问题

3.如何在代码中创建Block?

3.1不带参数和返回值的block

- (void)testBlockOne
{
    void (^myBlock)(void) = ^{
        NSLog(@"Hello Block One");
    };
  
    NSLog(@"%@",myBlock);
    myBlock();
}

3.2带参数的block

- (void)testBlockTwo{
    void (^myBlock)(NSString *) = ^(NSString *str){
        NSLog(@"Hello Block %@",str);
    };
    
    NSLog(@"%@",myBlock);
    myBlock(@"ligf");
}

3.3带参数和返回值的block

- (void)testBlockThree{
    int (^myBlock)(NSString *,int) = ^(NSString *str,int code){
        NSLog(@"Hello Block %@,code is %d", str, code);
        return 1;
    };
    
    NSLog(@"%@",myBlock);
    int iRet = myBlock(@"ligf",3);
    NSLog(@"%d",iRet);
}

4.block实现页面传值

4.1先用传统的Delegate来进行示例

  WebServicesHelper类:

@class WebServicesHelper;

@protocol WebServicesHelperDelegate <NSObject>

- (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher
           didFinishWithData:(NSString *)data;
- (void)networkFecherFailed:(WebServicesHelper *)networkFetcher
                      error:(NSError *)error;

@end
 
@interface WebServicesHelper : NSObject

@property (nonatomic, retain) NSURL *url;
@property (nonatomic, assign) id<WebServicesHelperDelegate> delegate;

- (id)initWithUrl:(NSURL *)url;
- (void)startDownload;

@end
- (id)initWithUrl:(NSURL *)url
{
    self = [super init];
     
    if (self)
    {
        self.url = url;
    }
    return self;
}

- (void)startDownload
{
    NSError *error = nil;
    NSString *str = [NSString stringWithContentsOfURL:self.url encoding:NSUTF8StringEncoding error:&error];
    
    if (error)
    {
        [_delegate networkFecherFailed:self error:error];
    }
    else
    {
        [_delegate networkFecherSuccess:self didFinishWithData:str];
    }
}

DownloadByDelegate类:

#import "WebServicesHelper.h"

@interface DownloadByDelegate : NSObject<WebServicesHelperDelegate>
- (void)fetchUrlData;
@end

@implementation DownloadByDelegate

- (void)fetchUrlData
{
    NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"];
    WebServicesHelper *webServicesHelper = [[WebServicesHelper alloc] initWithUrl:url];
    webServicesHelper.delegate = self;
    [webServicesHelper startDownload];
}

- (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher
           didFinishWithData:(NSString *)data
{
    NSLog(@"%@",data);
}
 
- (void)networkFecherFailed:(WebServicesHelper *)networkFetcher
                      error:(NSError *)error;
{
    NSLog(@"%@",error);
}

@end

调用:

DownloadByDelegate *downloadByDelegate = [[DownloadByDelegate alloc] init];
[downloadByDelegate fetchUrlData];

4.2再看看用Block的实现

DownloadByBlock类:

typedef void(^NetworkFetcherCompletionHandler) (NSString *data, NSError *error);

@interface DownloadByBlock : NSObject

- (id)initWithUrl:(NSURL *)url;
- (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion;

@end
@implementation DownloadByBlock
{
    NSURL       *_url;
}
 
- (id)initWithUrl:(NSURL *)url
{
    self = [super self];
     
    if (self)
    {
        _url = url;
    }
    
    return self;
}
 
- (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion
{
    NSError *error;
    NSString *str = [NSString stringWithContentsOfURL:_url encoding:NSUTF8StringEncoding error:&error];
    completion(str,error);
}

@end

调用: 

DownloadByBlock *downloadByBlock = [[DownloadByBlock alloc] initWithUrl:[NSURL URLWithString:@"http://www.baidu.com"]];
[downloadByBlock startWithCompletionHandler:^ (NSString *data, NSError *error){
   NSLog(@"%@",data);
}];

从上面的代码可以明显看到,使用Block方式,代码的可读性更高,使用也更加的方便。

5.Block存储域

先看一个示例:

    int (^myBlockOne)(int,int) = ^ (int a, int b) {
        return a + b;
    };
    //myBlockOne = <__NSGlobalBlock__: 0x101f1d230>
    NSLog(@"myBlockOne = %@", myBlockOne);
    
    int base = 100;
    int (^myBlockTwo)(int,int) = ^ (int a, int b) {
        return base + a + b;
    };
    //MRC:myBlockTwo = <__NSStackBlock__: 0x7fff5dce6520>
    //ARC:myBlockTwo = <__NSMallocBlock__: 0x6000002441d0>
    NSLog(@"myBlockTwo = %@", myBlockTwo);
    
    int (^myBlockThree)(int,int) = [[myBlockTwo copy] autorelease];
    //myBlockThree = <__NSMallocBlock__: 0x6080000499c0>
    NSLog(@"myBlockThree = %@", myBlockThree);

从上面的代码可以看到,Block在内存中的位置可以分为三种类型:__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__

  • __NSGlobalBlock__:与全局变量一样,该类对象存储在程序的数据区域(.data区)中;
  • __NSStackBlock__:从名称中可以看到含有“Stack”,即该类对象存储在栈上;位于栈上的Block对象,函数返回后Block将无效,变成野指针;
  • __NSMallocBlock__:该类对象由malloc函数分配的内存块(堆)中。

其中在全局区域和堆里面存储的对象是相对安全的,但是在栈区里面的变量是危险的,有可能造成程序的崩溃,因此在iOS中如果使用block的成员变量或者属性时,需要将其copy到堆内存中。

上面的例子中,myBlockOne和myBlockTwo的区别在于:myBlockOne没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使myBlockOne与函数没有任何区别。myBlockTwo与myBlockOne唯一不同是的使用了局部变量base,在定义(注意是定义,不是运行)myBlockTwo时,局部变量base当前值被截获到栈上,作为常量供Block使用。执行下面代码,结果是103,而不是203。

int base = 100;
int (^myBlockTwo)(int,int) = ^ (int a, int b) {
   return base + a + b;
};
base = 200;
NSLog(@“%d", myBlockTwo(1, 2));

我们再看一段代码,大家思考一下这段代码有没有问题?

    int base = 100;
    void(^myBlock)();
    
    if (YES)
    {
        myBlock = ^{
            NSLog(@"This is ture,%d", base);
        };
    }
    else
    {
        myBlock = ^{
            NSLog(@"This is false,%d", base);
        };
    }
    
    myBlock();

表面上看,和我们以前给变量赋值的语句没什么太大的差异,那么是不是没有问题呢?答案是NO。在定义这个块的时候,其所占的内存区域是分配在栈中的,块只在定义它的那个范围内有效,也就是说这个块只在对应的if或else语句范围内有效。当离开了这个范围之后,编译器有可能把分配给块的内存覆写掉。这样运行的时候,若编译器未覆写待执行的块,则程序照常运行;若覆写,则程序崩溃。

5.1__NSGlobalBlock__的实现

我们先写一段生成__NSGlobalBlock__的代码:

#include <stdio.h>
void (^gofBlock)(void) = ^{
    printf("Hello, Gof");
};
int main(int argc, char * argv[]) {
    gofBlock();
    
    return 0;
}

对上面的代码使用“clang -rewrite-objc main.m”进行编译,生成后的代码整理之后如下:

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

struct __gofBlock_block_impl_0 {
  struct __block_impl impl;
  struct __gofBlock_block_desc_0* Desc;
  __gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) {

    printf("Hello, Gof");
}

static struct __gofBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __gofBlock_block_desc_0_DATA = { 0, sizeof(struct __gofBlock_block_impl_0)};

static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA);
void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0);

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

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

    return 0;
}

我们将源代码分成几个部分来逐步理解。

第一部分:源码中的Block语法

^{
        printf("Hello, Gof");
    };

可以看到,变换后的代码中也含有相同的表达式:

static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) {

    printf("Hello, Gof");
}

从代码可以看到,通过Blocks使用的匿名函数,实际上被作为简单的C语言函数来处理。该函数名根据Block语法所属的函数名和该Block语法在函数出现的顺序值(这里为0)来命名。函数的参数__cself为指向__gofBlock_block_impl_0结构体的指针。

第二部分:__gofBlock_block_impl_0结构体 

struct __gofBlock_block_impl_0 {
  struct __block_impl impl;
  struct __gofBlock_block_desc_0* Desc;
  __gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我们先不看构造函数,__gofBlock_block_impl_0包含两个成员变量:impl和Desc。

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

static struct __gofBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

从这两个结构体的声明,可以知道:impl包含某些标志、所需区域、函数指针等信息;Desc包含所需区域、Block大小信息。

接下来我们看看__gofBlock_block_impl_0构造函数的调用:

static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA);
void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0);

继续看__gofBlock_block_impl_0构造函数的两个参数:

  1. __gofBlock_block_func_0是由Block语法转换的C语言函数指针;
  2. __gofBlock_block_desc_0_DATA是作为静态全局变量初始化的__gofBlock_block_desc_0结构体实例指针。

通过参数的配置,我们来看看__gofBlock_block_impl_0结构体的初始化:

isa = &_NSConcreteGlobalBlock;
Flags
= 0;
Reserved
= 0;
FuncPtr
= __gofBlock_block_func_0;
Desc = &__gofBlock_block_desc_0_DATA;

关于_NSConcreteGlobalBlock,我们可以看看RunTime之类与对象。实际上,__gofBlock_block_impl_0结构体相当于基于objc_object结构体的OC类对象的结构体。对其中的isa成员变量初始化,_NSConcreteGlobalBlock相当于objc_class结构体实例。在将Block作为OC的对象处理时,关于该类的信息放置于_NSConcreteGlobalBlock中。

第三部分:Block的调用。

gofBlock();

变换后的代码:

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

去掉转换部分:

(*gofBlock->impl.FuncPtr)(gofBlock);

这实际上就是使用函数指针调用函数__gofBlock_block_func_0。另外,我们也可以看到,__gofBlock_block_func_0函数的参数__cself指向Block值。

总结一下:

  • block 实际是一个对象,它主要由 一个 impl 和 一个 Desc 组成。
  • impl.FuncPtr是实际的函数指针,在这里它指向 __gofBlock_block_func_0。
  • Desc 用于描述当前这个 block 的附加信息的,包括结构体的大小,需要 capture 和 dispose 的变量列表等。结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __gofBlock_block_impl_0 这个结构体中,使其体积变大。

5.2__NSStackBlock__的实现

先看代码:

#include <stdio.h>

int main(int argc, char * argv[]) {
    int a = 18;
    void (^gofBlock)(void) = ^{
        printf("I have %d ages", a);
    };
    gofBlock();
    return 0;
}

对上面的代码使用“clang -rewrite-objc main.m”进行编译,生成后的代码整理之后如下:

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

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    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

        printf("I have %d ages", a);
    }

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 = 18;
    void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);
    return 0;
}

这和上面转换的源代码有一点点差异。

首先,Block语法表达式中的自动变量被作为成员变量追加到了__main_block_impl_0结构体中。

其次,在调用__main_block_impl_0构造函数初始化的时候,对由自动变量追加的成员变量进行了初始化。

void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

通过参数的配置,__main_block_impl_0结构体构造函数的初始化:

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
a = 18;

再次,Block匿名函数的实现:

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

        printf("I have %d ages", a);
    }

截获到__main_block_impl_0结构体实例的成员变量上的自动变量a,该变量在Block语法表达式之前被声明定义。

总结一下:

  • isa 指向 _NSConcreteStackBlock,说明这是一个分配在栈上的实例。
  • main_block_impl_0 中增加了一个变量 a,在 block 中引用的变量 a 实际是在申明 block 时,被复制到 main_block_impl_0 结构体中的那个变量 a。因为这样,我们就能理解,在 block 内部修改变量 a 的内容,不会影响外部的实际变量 a。
  • main_block_impl_0 中由于增加了一个变量 a,所以结构体的大小变大了,该结构体大小被写在了 main_block_desc_0 中

现在我们修改一下上面的代码,在变量前面增加 __block 关键字:

#include <stdio.h>

int main(int argc, char * argv[]) {
    __block int a = 18;
    void (^gofBlock)(void) = ^{
        a = 20;
        printf("I have %d ages", a);
    };
    gofBlock();
    printf("a variable is %d", a);
    return 0;
}

使用“clang -rewrite-objc main.m”进行编译:

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

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

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) = 20;
        printf("I have %d ages", (a->__forwarding->a));
    }

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), 18};
    void (*gofBlock)(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 *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);
    printf("a variable is %d", (a.__forwarding->a));
    return 0;
}

从编译之后的源代码可以看到,只加了一个__block关键字,源码数量大大增加。

首先,我们看看这句: 

__block int a = 18;

编译之后:

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};

去掉类型转换:

 __Block_byref_a_0 a = {
    0,
    &a,
     0, 
    sizeof(__Block_byref_a_0), 
    18
};

从源码可以看到,这个__block变量变成了__Block_byref_a_0结构体类型的自动变量。

接下来,我们看看Block匿名函数的实现:

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) = 20;
        printf("I have %d ages", (a->__forwarding->a));
    }

__Block_byref_a_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量a。

总结一下:

  • 源码中增加一个名为 __Block_byref_a_0 的结构体,用来保存我们要 capture 并且修改的变量 a。
  • main_block_impl_0 中引用的是 __Block_byref_a_0 的结构体指针,这样就可以达到修改外部变量的作用。
  • __Block_byref_a_0 结构体中带有 isa,说明它也是一个对象。
  • 我们需要负责 __Block_byref_a_0 结构体相关的内存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。

5.3__NSMallocBlock__的实现

__NSMallocBlock__类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中。以下是一个 block 被 copy 时的示例代码 (来自 这里),可以看到,在第 8 步,目标的 block 类型被修改为 _NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1
    if (!arg) return NULL;
    // 2
    aBlock = (struct Block_layout *)arg;
    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8
    result->isa = _NSConcreteMallocBlock;
    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

5.4Block的copy、retain、release


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Objective-C下对象生命周期以及使用策略发布时间:2022-07-12
下一篇:
常见排序算法-采用Objective-c实现发布时间:2022-07-12
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap