iOS中的网络和多线程编程(八)

简介: iOS中的网络和多线程编程(八)

block与GCD


“块”(block)与“大中枢派发”(GCD)是苹果公司为解决多线程编程而一起引入的解决方案。


block是一种可以在C、C++以及Objective-C代码中使用,类似于“闭包”(closure)的代码块,借助block机制,开发者可以将代码像对象一样在不同的上下文环境中进行传递。


GCD是一种与block有关的技术,它主要用于优化应用程序以支持多核处理器的调度。开发者可以将块排入队列中,由GCD负责处理所有的调度事宜。GCD会根据系统资源情况,适时地创建、复用、销毁后台线程(Background Thread),以便处理每个队列。和其他的多线程技术方案相比,使用起来更加简单和方便。


block有哪几种定义的方式


在Objective-C中,block定义包含了block的类型声明和实现,基本形式如下:


返回值类型(^block名称)(参数类型)=^(参数类型和参数名){};

其中,返回值类型和参数可以是空。如果有参数,那么在定义block的时候,必须要标明参数的类型和参数名。所以,block大致有3种细分的定义方式。


1)没有返回值,没有参数的定义方式。


void(^myBlock)() = ^{
    //代码
}


2)有返回值,有参数的定义方式。


int(^myBlock)(int) = ^(int a){
     return a;   
}


3)有返回值,没有参数的定义方式。


int(^myBlock)() = ^{
     return 100;
}


当然,block也有属于自己的类型,就像在Objective-C中,字符串对象属于NSString类型一样。block类型的格式就是:


返回值类型(^)(参数类型)

也就是说,上面第一种定义方式的block类型就是void(^)(),myBlock不是变量名,而是这种block类型的别名。在Objective-C中,可以使用typedef关键字定义block类型,也可以直接使用inline提示符来自动生成block格式。示例代码如下:


/*使用typedef关键字定义block类型*/
    typedef void(^myBlock)();
        myBlock block = ^{
    };
    /*使用inline提示符来自动生成block格式*/
    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>){
        <#statements#>
    };


在ARC环境下,是否需要使用copy关键字来修饰block


先要明确的是,block其实包含两个组成部分,一部分是block所执行的代码,这一部分在编译的时候已经确定;另一部分是block执行时所需要的外部变量值的数据结构。根据block在内存中的位置,系统将block分为3类。


1)NSGlobalBlock:该类型的block类似函数,内存地址位于内存全局区。只要block没有对作用域中局部变量进行引用,此block会被系统设置为该类型。示例代码如下:


- (void)test{
    void(^gBlock1)(int, int) =^(int a, int b){
        NSLog(@"a + b = %d", a + b);
    };
    NSLog(@"%@", gBlock1);
}


以上代码的输出结果是:


<NSGlobalBlock:0x1025e8110>

事实上,对于NSGlobalBlock类型的block,无需做更多的处理,不需要使用retain和copy进行修饰。即使使用了copy,系统也不会改变block的内存地址,操作是无效的。

2)NSStackBlock:该类型的block内存位于栈,其生命周期由函数决定,函数返回后block将无效。


在MRC环境下,若block内部引用了局部变量,此block就会被系统设置为该类型。对于NSStackBlock类型的block,使用retain和release操作都是无效的,必须调用Block_copy()方法,或者使用copy进行修饰,其作用就是将block的内存从栈转移到堆,此时block就会转变为NSMallocBlock类型,这也是一直使用copy修饰block的原因。

在ARC环境下,若block内部引用了局部变量,系统默认使用了copy对block进行修饰,使其变成NSMallocBlock类型。所以在ARC环境下,不需要手动使用copy关键字来修饰block。


3)NSMallocBlock:当对NSStackBlock类型的block进行copy操作后,block就会转为此类型。在MRC环境下,可以使用retain、release等方法手动管理此类型block的生命周期。在ARC环境下,系统会帮助管理此类型block的生命周期。


在block内如何修改block外部变量


在block内部修改block外部变量会造成编译错误,提示变量缺少__block修饰,不可赋值。要想在block内部修改block外部变量,则必须在外部定义变量时,前面加上__block修饰符。示例代码如下:


/*block外部变量*/
__block int var1 = 0;
int var2 = 0;
/*定义block*/
void(^block)(void) =^{
    /*试图修改block外部变量*/
    var1 = 100;
    /*编译错误,在block内部不可对var2赋值*/
    //var2 = 1;
};
/*执行block*/
block();
NSLog(@"修改后的var1:%d",var1);//修改后的var1:100


block内部为何不能直接修改外部变量呢?因为当外部变量没有使用__block修饰符修饰时,block在截获外部的自动变量时会在内部新创建一个新的变量val来保存所截获的外部变量的瞬时值,新变量val成为block的成员变量(Objective-C中block也是对象),之后在block代码中修改的值是成员变量val的值,而不是截获的外部变量的值,所以外部变量的值不会受影响。此时,修改外部变量是先取值并赋值给成员变量val,然后修改val的值。可用下面的代码模拟其原理,假设block对外部变量var进行了加1操作,block使用一个名为block的函数来表示。


int var = 1;
void block(){
    int val = var;
    val += 1;
}


