即时通信

以下内容摘自:开发即时通信是选择UDP还是TCP协议

大规模即时通讯软件的总体架构:

1。以UDP协议作为主要数据传输协议

2。服务端使用一个数据库保存信息(分布式数据库)

3。服务端是是分布式的,通过异步多线程技术,集群服务等 通过服务器集群共同运行服务端,对外进行海量信息处理(转发,暂存,广播消息)

4。客户端也属于分布式应用程序,具有一些服务端的功能 在进行语言,视频,文件传输的时候,可由服务端协调 在两个客户端直接进行点对点通信

目录

# 目录 * [一、为什么说Objective-C是一门动态的语言?](#Objective-C)
1

1
2
3
4
5
>线程间通信的体现:
>1. 一个线程传递数据给另一个线程
>2. 在一个线程中执行完特定任务后,转到另一个线程继续执行任务

>附:其他传值方法总结
1
2
3
4
5
6
7
1、通常的做法/不合理的做法
2、合理的做法(使用多线程异步执行)
1)、先说说简单的多线程同步问题(异步线程里没有再开启异步线程)
2)、真正的线程同步问题(异步线程里再开启异步线程)
3)、其他补充
附1:多个请求依次执行
附2:并发数控制
1
...
1
2
3
1、SDWebImage在图片下载及缓存的处理方法
2、SDWebImageDownloader 异步的图片下载器
3、SDWebImageDownloaderOperation 下载操作
1
2
3
1、一次完整的HTTP请求过程
2、TCP三次握手
3、TCP四次挥手
1
2
3
4
5
6
7
8
9
附:整个响应链及事件链
1、完善响应链查找知识点
2、基础概念等详解
2.1 响应者对象(UIResponder)
2.2、UITouch(点击对象)
2.2.1、UITouch的几个主要属性和方法
2.2.2、UITouch的生成场景
2.3、UIEvent(事件对象)
3、响应链的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
1、RunLoop概念

2、RunLoop和线程的关系?

3、RunLoop相关各类关系
3.1 CFRunLoopSourceRef
3.1.附 上题中button点击后,关于RunLoop的过程
3.2 CFRunLoopTimerRef
3.3 CFRunLoopObserverRef

4、RunLoop的应用
附:autoreleasepool 自动释放池
runloop、autorelease pool以及线程之间的关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1、内存管理
(1)、在ObjC中,对象什么时候会被释放(或者对象占用的内存什么时候会被回收利用)?
(2)、那怎么知道对象已经没有被引用了呢?
(3)、autorelease、autoreleasepool(自动释放池)
(4)、autoreleasepool(自动释放池) 
(5)、autoreleasepool里面对象的内存什么时候释放?
(6)、runloop、autorelease pool以及线程之间的关系
(7)、自动释放池怎样创建
(8)、自动释放池使用注意
(9)、自动释放池的应用/什么时候要用@autoreleasepool

2、如何监测内存泄漏

3、循环引用
(1)、NSTimer的循环引用
(2)、block的循环引用
(2.1)、Objective-C中block为何得用copy修饰,能否用其他
(2.2)、block的循环引用
(2.3)、判断该block是否会发生循环引用
(2.4)、为什么masonry的block里引用self不需要weak?
(3)、委托delegate的循环引用
1
2
3
4
5
6
7
1、NSTimer和NSRunLoop的关系?
2、NSTimer使用细节
3、NSTimer的创建
4、NSTimer的循环引用
5、NSTimer使用的优化
6、NSTimer的销毁问题
6.1子线程中NSTimer的创建和销毁问题
  • 十一、谈谈设计模式
  • 十二、如何优化过于臃肿的Controller
  • 十三、谈谈性能优化(功耗)
  • 十四、UITableView的优化、重用
  • 十五、布局 layoutsubview、drawrect等
  • 十六、有逼格的代码
  • 十七、单元测试
  • 十八、APP审核

时间计时器等

NSTimer、NSDate

###iOS时间那点事
经典看前必先看:iOS时间那点事NSCalendar + NSDateComponents
iOS 时区日期处理及定时 (NSDate,NSCalendar,NSTimer,NSTimeZone)

NSTimer

NSTimer 定时器用法总结

自动加一秒(dateByAddingComponents和dateByAddingTimeInterval)

参考:时间与日期处理
涉及知识点:
NSDate的生成
方法①通过NSDateFormatterdateFromString:方法对dateString生成NSDate;

1
2
3
4
5
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];

NSString *dateString = @"2016-08-15 22:55:03";
NSDate *date = [dateFormatter dateFromString:dateString];

