OC基础教程10-代码块和并发性

本文深入探讨了代码块的概念及其在C语言中的实现方式,包括自动绑定与托管绑定。同时,文章解释了并发性的概念,并通过Grand Central Dispatch(GCD)在iOS设备上的应用实例,展示了如何在多核环境下执行多个任务。此外,文章还介绍了如何在iOS开发中使用并发队列,如连续队列、并发队列和主队列,以及如何在代码块中访问外部变量。最后,文章通过实例展示了如何利用代码块实现异步编程中的函数回调。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

代码块,一块代码,一个简单的匿名函数体。


目标

理解代码块
理解如何让设备同时执行多个任务,也就是并发性。


内容

1.代码块

代码块对象(简称“代码块”)是对C语言中函数的扩展。
除了函数中的代码,代码块还包含变量绑定。代码块有时也被称为闭包(closure).
代码块包含两种类型的绑定:自动型与托管型。
自动绑定(automatic binding)使用的是栈中的内存。
托管绑定(manageed binding)使用的是堆中的内存。

因为代码块实际上是由C语言实现的,所以它们在各种以C作为基础的语言内都是有效的,包括Objective-C、C++以及Objective-C++。

在研究代码块之前,先来讨论一下C语言中的函数指针。为什么呢?因为代码块借鉴了函数指针的语法。
与函数指针类似,代码块具有以下特征:
1)返回类型可以手动声明也可以由编译器推导;
2)具有指定类型的参数列表;
3)拥有名称。
声明函数指针:

void (*my_func)(void);

声明代码块:

void(^my_block)(void);

例:

int(^square_block)(int number) = ^(int number){
return (number *number);
};
int result = square_block(5);
printf("Result = %d\n",result);

等号前面是代码块的定义,而等号后面是实现内容。
一般我们可以用如下关系来表示它们:

<returntype>(^blockname)(list of arguments) = ^(arguments){body;};

编译器可以通过代码块的内容推导出返回类型,所以可以省略,如果代码块没有参数,也可以省略。

例:

void (^theBlock)() = ^{printf("Hello Block!\n");};

使用时可以像函数一样使用。

int result = square_block(5);

2.直接使用代码块

使用代码块时通常不需要创建一个代码块变量,而是在代码内联代码块的内容。
通常,在需要代码块做参数的方法或函数里直接使用。

NSArray *array = [NSArray arrayWithObjects:@"Amir",@"Mishal",@"Irrum",nil];
NSLog(@"Unsorted Array %@",array);
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1,NSString *object2){
    return [object1 compare:object2];
}];
NSLog(@"Sorted Array %@",sortedArray);

本地变量

本地变量就是与代码块在同一范围内声明的变量。

typedef double(^MKSampleMultiplyBlockRef)(void);
double a = 10,b = 20;

MKSampleMultiplyBlockRef multply = ^(void){return a*b;};
NSLog(@"%f",multiply());
a = 20;
b = 50;
NSLog(@"%f",multiply());

第二个NSLog会输出什么呢?
结果是200.因为变量就是本地的,代码块会在定义时复制并保存它们的状态。

全局变量

static double a = 10,b = 20;

MKSampleMultiplyBlockRef multiply = ^(void){return a * b;};
NSLog(@"%f",multiply());
a = 20;
b = 50;
NSLog(@"%f",multiply());

在异步编程时我们经常进行函数回调,由于函数调用是异步执行的,我们如果想让一个操作执行完之后执行另一个函数,则无法按照正常代码书写顺序进行编程,因为我们无法获知前一个方法什么时候执行结束,此时我们经常会用到代码块。

代码块(Block)就是一个函数体(匿名函数),它是Objective-C对于闭包的实现,在代码块中我们可以持有或引用局部变量,同时利用Block你可以将一个操作作为一个参数进行传递(是不是想起了C语言中的函数指针)。

在下面的例子中我们将使用Block实现上面的点击监听操作:

KCButton.h

//
//  KCButton.h
//  Protocol&Block&Category
//

#import <Foundation/Foundation.h>
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);

@interface KCButton : NSObject

#pragma mark - 属性
#pragma mark 点击操作属性
@property (nonatomic,copy) KCButtonClick onClick;
//上面的属性定义等价于下面的代码
//@property (nonatomic,copy) void(^ onClick)(KCButton *);