当外部变量使用了__block修饰符进行修饰的时候则是另外一种情形了,此时block并不是截获外部自动变量的瞬时值并保存到自己的新成员变量中,而是保存了对外部变量的指引引用,因此对指针变量的修改会直接影响外部变量的值。此时使用代码模拟其原理如下,依然是block对外部变量var进行加1操作。


__block int var = 1;
void block(){
    int *ptr = &var;
    *ptr += 1;
}


因此,block内部不可以直接修改外部变量,如果要修改外部变量,那么该外部变量必须使用__block修饰符进行修饰,否则编译器会直接进行报错提示。


需要注意的是,此处讨论的是自动变量,而静态变量由于默认传给block的就是地址值,所以是可以直接修改的。另外,全局变量和静态全局变量由于作用域很广,也是可以在block中直接被修改的,编译器也不会报错。


在block中使用self关键字是否一定导致循环引用


在block中使用self关键字并不总会引起循环引用。事实上,只有当block和self相互持有时,才会导致循环引用。由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,此时如果block中的对象又持有了该block,那么就会造成循环引用。典型的场景就是当block作为self的属性使用时,又在block内部调用了self的属性或者方法。示例代码如下:


typedef  void(^block)();
@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock{
    self.myBlock = ^{
        /*其实注释中的代码,同样会造成循环引用*/
        NSString *localString = self.blockString;
    }
}


在上面这个例子中,myBlock和self相互引用了对方。此时,self的销毁依赖于myBlock的销毁,而myBlock的销毁又依赖self的销毁,这样就造成了循环引用,即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放,如图所示。


2466108-9416fd65b24db035.webp.jpg


block循环引用

解决循环引用的关键是断开引用链。在实际开发中,主要使用弱引用(weak reference)的方法来避免循环引用的产生。在ARC环境下,使用__weak修饰符定义一个__weak self的引用,并且在里面使用这个弱引用。使用这种方式对示例代码修改如下:


typedef  void(^block)();
@property (copy, nonatomic) block myBlock;
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock{
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        /*其实注释中的代码,同样会造成循环引用*/
        NSString *localString = weakSelf.blockString;
    };
}


当使用__weak修饰的弱类型self时,block便不会再持有self的引用了,也就不会再产生循环引用了。


下面是不会造成循环引用的几种情况:


1)大部分GCD方法。


示例代码如下:


/* 使用GCD异步执行主队列任务*/
dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});


在例子中,因为self并没有对GCD的block进行持有,只有block持有了self的引用,所以不会造成循环引用。


2)block作为临时变量。在这种情况下,同样self并没有持有block,所以也不会造成循环引用。


3)block执行过程中self对象被释放。事实上,block的具体执行时间不确定,当block被执行的时候block中被__weak修饰的self对象有可能已经被释放了(例如,控制器对象已经被POP了)。当在并发执行,涉及异步服务的时候,这种情况有可能会出现。


对于这种情况,应该在block中使用__strong修饰符修饰self对象,使得在block期间对对象持有,当block执行结束后,解除其持有。示例代码如下:


- (void)testBlock{
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        __strong typeof(self)strongSelf = weakSelf;
        NSString *localString = strongSelf.blockString;
    }
}


目录
相关文章
|
8月前
|
iOS开发
iOS多线程之NSOperationQueue-依赖、并发数、优先级、自定义Operation等最全的使用总结
iOS多线程之NSOperationQueue-依赖、并发数、优先级、自定义Operation等最全的使用总结
232 0
|
8月前
|
安全 调度 C语言
iOS多线程之GCD-同步、异步、并发、串行、线程组、栅栏函数、信号量等全网最全的总结
iOS多线程之GCD-同步、异步、并发、串行、线程组、栅栏函数、信号量等全网最全的总结
495 1
|
12月前
|
安全 算法 编译器
iOS线程安全——锁(二)
iOS线程安全——锁(二)
121 0
|
12月前
|
存储 安全 API
iOS线程安全——锁(一)
iOS线程安全——锁(一)
211 0
|
iOS开发
iOS多线程的初步研究-- dispatch同步
GCD提供两种方式支持dispatch队列同步,即dispatch组和信号量。
148 0
|
API iOS开发
iOS多个线程发起相同请求,避免重复
有时候在调用多个模块时,会对同一个API进行多次请求,但因为内容都是一样的,所以最好就是加上锁,防止重复请求造成网络资源浪费
152 0
|
敏捷开发 安全 Unix
iOS开发 - 在实战中挖掘之线程间的通信方式
iOS开发 - 在实战中挖掘之线程间的通信方式
162 0
|
存储 Java 数据库
JAVA实现网络多线程编程小游戏开发
实验总结:五子棋是一个很简单的游戏,但是如果认真对待,一个代码一个代码的去研究,会收获到很多知识,会打好学习基础。方便以后开发更高、更难的项目时打下稳固的基础。在自己开发的过程中会有各种意想不到的bug,通过查阅资料及询问老师同学进行解决对本身的一个代码能力会有一个质的增长,同时这也是一个非常快乐的过程。有进步,总归是好事。
JAVA实现网络多线程编程小游戏开发