方法②通过NSCalendardateFromComponents:方法对dateComponents生成NSDate;(dateComponents由分个字段生成)
[greCalendar dateFromComponents:dateComponents];

1
2
3
4
5
6
7
    NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear:2016];
[dateComponents setMonth:8];
[dateComponents setDay:15];
NSDate *operationDate = [greCalendar dateFromComponents:dateComponents];

NSDate转NSString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
NSDate *operationDate;

- (void)updateTimeExample {
NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear:2016];
[dateComponents setMonth:8];
[dateComponents setDay:15];
operationDate = [greCalendar dateFromComponents:dateComponents];

if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: self selector:@selector(addOneSecondToDate:) userInfo:nil repeats:YES];
}
}

- (void)addOneSecondToDate:(NSTimer *)timer {
// NSDate *operationDate = [timer userInfo];
NSCalendar *greCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

NSDateComponents *offsetDateComponents = [[NSDateComponents alloc] init];
[offsetDateComponents setMinute:1];
[offsetDateComponents setSecond:1];
NSDate *finalDate = [greCalendar dateByAddingComponents:offsetDateComponents toDate:operationDate options:0];
operationDate = finalDate;

//NSDate转NSString
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy年MM月dd HH:mm:ss"];
NSString *dateString = [dateFormatter stringFromDate:finalDate];

self.networkTimeLabel.text = dateString;
}

#####NSJSONSerialization 去除不必要的空格回车
NSJSONSerialization介绍
NSJSONSerialization四个枚举什么意思

1
2
3
4
5
6
// 要去除不必要的空格回车,只需将参数Option设置为0即可。如果设置NSJSONWritingPrettyPrinted会使格式化的json更加可读,即添加空格和换行,但如果设为0,则不添加空格和换行
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary
options:0
error:nil];
NSString *JSON = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];

######颜色渐变 CAGradientLayer
CAGradientLayer简介

线程-①线程基础

线程与网络-①线程基础

# 目录
1
2
3
4
>1、多线程的原理
>2、多线程的优缺点
>3、多线程在iOS开发中的应用
>4、iOS中三种多线程技术(NSThread、NSOperation/NSOperationQueue、GCD)
1
2
3
4
5
>线程间通信的体现:
>1. 一个线程传递数据给另一个线程
>2. 在一个线程中执行完特定任务后,转到另一个线程继续执行任务

>附:其他传值方法总结
1
2
3
4
5
6
7
>1、通常的做法/不好的做法
>2、合理的做法(使用多线程异步执行)
>1)、先说说简单的多线程同步问题(异步线程里没有再开启异步线程)
>2)、真正的线程同步问题(异步线程里再开启异步线程)
>3)、其他补充
>附1:多个请求依次执行
>附2:并发数控制
1
>1、线程安全
1
2
>1、在使用SQLite过程中,如果多条线程同时操作同一数据库会造成什么问题,怎么解决?
>2、串行队列与并行队列的区别

## 一、多线程介绍 > [< 返回目录](#目录)

1、多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)。多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)。如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
思考:如果线程非常非常多,会发生什么情况?
答:CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)。

2、多线程的优缺点

1
2
3
4
5
6
7
8
多线程的优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)

多线程的缺点
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享

3、多线程在iOS开发中的应用

主线程:

一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”

主线程的主要作用:

显示\刷新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件等)

主线程的使用注意:

别将比较耗时的操作放到主线程中。耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。

4、iOS中三种多线程技术(NSThread、NSOperation/NSOperationQueue、GCD)

1.NSThread
  1. 使用nsthread对象建立一个线程非常方便
  2. 但是!要使用nsthread管理多个线程非常困难,不推荐使用
  3. 技巧!使用[NSThread currentThread]跟踪任务所在线程,适用于这三种技术
2.NSOperation/NSOperationQueue

NSOperation和NSOperationQueue相关

  1. 是使用gcd实现的一套objective-c的api
  2. 是面向对象的线程技术
  3. 提供了一些在gcd中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系
1
2
3
4
5
6
7
8
9
NSOperation`是系统提供的抽象的基类,我们使用的时候需要使用继承于它的子类。系统为我们提供了两种继承于`NSOperation`的子类,分别是`NSInvocationOperation`和`NSBlockOperation。

当将操作添加到主操作队列时,所有操作会按照添加到队列中的先后顺序串行依次执行。(主队列是GCD自带的一种特殊串行队列。)
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:op1];