#pragma mark - 公共方法
#pragma mark 点击方法
-(void)click;
@end

KCButton.m

//
//  KCButton.m
//  Protocol&Block&Category
//

#import "KCButton.h"

@implementation KCButton

-(void)click{
    NSLog(@"Invoke KCButton's click method.");
    if (_onClick) {
        _onClick(self);
    }
}

@end

main.m

//
//  main.m
//  Protocol&Block&Category
//

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

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

    KCButton *button=[[KCButton alloc]init];
    button.onClick=^(KCButton *btn){
        NSLog(@"Invoke onClick method.The button is:%@.",btn);
    };
    [button click];
    /*结果:
     Invoke KCButton's click method.
     Invoke onClick method.The button is:<KCButton: 0x1006011f0>.
     */

    return 0;
}

上面代码中使用Block同样实现了按钮的点击事件,关于Block总结如下:
1. Block类型定义:返回值类型(^ 变量名)(参数列表)(注意Block也是一种类型);
2. Block的typedef定义:返回值类型(^类型名称)(参数列表);
3. Block的实现:^(参数列表){操作主体};
4. Block中可以读取块外面定义的变量但是不能修改,如果要修改那么这个变量必须声明_block修饰;

并发性

现在的iOS设备都是多核的,这意味着你可以在同一时间进行多项任务。能够同一时间进行多项任务的程序称为并发的(concurrent)程序。
为了减轻在多核上编程的负担,苹果公司引入了Grand Central Dispatch,我们称之为GCD。

Objective -C提供了一个语言级别的(language-level)关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。

@sychronized(theObject)
{

}

它可以确保不同的线程会连续地访问临界区的代码。
若你定义了一个属性并且没有指定关键字nonatomic作为属性的特性,编译器会生成强制彼此互斥的getter和setter方法。
编译器生成了synchronize(mutex,atomic)语句来确保彼此互斥。这样设置代码和变量会产生一些消耗,它会比直接访问更慢一些。

调度队列

GCD可以使用调度队列(dispatch queue),它与线程很相似但使用起来更简单。
只需写下你的代码,把它指派为一个队列,系统就会执行它。你可以同步或异步执行任意代码。

一共有以下3种类型的队列:
1.连续队列:每个连续队列都会根据指派的顺序执行任务。
2.并发队列:每个并发队列都能并发执行一个或多个任务。
3.主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。

连续队列

有时有一连串任务需要按照一定的顺序执行,这时便可以使用连续队列。任务执行顺序为先入先出(FIFO):只要任务是异步提交的,队列会确保任务根据预定的顺序执行。这些队列都是不会发生死锁的。

死锁(deadlock)是一个令人不悦的情况,指的是两个或多个任务在等待他方运行结束,就像是几辆汽车同时位于一个很拥挤的停车场里。

使用连续队列

dispatch_queue_t my_serial_queue;
my_serial_queue = dispatch_queue_create("com.apress.MySerialQueue1",NULL);

参数是队列名称,第二个负责提供队列的特性(现在用不到,所以必须是NULL)。
当队列创建好之后,就可以给它指派任务。

并发队列

并发调度适用于那些可以并行运行的任务。并发队列也遵从先入先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。一次所运行的任务数量是无法预测的。

每个应用程序都有3种并发队列可以使用:

  • 高优先级(high):DISPATCH_QUEUE_PRIORITY_HIGH
  • 默认优先级(default): DISPATCH_QUEUE_PRIORITY_DEFAULT
  • 低优先级(low): DISPATCH_QUEUE_PRIORITY_LOW

例:

dispatch_queue_t myQueue;
myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)

主队列

使用dispatch_get_main_queue可以访问与应用程序主线程相关的连续队列。

dispatch_queue_t main_queue = dispatch_get_main_queue(void);

因为这个队列与主线程相关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步方式使用这个队列,提交多个任务并在它们 操作完毕后执行一些动作。

获取当前队列

你可以通过调用dispatch_get_current_queue()来找出当前运行的队列代码块。
如果你在代码块对象之外调用了这个函数,则它将会返回主队列。

dispatch_queue_t myQueue = dispatch_get_current_queue();

添加任务

有两种方式可以向队列中添加任务。
同步:队列会一直等待前面任务结束。
异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。


总结+废话

介绍了代码块,以及代码块的使用。
并发性有点复杂。在介绍UI之后还会详细讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值