单例模式:保证一个类仅有一个实例,并提供一个访问它的全局变量点。
在OC中,构建单例模式较简单的是提供一个类方法getInstance来进行构造和访问。
#import "Count.h"
@implementation Count
static Count* _instance=nil;
+(instancetype)getInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance=[Count new];
});
return _instance;
}
@end
首先声明一个全局的Count类对象_instance,作为单例。 类方法中使用了GCD中的dispatch_once方法,这里涉及到了多线程,使用 dispatch_once
方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance=[Count new];
});
但这样的话,使用构造方法和使用getInstance类方法构造的对象并不是同一个,也就是这个类初始化了两次,如下图。
所以,我们需要重写一下alloc方法,来保证Count类只初始化了一次。
+(instancetype)alloc{
if(_instance){
return _instance;
}
return [super alloc];
}
然后我们再运行 看看使用构造方法和类方法的情况
这里我们可以看到c1和c2的地址相同,说明Count类只初始化了一次。
同时为了避免copy和mutablecopy,我们可以选择重写或者禁用这两个方法。
附上完整代码:
#import "Count.h"
@implementation Count
static Count* _instance=nil;
+(instancetype)alloc{
if(_instance){
return _instance;
}
return [super alloc];
}
+(instancetype)getInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance=[Count new];
});
return _instance;
}
@end
GCD
前面在构造单例的时候,使用了GCD的dispatch_once 方法,现在说一下GCD
首先,什么是GCD?
GCD (Grand Central Dispatch)
它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
使用GCD有哪些好处?
- GCD 可用于多核的并行运算;
- GCD 会自动利用更多的 CPU 内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
在GCD中有两个重要的概念:任务 和 队列
任务:就是要执行的代码块,在block中的代码。
队列:就是存放任务的一个队列,具有数据结构中队列的性质,先进先出。
任务执行方式有两种:同步执行 和 异步执行
- 同步执行(sync):
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
队列也有两种:串行队列 和 并发队列
- 串行队列(Serial Dispatch Queue):
- 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列的并发功能只有在异步(dispatch_async)方法下才有效。
两者的具体区别为:
GCD的使用
GCD 的使用步骤其实很简单,只有两步:
- 创建一个队列(串行队列或并发队列);
- 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务
可以使用dispatch_queue_create来创建队列,该方法有两个参数,第一个参数为标识符,可填可不填, 第二个为所创建的队列类型 DISPATCH_QUEUE_SERIAL为串行队列 DISPATCH_QUEUE_CONCURRENT为并行队列。
//创建串行队列
dispatch_queue_t queue= dispatch_queue_create("test1", DISPATCH_QUEUE_SERIAL);
//创建并行队列
dispatch_queue_t queue=dispatch_queue_create("test2", DISPATCH_QUEUE_CONCURRENT);
GCD还提供了主队列,主队列为串行队列,使用dispatch_get_main_queue()得到主队列
所有放到主队列中的任务都会放到主线程中执行。
//主队列的获取方法
dispatch_queue_t mainQueue=dispatch_get_main_queue();
GCD还有一个全局队列,为并发队列,使用dispatch_get_global_queue()得到。
dispatch_queue_t queue=dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)
第一个参数为队列的优先级,一般为DISPATCH_QUEUE_PRIORITY_DEFAULT,第二个参数一般为0;
例子分析:
首先添加两个方法
-(void)startCount{
dispatch_queue_t queue= dispatch_queue_create("test1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"this is first");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"this is second");
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"this is third");
NSLog(@"%@",[NSThread currentThread]);
});
}
-(void)asyncCount{
dispatch_queue_t queue=dispatch_queue_create("test2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//NSLog(@"this is the first in context");
//[NSThread sleepForTimeInterval:2];
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
//NSLog(@"this is the second in context");
//[NSThread sleepForTimeInterval:2];
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
//NSLog(@"this is the third in context");
//[NSThread sleepForTimeInterval:2];
NSLog(@"%@",[NSThread currentThread]);
});
}
startCount创建了一个串行队列,并且同步执行了三个任务
asyncCount创建了一个并发队列,并且异步执行了三个任务
我们运行一下程序,执行两个方法
#import <Foundation/Foundation.h>
#import "Count.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Count* c1=[[Count alloc]init];
[c1 asyncCount];
[c1 startCount];
}
return 0;
}
2021-09-27 01:42:17.550509-0700 GCD[3337:37060] this is first
2021-09-27 01:42:17.550834-0700 GCD[3337:37085] <NSThread: 0x1006007c0>{number = 2, name = (null)}
2021-09-27 01:42:17.550915-0700 GCD[3337:37086] <NSThread: 0x100700210>{number = 3, name = (null)}
2021-09-27 01:42:17.550931-0700 GCD[3337:37087] <NSThread: 0x100505070>{number = 4, name = (null)}
2021-09-27 01:42:17.550942-0700 GCD[3337:37060] <NSThread: 0x10050b280>{number = 1, name = main}
2021-09-27 01:42:17.550956-0700 GCD[3337:37060] this is second
2021-09-27 01:42:17.550973-0700 GCD[3337:37060] <NSThread: 0x10050b280>{number = 1, name = main}
2021-09-27 01:42:17.550980-0700 GCD[3337:37060] this is third
2021-09-27 01:42:17.550989-0700 GCD[3337:37060] <NSThread: 0x10050b280>{number = 1, name = main}
Program ended with exit code: 0
首先,我们知道利用NSThread的currentThread得到的是当前线程,即上面的number表示的为第几个线程。
我们打个断点进行分析,可以通过Xcode看到下图
显示了我们创建的队列test1和test2,并且异步执行并发队列中的三个任务创建了三个线程
我们来看输出结果
第一句
2021-09-27 01:42:17.550509-0700 GCD[3337:37060] this is first
这个是第二个方法,startCount的第一个输出,属于线程1,我们知道同步执行是无法开辟线程的,所以只有Thread1存在。
为什么第二个方法反而会比第一个asyncCount先输出结果?
因为asyncCount中为异步执行,不需要等它结束再去运行下一部分,将任务添加到队列后,无需等待,就开始运行startCount方法,这时才输出this is first;
同时,我们注意到startCount创建的队列在的线程为1,所以能判断是startCount先执行任务,创建线程。
第二句-第四句
2021-09-27 01:56:09.143174-0700 GCD[3766:41073] <NSThread: 0x100687a60>{number = 2, name = (null)}
2021-09-27 01:56:09.143180-0700 GCD[3766:41075] <NSThread: 0x102a003b0>{number = 3, name = (null)}
2021-09-27 01:56:09.143180-0700 GCD[3766:41074] <NSThread: 0x1005073c0>{number = 4, name = (null)}
我们可以看到这是asyncCount方法的输出结果,输出了三个任务所在的三个线程,并且是按顺序输出的
也就是说asyncCount开辟了三个线程,为每一个任务开辟了一个线程
当我再运行一次,输出结果的这三行为:
2021-09-27 01:56:09.143174-0700 GCD[3766:41073] <NSThread: 0x100687a60>{number = 2, name = (null)}
2021-09-27 01:56:09.143180-0700 GCD[3766:41075] <NSThread: 0x102a003b0>{number = 4, name = (null)}
2021-09-27 01:56:09.143180-0700 GCD[3766:41074] <NSThread: 0x1005073c0>{number = 3, name = (null)}
但这三个线程并不是按顺序输出的,Thread4比Thread3要先输出,为什么?
按我个人的理解为,Thread3先创建,Thread4后创建,但是在抢占资源时,Thread4要比Thread3抢占了更多的资源,先结束。
为了验证,我又多次运行程序,可以得到这三行的顺序是不定的,也就可以看到各个线程抢占资源的情况还是随机的
第五句-第九句
2021-09-27 02:02:12.252049-0700 GCD[3957:42833] <NSThread: 0x10050b280>{number = 1, name = main}
2021-09-27 02:02:12.252065-0700 GCD[3957:42833] this is second
2021-09-27 02:02:12.252083-0700 GCD[3957:42833] <NSThread: 0x10050b280>{number = 1, name = main}
2021-09-27 02:02:12.252093-0700 GCD[3957:42833] this is third
2021-09-27 02:02:12.252127-0700 GCD[3957:42833] <NSThread: 0x10050b280>{number = 1, name = main}
为startCount的结果,这时,Thread2,Thread3,Thread4线程都结束了,只有Thread1在运行。从结果可以看出Thread1中的三个任务在同一个线程。
但是通过上面的分析得到各个线程抢占资源是随机的,所以Thread1也应该如此。然后,在尝试多次后,出现了各个线程比较混乱的情况
2021-09-27 02:07:28.992934-0700 GCD[4121:44166] this is first
2021-09-27 02:07:28.993211-0700 GCD[4121:44179] <NSThread: 0x10200a400>{number = 3, name = (null)}
2021-09-27 02:07:28.993228-0700 GCD[4121:44166] <NSThread: 0x1021035e0>{number = 1, name = main}
2021-09-27 02:07:28.993243-0700 GCD[4121:44166] this is second
2021-09-27 02:07:28.993257-0700 GCD[4121:44166] <NSThread: 0x1021035e0>{number = 1, name = main}
2021-09-27 02:07:28.993265-0700 GCD[4121:44166] this is third
2021-09-27 02:07:28.993273-0700 GCD[4121:44180] <NSThread: 0x1020001f0>{number = 4, name = (null)}
2021-09-27 02:07:28.993275-0700 GCD[4121:44166] <NSThread: 0x1021035e0>{number = 1, name = main}
2021-09-27 02:07:28.993273-0700 GCD[4121:44178] <NSThread: 0x100583da0>{number = 2, name = (null)}