当将任务添加到自定义队列的时候,会开启子线程,操作会并发执行。
NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
[customQueue addOperation:op1];
3.GCD —— grand central dispatch
  1. 是基于c语言的底层api
  2. 用block定义任务,使用起来非常灵活便捷
  3. 提供了更多的控制能力以及操作队列中所不能使用的底层函数

NSOperationQueue与GCD的使用原则和场景

NSOperationQueue与GCD的使用原则和场景

## 三、线程等待(线程同步)、线程依赖、任务顺序问题 > [< 返回目录](#目录)

多个任务中,某个线程的执行依赖其他线程的执行完毕,或者某个线程的执行需等待其他线程执行完毕。

常见场景:
某个页面加载时通过网络请求获得相应的数据,再做某些操作。有时候加载的内容需要通过好几个请求的数据组合而成,比如有两个请求A和B。

多线程同步需求分析图

1、通常的做法/不好的做法:把并发队列变成串行

常见的有:为了省事,会将B请求放在A请求成功的回调中发起,将C请求放在B请求成功的回调中发起,在C的成功回调中将数据组合起来,这样做有明显的问题:

1
2
①、请求如果多了,需要写许多嵌套的请求
②、请求被变成了同步的方式了,这是最大的问题,在网络差的情况下,如果有n个请求,意味着用户要等待n倍于并发请求的时间才能看到内容

2、合理的做法(使用多线程异步执行)

1)、先说说简单的多线程同步问题(异步线程里没有再开启异步线程)

首先我们先来看简单的多线程同步问题(异步线程里没有再开启异步线程),这种时候,我们可以使用的方法有

使用GCD线程组dispatch_group的dispatch_group_notify即可解决问题。其中notify的作用就是在group中的其他操作全部完成后,再操作自己的内容。
② 使用NSOperationQueue中operation的addDependency
③使用dispatch_barrier_async方法。

①、使用GCD线程组dispatch_group的dispatch_group_notify即可解决问题

使用dispatch_group方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求1
NSLog(@"Request_1");
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求2
NSLog(@"Request_2");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
②使用使用NSOperationQueue中operation的addDependency,则是
1
2
3
4
5
6
7
//4.设置依赖
[operation3 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二

//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
③dispatch_barrier_async

2)、真正的线程同步问题(异步线程里再开启异步线程)

对于上面的例子,当将上面三个操作改成真实的网络操作后,这个简单的做法突然变得无效了,这是为什么呢?

其实这个道理很简单,因为我们开启的网络请求,是一个异步线程,所谓的异步线程,就是告诉系统你不要管我是否完成了,你尽管执行其他操作,开一个线程让我到外面操作去执行就行了,对我的处理你已经完成了,也就是说线程只负责将请求发出去,就认为自己的任务算完成了。所以,当三个请求都发送出去后,不会管网络操作是否完成,就会执行notify中的内容,但由于请求结果本身需要一定的时间,所以导致了界面都刷新了,而请求结果才返回。

即上面的问题,总结为如果dispatch_group_async里执行的是异步代码dispatch_group_notify会直接触发而不会等待异步任务完成。

所以,对于这种异步线程里开异步线程的线程同步问题,该怎么处理呢?

为了应对这种异步线程里开异步线程的线程同步问题,其实我们需要多处理的只是监控异步线程里的所有操作(包括异步线程里的异步线程)都结束后,才算这个异步线程结束

这里我们的解决方法常见的有:

方法A:直接使用dispatch_group_enter和dispatch_group_leave,即只需要在任务开始前enter和结束后leave即可达到线程同步的效果。

dispatch_group_enter(group):下面的任务由group组管理,group组的任务数+1
dispatch_group_leave(group):相应的任务执行完成,group组的任务数-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)testGroupSync_userEnterLeave {
dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(5);
NSLog(@"任务一完成");
dispatch_group_leave(group);
});
});

dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(8);
NSLog(@"任务二完成");
dispatch_group_leave(group);
});
});


dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
}

方法B:使用信号量,控制异步线程的结束,来达到多线程同步的效果。

在GCD中有三个函数是semaphore的操作,分别是:

1
2
3
  dispatch_semaphore_create   创建一个semaphore
  dispatch_semaphore_signal   发送一个信号(信号量+1)
  dispatch_semaphore_wait    等待信号(wait执行完后,信号量-1)

实际使用,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- (void)testGroupSync_semaphore_signal_wait {
dispatch_group_t group = dispatch_group_create();

// 创建一个控制线程同步的信号量,初始值为0(红灯)
dispatch_semaphore_t syncSemaphore = dispatch_semaphore_create(0);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//先再任务1的线程里中再开启一个异步线程执行请求1,同时堵塞住此时的任务1所在的线程,等到已开始的异步请求1结束之后,原本阻塞住的任务1线程才会恢复畅通,才代表任务1结束了。
//请求1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(5);
NSLog(@"任务一完成");
// 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
dispatch_semaphore_signal(syncSemaphore);
});

// 开启信号等待,设置等待时间为永久,直到信号的信号量大于等于1(等绿灯)
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(8);
NSLog(@"任务二完成");
// 使信号的信号量+1,这里的信号量本来为0,+1信号量为1(绿灯)
dispatch_semaphore_signal(syncSemaphore);
});

// 开启信号等待,设置等待时间为永久,直到信号的信号量大于等于1(等绿灯)
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
});


dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
}

附:实际上,这段代码是一个常用的不控制并发数,只控制线程阻塞,实现线程同步的例子。在这里它是将等待信号dispatch_semaphore_wait放在线程执行后。往下看,待会会介绍控制并发数的例子。

上面例子中,先再任务1的线程里中再开启一个异步线程执行请求1,同时堵塞住此时的任务1所在的线程,等到已开始的异步请求1结束之后,恢复畅通,才代表任务1结束了。

3)、其他控制任务顺序的例子

附1:多个请求依次执行

例子:三个任务分别为下载图片,打水印和上传图片,三个任务需异步执行但需要顺序性。

对于这个问题通常会通过线程依赖进行解决。

  • 1、如果是GCD的话,设置线程依赖比较繁琐,所以这里就不讨论了。
  • 2、如果是NSOperation的话,我们直接通过addDependency来设置一个操作队列NSOperationQueue中的线程依赖就可以了。

代码如下(此例子不讨论线程里开线程,如果需要的话,请将信号量的使用添加上去即可解决):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 //1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
[self request_A];
}];

//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
[self request_B];
}];

//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
[self request_C];
}];

//4.设置依赖
[operation2 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二

//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

四、多线程并发数控制

当我们在处理一系列线程的时候,当数量达到一定量,我们需要处理并发数控制。

  • 1、如果是GCD的话,怎么快速的控制并发呢?答案就是dispatch_semaphore。这个比较复杂,但我们这边还是谈一下
  • 2、如果是NSOperation的话,我们直接使用NSOperationQueue来控制并发,这个就不谈了。

GCD使用信号量dispatch_semaphore控制并发

说道信号量,这里顺便谈下信号量的概念:

信号量就是一个资源计数器,它是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。

上面的例子中,信号量初始值为0,其先执行到dispatch_semaphore_wait,此时由于信号量为0,所以造成了阻塞,而使得线程1没能结束它的工作。而到线程1里的异步线程执行完后,给信号量发送了一个通知,使得信号量的值加上了1,此时刚才线程1里的dispatch_semaphore_wait发现信号量大于等于1了,它就不再阻塞,而是继续执行下去,从而使得了线程1这时候真正结束了它所应该处理的工作。wait执行完后,信号量又减1了。

也就是通过这种线程阻塞/等待的方法,我们实现了线程的同步。

当然信号量的用途,除了可以用来实现线程同步外,还可以用来实现控制GCD的并发数

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)testBingfa {
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建信号量,并且设置值为10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{ // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
// 每次发送信号则semaphore会+1,
dispatch_semaphore_signal(semaphore);
});
}
}

比较这段代码与上面的代码,控制并发一般都是将等待信号dispatch_semaphore_wait放在线程执行前。而我们常用的只控制线程阻塞,实现线程同步的,都是将等待信号dispatch_semaphore_wait放在线程执行后。所以,如果一个多线程,它需要同时控制线程同步和线程并发数的话,那它就需要创建两个信号量来分别控制。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- (void)testGroupSyncAndBingfa_semaphore_signal_wait {
dispatch_group_t group = dispatch_group_create();

// 创建一个控制线程并发的信号量,初始值为最大并发数2(红灯)
dispatch_semaphore_t bingfaSemaphore = dispatch_semaphore_create(2);

// 创建一个控制线程同步的信号量,初始值为0(红灯)
dispatch_semaphore_t syncSemaphore = dispatch_semaphore_create(0);

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(bingfaSemaphore, DISPATCH_TIME_FOREVER); //并发信号

//请求1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(5);
NSLog(@"任务一完成");
dispatch_semaphore_signal(syncSemaphore); //同步信号
});
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER); //同步信号
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(bingfaSemaphore, DISPATCH_TIME_FOREVER); //并发信号

//请求2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(8);
NSLog(@"任务二完成");
dispatch_semaphore_signal(syncSemaphore); //同步信号
});
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER); //同步信号
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(bingfaSemaphore, DISPATCH_TIME_FOREVER); //并发信号

//请求3
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(6);
NSLog(@"任务三完成");
dispatch_semaphore_signal(syncSemaphore); //同步信号
});
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER); //同步信号
});


dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
}

通过所附加的两个例子,我们明显的看出NSOperation在处理线程依赖以及并发数的问题上,明显更高级。所以,如果有类似的问题,我们优先使用NSOperation来处理。

## 常见笔试/面试题 [< 返回目录](#目录)
1、在使用SQLite过程中,如果多条线程同时操作同一数据库会造成什么问题,怎么解决?

答:(1)容易造成系统崩溃
(2)解决方案:开启串行模式,使用一个类(单例方式)操作数据库。

## END > < [返回目录](#目录)

线程-①线程间通信

线程与网络-①线程

## 二、谈谈你对线程间的通信的了解 > [< 返回目录](#目录)

在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。

线程间通信的体现:

1
2
1. 一个线程传递数据给另一个线程
2. 在一个线程中执行完特定任务后,转到另一个线程继续执行任务

线程间通信应用举例:(即在一个线程中执行完特定任务后,转到另一个线程继续执行任务,并将该线程数据传递给另一个线程的应用举例)

以下例子摘自:线程间通讯

NSThread可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了。

1
2
3
4
5
6
7
8
9
10
>回到主线程执行,执行self的showImage方法,参数是arg

>- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

>回到后台线程执行aSelector方法,参数是arg
>- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg

>回到xx线程执行aSelector方法,参数是arg

>- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

②、GCD一个线程传递数据给另一个线程。

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"donwload---%@", [NSThread currentThread]);
// 1.子线程下载图片
NSURL *url = [NSURL URLWithString:@"http://d.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.回到主线程设置图片
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"setting---%@ %@", [NSThread currentThread], image);
[self.button setImage:image forState:UIControlStateNormal];
});
});

③、NSOperation线程通信的举例:在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。这里有两种方法,一种是线程里转线程,另一种是线程间使用依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1.创建一个新的队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加任务(操作)
[queue addOperationWithBlock:^{
// 2.1在子线程中下载图片
NSURL *url = [NSURL URLWithString:@"http://imgcache.mysodao.com/img2/M04/8C/74/CgAPDk9dyjvS1AanAAJPpRypnFA573_700x0x1.JPG"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.2回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];

首先我们先来说说线程与线程间的关系,其引入了我们下面的第二点线程等待(线程同步)问题、线程依赖问题。

然后我们再来说说,有时候我们还常经常在多个线程中操作一个全局变量,这个的话,也算线程间通信吗?不管他算不算(应该是不算的,个人觉得),但是他的存在使得我们引入了另一个线程安全的问题,即多个线程操作同一个变量。也就是下面说的第三点,线程安全问题。

附:其他传值方法总结

参考:其他传值方法总结

1
2
1、A界面到B界面的正向传值,即A界面传值给B界面使用
2、。。。

## END > < [返回目录](#目录)

线程-①线程GCD

线程与网络-①线程GCD

知识架构

iOS知识库

Android知识库

## 六、GCD > [< 返回目录](#目录)

精: iOS开发多线程篇—GCD介绍

0、dispatch_sync、dispatch_async

1.GCD中有2个用来执行任务的函数

说明:把右边的参数(任务)提交给左边的参数(队列)进行执行

(1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

(2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

以下参考自:dispatch_group_async 使用详解

1、dispatch_group_async (可实现队列同步)

要实现监听一组任务是否全部完成,等到全部完成后执行其他的操作可以使用dispatch_group_async(这个方法很有用)。比如异步下载多张图片,等所有图片下载完成之后,再去做一些事情(比如才通知界面说完成的)。

如果想在dispatch_queue中所有的任务执行完成后在做某种操作,在串行队列中,我们只需把该操作放到最后一个任务来执行就可以,但是在并行队列中怎么做呢。答案是用dispatch_group任务组来做。

思路:创建一个任务组,然后将异步操作都放进任务组里面,在最后用notify 告知所有任务完成,并做相应处理,一般最后的处理都是在主线程里面刷新UI来提示用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //创建并行队列queue
dispatch_group_t group = dispatch_group_create(); //创建任务组group

dispatch_group_async(group, queue, ^{
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});

dispatch_release(group);

附:下面的两种调用其实是等价的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A)

dispatch_group_async(group, queue, ^{

  // 。。。

});

B)

dispatch_group_enter(group);

dispatch_async(queue, ^{

  //。。。

  dispatch_group_leave(group);

});

其他:

使用gcd怎么让两个线程执行完结束后再去执行另一个线程

多线程并发流程控制之dispatch_group 有关函数

2、dispatch_barrier_async(可实现队列同步)

dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dispatch_queue_t queue = dispatch_queue_create("**test.abc.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});

dispatch_barrier_async(queue, ^{ //dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});

3、dispatch_apply

dispatch_apply执行某个代码片段N次。

1
2
3
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});

4、小结dispatch_group_async、dispatch_barrier_async、dispatch_apply分别是什么?

dispatch_group_async 可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了
dispatch_barrier_async 是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_apply 执行某个代码片段N次。

5、GCD四大队列

iOS GCD四大队列

(1)、串行队列
(2)、并发队列
(3)、全局队列

全局队列:
和并发队列,执行效果一样,只是说,并发队列,需要我们程序员,自己创建
而全局队列,是由于系统提供

(4)、主队列

## END > < [返回目录](#目录)

线程-①进程与线程

线程与网络-①线程

知识架构

iOS知识库

Android知识库

5、进程和线程的区别

(1)、进程和线程的区别

以下区别摘自:进程和线程的区别

一个程序至少一个进程,一个进程至少一个线程。

一个线程只能属于一个进程,而一个进程可以有多个线程,且至少有一个线程。

进程线程的区别:

1
2
3
4
5
6
7
8
资源拥有:同一进程内的所有线程共享该进程的资源。如内存、I/O、cpu等,但是进程之间的资源是独立的。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

两者均可并发执行。
(2)、一个app运行时只有一个进程吗?有没有多进程?

一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。

iOS (APP)进程间8中常用通信方式总结

(3)、什么时候用进程?什么时候用线程?

进程与线程的选择取决以下几点:

1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。

2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应

3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;

4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;

5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

END

线程-②队列与任务或线程

线程与网络-①队列与任务或线程

知识架构

iOS知识库

Android知识库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>1、死锁探究:GCD死锁及报错提示(EXC_BAD_INSTRUCTION)
>(1)、死锁举例:通过串行队列里的任务,往这个串行队列里添加同步任务,会造成死锁
>(2)、死锁结论:往串行队列里添加的同步任务不能卡住该串行队列,否则会造成死锁(这句话非常重要)
>(3)、死锁原因分析

>2、主队列中的死锁:在主队列开启同步任务,一定为什么会阻塞线程?

>3、iOS多线程中,队列和执行的排列组合结果分析
>(1)、队列的类型
>(2)、四个比较容易混淆的术语:同步、异步、并发、串行
>(3)、队列和线程的关系
>(4)、自己延伸的问题:一个队列同时存在同步任务和异步任务?

>4、并发是并发队列,并行又是什么鬼???
>(1)、多个处理器和多核处理器的区别
>(2)、并发和并行的区别

>5、进程和线程的区别
>(1)、进程和线程的区别
>(2)、一个app运行时只有一个进程吗?有没有多进程?
>(3)、什么时候用进程?什么时候用线程?

五、队列与任务执行

< 返回目录

1、队列的类型(并发队列(Concurrent Dispatch Queue)& 串行队列(Serial Dispatch Queue))

GCD的队列可以分为2大类型:并发队列和串行队列。

(1)并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
并发队列

####(2)串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
串行队列

问:并发是并发队列,并行又是什么鬼???

答案:下文会详细介绍。

2、队列是用来干嘛的?说一说【队列与任务 】或【队列与线程】的关系?(重要)

答:

1、队列是用来添加任务/保存以及管理任务的,它只是负责任务的调度(是按串行队列还是按并发队列调度),而不负责任务的执行
2、任务是添加到队列里的,可以添加同步任务或者异步任务。

3、对队列所添加的任务是在线程中执行的/由线程负责进行执行,至于在什么线程根据是同步任务还是异步任务,以及当前是什么队列共同决定。

(所以不管是将同步任务添加到串行队列还是并行队列,因为同步任务都不会创建新线程。所以并行同步队列和串行同步队列其实是一样的,它们都不会创建新的线程而且会是顺序执行)

4、补充:在一个线程内可能有多个队列。

问:往队列里面添加任务,是否会创建新线程是由什么决定的,是任务类型是同步还是异步,还是队列是串行还是并发?

答:是否会创建新线程,主要由队列的类型(串行 or 并发)以及任务类型(同步 or 异步)共同决定。即异步 + 并发,才会创建新线程,并发执行任务。

同步(dispatch_sync) 异步(dispatch_async)
串行队列(Serial Queue) ❌ 不创建新线程,任务按顺序在当前线程执行 ❌ 不创建新线程,任务按顺序在当前线程执行
并发队列(Concurrent Queue) ❌ 不创建新线程,任务按顺序在当前线程执行 ✅ 可能创建新线程,并发执行任务

⚠️ 例外情况:

​ • 主队列 (dispatch_get_main_queue()) 永远不会创建新线程,不管是 sync 还是 async,任务都在主线程执行。

​ • dispatch_sync 只会在当前线程执行,不管队列是不是并发的,都不会开新线程。

2.1、理解队列是用来添加任务/保存以及管理任务的,它只是负责任务的调度,而不负责任务的执行的代码

看以下代码,我们通过它来方便我们理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//  NSOperationQueueCJHelper.m

/**
* 创建队列
*
* @param operations 队列的操作数组
* @param lastOperation 队列的最后一条(最后一条,会等到前面都结束后才会结束)
*
* return 队列
*/
+ (NSOperationQueue *)createOperationQueueWithOperations:(NSArray<NSOperation *> *)operations lastOperation:(NSOperation *)lastOperation {

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"this is a queue";

for (NSBlockOperation *operation in operations) {
[queue addOperation:operation];

[lastOperation addDependency:operation];
}

[queue addOperation:lastOperation];

return queue;
}

// 或者
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(myTask) object:nil];
[operationQueue addOperation:operation];

附:队列的取消、暂停、恢复
取消队列的所有操作使用NSOperationQueue的 - (void)cancelAllOperations;

也可以调用NSOperation的- (void)cancel方法取消单个操作

暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列

2.2、iOS主线程、主队列(串行队列)、全局队列

iOS 主队列 全局队列

主队列是系统为我们创建的串行队列,而且每个APP只有一个主队列,负责调度处理APP的唯一主线程的UI事件。

注意:主队列没有办法开辟新的线程。所以,在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。

主队列异步任务:先将任务放在主队列中,但是不是马上执行,等到主队列中的其它所有除我们使用代码添加到主队列的任务的任务都执行完毕之后才会执行我们使用代码添加的任务。

主队列同步任务:容易阻塞主线程,所以不要这样写。原因:我们自己代码任务需要马上执行,但是主线程正在执行代码任务的方法体,因此代码任务就必须等待,而主线程又在等待代码任务的完成好去完成下面的任务,因此就形成了相互等待。整个主线程就被阻塞了。

全局队列:本质是一个并发队列,由系统提供,方便编程,可以不用创建就直接使用。

2.3、插入问:NSOperationQueue是什么队列?

答:NSOperationQueue是操作队列。结合实际才知道它是并发队列,还是串行队列。

[NSOperationQueue mainQueue]是主队列,和GCD中的主队列一样,是串行队列

[[NSOperationQueue alloc]init]是非主队列,非常特殊(同时具备并发和串行的功能)。默认情况下,非主队列是并发队列。

NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
当operationQueue.maxConcurrentOperationCount > 1,那么operationQueue就是并发队列;
当operationQueue.maxConcurrentOperationCount == 1,那么operationQueue就是串行队列。

3、总结四个比较容易混淆的术语:并发队列、串行队列、同步任务、异步任务

3.1、并发和串行决定了任务的执行方式(队列)。

并发队列:多个任务并发(同时)执行,指一个处理器同时处理多个任务
串行队列:一个任务执行完毕后,再执行下一个任务

3.2、同步和异步决定了要不要开启新的线程(线程)

1
2
>同步:在当前线程中执行任务,不具备开启新线程的能力
>异步:在新的线程中执行任务,具备开启新线程的能力

4、并发是并发队列,并行又是什么鬼(多核处理器同时执行任务)???

(1)、多个处理器和多核处理器的区别

多个处理器:多个单核处理器,就是说电脑和处理器有多个,但是这个电脑的处理器是单核的;

多核处理器:单个多核处理器,也就是说电脑有一个处理器,但是这个处理器是多核的;

附:在生活中,我们的电脑上常见的处理器都是单处理器,但是这个处理器是多核的,一般是四核,有些需要玩高级游戏的,可能还是八核的处理器。

详情查看:多个处理器和多核处理器的区别

(2)、并发和并行的区别

并发(concurrency)是指一个处理器同时处理多个任务。即并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。

并发(concurrency)

并行(parallel)是指多个处理器或者是多核的处理器同时处理多个不同的任务。即并行是指在同一时刻,有多条指令在多个处理器上同时执行。

并行(parallel)

个人理解:是否可以理解为在出现多核处理器或者只有一个处理器的时候,我们那时候就只还有并发的概念。

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)

5、iOS多线程中,队列和执行的排列组合结果分析

该文章非常重要,且主要总结点有

1
2
3
4
5
6
7
1. 开不开线程,取决于执行任务的函数,同步不开,异步开。
2. 开几条线程,取决于队列,串行开一条,并发可以开多条(即并发队列里有异步任务,则可以开多条,如果都只有同步任务,那由于同步任务并不会开新线程,所以此时也就不会开新线程)
3. 主队列:专门用来在主线程上调度任务的"队列",主队列不能在其他线程中调度任务!
4. 如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!主队列同步任务,会造成死锁。原因是循环等待
5. 同步任务可以队列调度多个异步任务前,指定一个同步任务,让所有的异步任务,等待同步任务执行完成,这是依赖关系。
6. 全局队列:并发,能够调度多个线程,执行效率高,但是相对费电。 串行队列效率较低,省电省流量,或者是任务之间需要依赖也可以使用串行队列。
7. 也可以通过判断当前用户的网络环境来决定开的线程数。WIFI下6条,3G/4G下2~3条。

6、代码:使用GCD为队列添加任务

实例1:并发队列添加异步任务/用异步函数往并发队列中添加任务的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)viewDidLoad
{
[super viewDidLoad];

//1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//2.添加任务到队列中,就可以执行任务
//异步函数:具备开启新线程的能力
dispatch_async(queue, ^{
​ NSLog(@"下载图片1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
​ NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
​ NSLog(@"下载图片2----%@",[NSThread currentThread]);
});
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);

}
上述方法说明.GCD中有2个用来执行任务的函数

说明:把右边的参数(任务)提交给左边的参数(队列)进行执行

(1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

(2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

## 常见笔试/面试题 [< 返回目录](#目录)
2、以下两种GCD队列创建有什么不同?
1
2
3
4
5
6
7
dispatch_queue_t queue = dispatch_queue_create("MyQueue",DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue =dispatch_queue_create(@“MyQueue", DISPATCH_QUEUE_CONCURRENT);

//生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

//生成一个并发执行队列,block被分发到多个线程去执行

## END > < [返回目录](#目录)

线程-⑤线程安全

六、队列 -> 任务 -> 线程 -> 数据安全

有队列,就有需要处理的任务,有任务就有线程相关的要考虑(附队列本身也处在线程中),有多线程的考虑,就要考虑数据安全。

常见笔试/面试题

3、队列和数据安全

那我们先来知道一个非常重要的事情:

——- 队列只是负责任务的调度,而不负责任务的执行 ———

——- 任务是在线程中执行的 ———

所以,这个问题其实问得就不大对。让人不知怎么答。

我们这里说下,分情况。

如果是串行队列,数据是安全的。

如果是并发队列,在读写的时候,数据是不安全的。我们可以通过以下几种方式将其处理成安全的。

①、并发队列,改成串行执行。

可以通过

任务是同步任务;

异步任务用依赖;

异步任务用信号量控制并发量为1

②、加锁

@synchronized

NSLock

dispatch_semaphore

多线程、锁、队列

信号量

1
2
3
①dispatch_semaphore_t sem = dispatch_semaphore_create(0);
②dispatch_semaphore_signal(sem);
③dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

GCD

1
2
3
4
5
6
①dispatch_group_t group = dispatch_group_create();
②dispatch_group_enter(group);
③dispatch_group_leave(group);
④dispatch_group_notify(group, dispatch_get_main_queue(), ^{
这里跑去干自己的事情。。。。
});

用信号量时候,

某个线程锁住后,就处于等待状态,即该线程也就无法再继续操作,所以试图尝试在该线程锁住的时候去发送信号,增加信号量,会造成死锁。

举例:

信号量使用场景

任务

iOS 实现并发和串行任务
串行队列如何知道任务已经完成
iOS开发中的并发、串行队列,同步、异步任务

iOS代码同步到主线程的三种方法

autoreleasepool 使用场景、什么时候释放

临时变量什么时候释放

RunLoop和线程间的关系

每条线程都有唯一的一个与之对应的RunLoop对象,RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value,主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建,RunLoop在第一次获取时创建,在线程结束时销毁。

1