iOS开发——实用技术OC篇&多线程整合

多线程整合

本文知识对iOS开发中多线程的一些知识整合,关于一些概念和技术问题并没有过多的介绍,如果你想了解更多请查看笔者之前写的iOS开发之多线程详解(比较完整):但是有部分涉及到之前文章中没有的技术点和常识,比如加锁的方式,面试相关的,还有一些关于GCD的高级用法,希望你能认真看完,或许可以收获到很多!

http://www.cnblogs.com/iCocos/p/4553103.html

http://www.cnblogs.com/iCocos/p/4553262.html

??先来看看多线程开发中三个非常重要的概念

  • 线程(线程)用于指代独立执行的代码段。
  • ??进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。 ??
  • 任务(task)用于指代抽象的概念,表示需要执行工作。

一点小常识

关于多线程的种类

注意点:

新版iOS中,使用其他线程更新UI可能也能成功,但是不推荐

后面三种对应对应.Net中的多线程、线程池和异步调用

/************************************基本介绍**********************************/

1:pthread:C语言的,实际开发中从来不用,除非你真的闲得蛋疼。这里只是以一个简单的例子应用一下

// 将耗时操作放到子线程中执行

  • 第一个参数: 线程的代号(当做就是线程)
  • 第二个参数: 线程的属性
  • 第三个参数: 指向函数的指针, 就是将来线程需要执行的方法
  • 第四个参数: 给第三个参数的指向函数的指针 传递的参数

void *(*functionP)(void *)

void *  == id

一般情况下C语言中的类型都是以 _t或者Ref结尾

1  pthread_t threadId;
2
3     // 只要create一次就会创建一个新的线程
4
5     pthread_create(&threadId , NULL, &demo, "lnj");

执行的(函数)方法

 1 void *demo(void * param)
 2
 3 {
 4
 5     // 会在子线程中执行
 6
 7     NSLog(@"%s, %@", param, [NSThread currentThread]);
 8
 9     for (int i = 0; i < 99999; i++) {
10
11         // NSLog是非常耗时的操作
12
13         // 一般情况在企业开发中, 如果程序要上架, 必须去掉程序中所有的NSLog
14
15         NSLog(@"%i", i);
16
17     }
18
19     return NULL;
20
21 }

2:NSThread

可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,

优点:

  • - 使用简便
  • - 如果通过detach方法创建线程, 不需要手动调用start启动线程 \
  • 系统会自动启动

缺点:

  • - 不可以进行其它设置
  • - 通过detach方法创建子线程是没有返回值的

应用场景:

  • - 如果仅仅需要简单的开启一个子线程执行一些操作, 不需要对子线程进行其它设置, 那么推荐使用detach方法创建线程

1 [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"xxx"];

也可以使用对象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,然后调用start方法启动线程。(或者使用:alloc/init)

1  NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"lw"];
2
3     thread2.name = @"子线程2";
4
5     thread2.threadPriority = 1.0;
6
7     [thread2 start];

NSObject分类拓展

扩展--NSObject分类扩展方法

为了简化多线程开发过程,苹果官方对NSObject进行分类扩展(本质还是创建NSThread),对于简单的多线程操作可以直接使用这些扩展方法。

  • - (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg:在后台执行一个操作,本质就是重新创建一个线程执行当前方法.(类上面的第三种方法)
  • - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait:在指定的线程上执行一个方法,需要用户创建一个线程对象。
  • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait:在主线程上执行一个方法(前面已经使用过)。

状态:线程状态分为

  1. isExecuting(正在执行)
  2. isFinished(已经完成)
  3. isCancellled(已经取消)
  • exist(终止)

[NSThread exit] == return

睡眠;

1    // 注意: 在那个线程中调用sleepForTimeInterval, 那么哪个线程就会被睡
2
3             [NSThread sleepForTimeInterval:2.0];
4
5             [NSThread sleepUntilDate:[NSDate distantFuture]];

线程中通讯:其实就是相当去在子线程中更新数据,在主线程中刷新UI

比如我们平时在网络上下一张图片显示到界面上,在界面上显示就是其中的通讯:注意刷新UI职能在主线程中,方法有下面几种,但是使用最多的就是第一种。

 1 // 开发中常用
 2
 3    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
 4
 5
 6
 7      //waitUntilDone: updateImage方法执行完毕,是否立刻执行后面的代码
 8
 9     [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:NO];
10
11
12
13     // 可以在指定的线程中, 调用指定对象的指定方法
14
15     [self performSelector:@selector(updateImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];

然后再相应的方法中实现UI界面的刷新

关于时间的代码执行计算:

1)

  • CFAbsoluteTime begin = CFAbsoluteTimeGetCurrent();
  • CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
  • NSLog(@"%f", end - begin);

2)

  • NSDate *begin = [NSDate date];
  • NSDate *end = [NSDate date];
  • NSLog(@"%f", [end timeIntervalSinceDate:begin]);

3:NSOperation

NSOperationQueue:

NSInvocationOperation:NSOperation

 1  /*创建一个调用操作
 2
 3      object:调用方法参数
 4
 5     */
 6
 7     NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
 8
 9     //创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中
10
11     [invocationOperation start];
12
13
14
15     //创建操作队列
16
17     NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
18
19     //注意添加到操作队后,队列会开启一个线程执行此操作
20
21     [operationQueue addOperation:invocationOperation];
22
23     

NSBlockOperation:NSOperation

 1 //创建操作队列
 2
 3     NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
 4
 5     operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数
 6
 7     //创建多个线程用于填充图片
 8
 9     for (int i=0; i<count; ++i) {
10
11         //方法1:创建操作块添加到队列
12
13         //创建多线程操作
14
15         NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
16
17             [self loadImage:[NSNumber numberWithInt:i]];
18
19         }];
20
21         //创建操作队列
22
23
24
25         [operationQueue addOperation:blockOperation];
26
27
28
29         //方法2:直接使用操队列添加操作
30
31         [operationQueue addOperationWithBlock:^{
32
33             [self loadImage:[NSNumber numberWithInt:i]];
34
35         }];
36
37
38
39     }

//添加依赖

  • addDependency
 1   //    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
 2
 3     operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数
 4
 5
 6
 7     NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
 8
 9         [self loadImage:[NSNumber numberWithInt:(count-1)]];
10
11     }];
12
13     //创建多个线程用于填充图片
14
15     for (int i=0; i<count-1; ++i) {
16
17         //方法1:创建操作块添加到队列
18
19         //创建多线程操作
20
21         NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
22
23             [self loadImage:[NSNumber numberWithInt:i]];
24
25         }];
26
27         //设置依赖操作为最后一张图片加载操作
28
29         [blockOperation addDependency:lastBlockOperation];
30
31
32
33         [operationQueue addOperation:blockOperation];
34
35
36
37     }
38
39     //将最后一个图片的加载操作加入线程队列
40
41 [operationQueue addOperation:lastBlockOperation];

注意:

  1. 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
  2. 调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。
  3. 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)。

4:GCD

  • 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
  • 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。

串行

 1  dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue对象不是指针类型
 2
 3     //创建多个线程用于填充图片
 4
 5     for (int i=0; i<count; ++i) {
 6
 7         //异步执行队列任务
 8
 9         dispatch_async(serialQueue, ^{
10
11             [self loadImage:[NSNumber numberWithInt:i]];
12
13         });
14
15
16
17     }
18
19     //非ARC环境请释放
20
21 //    dispatch_release(seriQueue);
22
23 }

并发

 1  dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 2
 3     //创建多个线程用于填充图片
 4
 5     for (int i=0; i<count; ++i) {
 6
 7         //异步执行队列任务
 8
 9         dispatch_async(globalQueue, ^{
10
11             [self loadImage:[NSNumber numberWithInt:i]];
12
13         });
14
15     }
16
17     

同步和异步:

  • dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, <#^(size_t)block#>)
  • dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)

线程中通讯:同上。

以下是开发中使用最多的GCD线程中通讯的方式:刷新UI

 1   // 1.除主队列以外, 随便搞一个队列
 2
 3     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 4
 5
 6
 7     // 2.调用异步函数
 8
 9     dispatch_async(queue, ^{
10
11         // 1.下载图片
12
13         NSURL *url = [NSURL URLWithString:@"http://pic.4j4j.cn/upload/pic/20130531/07ed5ea485.jpg"];
14
15         NSData *data = [NSData dataWithContentsOfURL:url];
16
17         // 2.将二进制转换为图片
18
19         UIImage *image = [UIImage imageWithData:data];
20
21
22
23         // 3.回到主线程更新UI
24
25 //        self.imageView.image = image;
26
27         /*
28
29          技巧:
30
31          如果想等UI更新完毕再执行后面的代码, 那么使用同步函数
32
33          如果不想等UI更新完毕就需要执行后面的代码, 那么使用异步函数
34
35          */
36
37         dispatch_sync(dispatch_get_main_queue(), ^{
38
39             self.imageView.image = image;
40
41         });
42
43         NSLog(@"设置图片完毕 %@", image);
44
45     });
46
47
48
49     

其他方式:

  • dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
  • dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
  • dispatch_time():延迟一定的时间后执行。
  • dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
  • dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。

//单例:执行一次

1     static dispatch_once_t onceToken;
2
3     dispatch_once(&onceToken, ^{
4
5
6
7     });

  //延迟,相当于after

1 dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>);

//快速遍历,多线程遍历

  1  dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
  2
  3
  4
  5     });
  6
  7     /*
  8
  9      for (int i = 0; i < 10; i++) {
 10
 11      NSLog(@"i = %i", i);
 12
 13      }
 14
 15      */
 16
 17     /*
 18
 19      第一个参数: 需要遍历几次
 20
 21      第二个参数: 决定第三个参数的block在哪个线程中执行
 22
 23      第三个参数: 回掉
 24
 25      */
 26
 27
 28
 29     /*
 30
 31      dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
 32
 33      NSLog(@"index = %zd, ==== %@", index,  [NSThread currentThread]);
 34
 35      });
 36
 37      */
 38
 39
 40
 41     // 1.定义变量记录原始文件夹和目标文件夹的路径
 42
 43     NSString *sourcePath = @"/Users/xiaomage/Desktop/test";
 44
 45     NSString *destPath = @"/Users/xiaomage/Desktop/lnj";
 46
 47     // 2.取出原始文件夹中所有的文件
 48
 49     NSFileManager *manager = [NSFileManager defaultManager];
 50
 51     NSArray *files = [manager subpathsAtPath:sourcePath];
 52
 53     //    NSLog(@"%@", files);
 54
 55     // 3.开始拷贝文件
 56
 57     /*
 58
 59      for (NSString *fileName in files) {
 60
 61      // 3.1生产原始文件的绝对路径
 62
 63      NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName];
 64
 65      // 3.2生产目标文件的绝对路径
 66
 67      NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName];
 68
 69      //        NSLog(@"%@", sourceFilePath);
 70
 71      //        NSLog(@"%@", destFilePath);
 72
 73      // 3.3利用NSFileManager拷贝文件
 74
 75      [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil];
 76
 77      }
 78
 79      */
 80
 81     dispatch_apply(files.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
 82
 83         NSString *fileName = files[index];
 84
 85         // 3.1生产原始文件的绝对路径
 86
 87         NSString *sourceFilePath = [sourcePath stringByAppendingPathComponent:fileName];
 88
 89         // 3.2生产目标文件的绝对路径
 90
 91         NSString *destFilePath = [destPath stringByAppendingPathComponent:fileName];
 92
 93         //        NSLog(@"%@", sourceFilePath);
 94
 95         //        NSLog(@"%@", destFilePath);
 96
 97         // 3.3利用NSFileManager拷贝文件
 98
 99         [manager moveItemAtPath:sourceFilePath toPath:destFilePath error:nil];
100
101     });

//栅栏(等待前面所有的执行完,自己猜执行)

  1  //不能结合全局并发队列使用
  2
  3     //所有任务都必须在同一个队列中
  4
  5     dispatch_barrier_sync(dispatch_get_main_queue(), ^{
  6
  7
  8
  9     });
 10
 11     dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
 12
 13     //    dispatch_queue_t queue2 = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
 14
 15     //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 16
 17
 18
 19     __block UIImage *image1 = nil;
 20
 21     __block UIImage *image2 = nil;
 22
 23     // 1.开启一个新的线程下载第一张图片
 24
 25     dispatch_async(queue, ^{
 26
 27         NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d1632701663f5deb48f8c546479.jpg"];
 28
 29         NSData *data = [NSData dataWithContentsOfURL:url];
 30
 31         UIImage *image = [UIImage imageWithData:data];
 32
 33         image1 = image;
 34
 35         NSLog(@"图片1下载完毕");
 36
 37     });
 38
 39     // 2.开启一个新的线程下载第二张图片
 40
 41     dispatch_async(queue, ^{
 42
 43         NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa0f2eb8c0acd3fd1f40345b47.jpg"];
 44
 45         NSData *data = [NSData dataWithContentsOfURL:url];
 46
 47         UIImage *image = [UIImage imageWithData:data];
 48
 49         image2 = image;
 50
 51         NSLog(@"图片2下载完毕");
 52
 53     });
 54
 55
 56
 57     // 3.开启一个新的线程, 合成图片
 58
 59     // 栅栏
 60
 61     // 功能:
 62
 63     // 1.拦截前面的任务, 只有先添加到队列中的任务=执行完毕, 才会执行栅栏添加的任务
 64
 65     // 2.如果栅栏后面还有其它的任务, 那么必须等栅栏执行完毕才会执行后面的其它任务
 66
 67     // 注意点:
 68
 69     // 1.如果想要使用栅栏, 那么就不能使用全局的并发队列
 70
 71     // 2.如果想使用栅栏, 那么所有的任务都必须添加到同一个队列中
 72
 73     dispatch_barrier_async(queue, ^{
 74
 75         NSLog(@"%@ %@", image1, image2);
 76
 77         // 1.开启图片上下文
 78
 79         UIGraphicsBeginImageContext(CGSizeMake(200, 200));
 80
 81         // 2.将第一张图片画上去
 82
 83         [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
 84
 85         // 3.将第二张图片画上去
 86
 87         [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
 88
 89         // 4.从上下文中获取绘制好的图片
 90
 91         UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
 92
 93         // 5.关闭上下文
 94
 95         UIGraphicsEndImageContext();
 96
 97
 98
 99         // 4.回到主线程更新UI
100
101         dispatch_async(dispatch_get_main_queue(), ^{
102
103             self.imageView.image = newImage;
104
105             [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/xiaomage/Desktop/lnj/123.png" atomically:YES];
106
107         });
108
109
110
111         NSLog(@"栅栏执行完毕了");
112
113     });
114
115
116
117
118
119     dispatch_async(queue, ^{
120
121         NSLog(@"---------");
122
123     });
124
125     

dispatch_group_t;

dispatch_group_async(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>);

dispatch_group_notify(<#dispatch_group_t group#>, <#dispatch_queue_t queue#>, <#^(void)block#>);

 1     dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
 2
 3
 4
 5     __block UIImage *image1 = nil;
 6
 7     __block UIImage *image2 = nil;
 8
 9
10
11     dispatch_group_t group = dispatch_group_create();
12
13
14
15     // 1.开启一个新的线程下载第一张图片
16
17     dispatch_group_async(group, queue, ^{
18
19         NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/image/pic/item/77c6a7efce1b9d1632701663f5deb48f8c546479.jpg"];
20
21         NSData *data = [NSData dataWithContentsOfURL:url];
22
23         UIImage *image = [UIImage imageWithData:data];
24
25         image1 = image;
26
27         NSLog(@"图片1下载完毕");
28
29     });
30
31
32
33     // 2.开启一个新的线程下载第二张图片
34
35     dispatch_group_async(group, queue, ^{
36
37         NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/18d8bc3eb13533fa0f2eb8c0acd3fd1f40345b47.jpg"];
38
39         NSData *data = [NSData dataWithContentsOfURL:url];
40
41         UIImage *image = [UIImage imageWithData:data];
42
43         image2 = image;
44
45         NSLog(@"图片2下载完毕");
46
47     });
48
49
50
51     // 3.开启一个新的线程, 合成图片
52
53     // 只要将队列放到group中, 队列中的任务执行完毕, group就会发出一个通知
54
55
56
57     dispatch_group_notify(group, queue, ^{
58
59         NSLog(@"%@ %@", image1, image2);
60
61         // 1.开启图片上下文
62
63         UIGraphicsBeginImageContext(CGSizeMake(200, 200));
64
65         // 2.将第一张图片画上去
66
67         [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
68
69         // 3.将第二张图片画上去
70
71         [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
72
73         // 4.从上下文中获取绘制好的图片
74
75         UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
76
77         // 5.关闭上下文
78
79         UIGraphicsEndImageContext();
80
81
82
83         // 4.回到主线程更新UI
84
85         dispatch_async(dispatch_get_main_queue(), ^{
86
87             self.imageView.image = newImage;
88
89             [UIImagePNGRepresentation(newImage) writeToFile:@"/Users/xiaomage/Desktop/lnj/123.png" atomically:YES];
90
91         });
92
93     });

锁机制:枷锁的几种方法;

@synchronized(self){}互斥锁//其中括号中要么使用self如果不是用self就必须使用同一个对象

 1 NSData *data;
 2
 3     NSString *name;
 4
 5     //线程同步
 6
 7     @synchronized(self){
 8
 9         if (_imageNames.count>0) {
10
11             name=[_imageNames lastObject];
12
13             [NSThread sleepForTimeInterval:0.001f];
14
15             [_imageNames removeObject:name];
16
17         }
18
19     }
20
21     if(name){
22
23         NSURL *url=[NSURL URLWithString:name];
24
25         data=[NSData dataWithContentsOfURL:url];
26
27     }
28
29     return data;

NSLock同步锁(unlock,lock)

  • nonatomic:非原子:(setter)不枷锁——iOS开发中99%都是使用这个
  • atomic:原子:(setter)枷锁-》资源抢夺的时候会使用到他:自旋锁(和互斥锁的区别是不会睡眠,只是等待)
  • nonatomic属性读取的是内存数据(寄存器计算好的结果),而atomic就保证直接读取寄存器的数据

创建一个原子属性

1 @property (atomic,strong) NSMutableArray *imageNames; 2 3

创建一个NSLock成员变量

1 NSLock *_lock;

实现NSLock枷锁

 1   //加锁
 2
 3     [_lock lock];
 4
 5     if (_imageNames.count>0) {
 6
 7         name=[_imageNames lastObject];
 8
 9         [_imageNames removeObject:name];
10
11     }
12
13     //使用完解锁
14
15     [_lock unlock];
16
17  

如果开发中用到的关于加锁相关的,那么基本上就是上面两种中的一种去使用,具体使用哪一种看个人,笔者比较倾向前一种,简单,粗暴!

可以通过tryLock方法,此方法会返回一个BOOL型的值,如果为YES说明获取锁成功,否则失败。另外还有一个lockBeforeData:方法指定在某个时间内获取锁,同样返回一个BOOL值,如果在这个时间内加锁成功则返回YES,失败则返回NO。

一种是使用@synchronized代码块

之前还提到的nonatomic,atomic关于原子锁,区别开来:其实就是结合同步锁的使用

其他锁:

  1. NSRecursiveLock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递归锁来解决。使用递归锁可以在一个线程中反复 获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
  2. NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。
  3. pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。
  4. NSConditionLock:条件锁
  5. NSCondition来控制线程通信
  6. NSCondition实现了NSLocking协议,所以它本身也有lock和unlock方法,因此也可以将它作为NSLock解决线程同步问题
  7. NSCondition更重要的是解决线程之间的调度关系
    • wati方法控制某个线程处于等待状态
    • signal(此方法唤醒一个线程
    • broadcast(此方法会唤醒所有等待线程)

GCD是一种信号机制

GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待

 1 /*信号等待
 2
 3   第二个参数:等待时间
 4
 5  */
 6
 7 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
 8
 9 //信号通知
10
11 dispatch_semaphore_signal(_semaphore);
12
13  

/*****************************************使用总结*********************************************/

关于多线程使用总结:

  • 1>无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度(CPU空闲时就会执行)。
  • 2>更新UI应该在主线程(UI线程)中进行,并且推荐使用同步调用,常用的方法如下:
  • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread])
  • [NSOperationQueue mainQueue] addOperationWithBlock:
  • dispatch_sync(dispatch_get_main_queue(), ^{})
  • 3>NSThread适合轻量级多线程开发,控制线程顺序比较难,同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)。
  • 4>对于简单的多线程开发建议使用NSObject的扩展方法完成,而不必使用NSThread。
  • 5>可以使用NSThread的currentThread方法取得当前线程,使用 sleepForTimeInterval:方法让当前线程休眠。
  • 6>NSOperation进行多线程开发可以控制线程总数及线程依赖关系。
  • 7>创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。
  • 8>相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。
  • 9>NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。
  • 10>在GCD中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。
  • 11>在GDC中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)。
  • 12>相比使用NSLock,@synchronized更加简单,推荐使用后者。

/************************************常用方法与总结**************************************************************/

//子线程

 1 //pthread
 2
 3     pthread_t pt;
 4
 5     pthread_create(&pt, NULL, &iCocos, @"iCocos");
 6
 7     void *iCocos(void * param);//{}
 8
 9
10
11 //pthread_mutex_lock(<#pthread_mutex_t *#>)
12
13     

//NSThread;

 1     NSThread *thread1 = [[NSThread alloc] init];
 2
 3     NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(ios) object:@"iCocos"];
 4
 5     [NSThread detachNewThreadSelector:@selector(ios) toTarget:self withObject:@"iCocos"];
 6
 7     [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
 8
 9     [self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>];
10
11     [self performSelector:<#(SEL)#> onThread:<#(NSThread *)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
12
13     

//NSOperation;

 1 NSOperationQueue *queue = [NSOperationQueue mainQueue];
 2
 3
 4
 5      NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(ios) object:@"iCocos"];
 6
 7     [operation1 start];
 8
 9     [queue addOperation:operation1];
10
11
12
13
14
15     NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
16
17     }];
18
19     [queue addOperation:block];
20
21
22
23
24
25     [queue addOperationWithBlock:^{
26
27
28
29     }];

//GCD

 1 dispatch_queue_t main = dispatch_get_main_queue();
 2
 3     dispatch_sync(main, ^{
 4
 5
 6
 7     });
 8
 9
10
11     //串行
12
13     dispatch_queue_t  searlizer = dispatch_queue_create("iCocos", DISPATCH_QUEUE_SERIAL);
14
15     dispatch_async(sear, ^{
16
17
18
19     });
20
21
22
23     //并发
24
25     dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //DISPATCH_QUEUE_CONCURRENT
26
27     dispatch_async(global, ^{
28
29
30
31     });
32
33
34
35
36
37     //UI刷新
38
39     dispatch_queue_t gl = dispatch_get_global_queue(0, 0);
40
41     dispatch_async(gl, ^{
42
43
44
45         dispatch_sync(dispatch_get_main_queue(), ^{
46
47
48
49         });
50
51
52
53     });

//回到主线程:

 1     [self performSelector:<#(SEL)#> onThread:[main] withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
 2
 3     [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
 4
 5     dispatch_sync(dispatch_get_main_queue(), ^{
 6
 7
 8
 9     });
10
11     

//延迟

 1  [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> invocation:<#(NSInvocation *)#> repeats:<#(BOOL)#>];
 2
 3     [NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(id)#> selector:<#(SEL)#> userInfo:<#(id)#> repeats:<#(BOOL)#>];
 4
 5
 6
 7     [self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>];
 8
 9
10
11     [dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
12
13         <#code to be executed after a specified delay#>];
14
15     });

/****************************************面试之重点************************************************/

关于同步,异步-并发,串行和主队列

异步加并发:

  1 /*
  2
  3  异步 + 并发 : 会开启新的线程
  4
  5  如果任务比较多, 那么就会开启多个线程
  6
  7  */
  8
  9 - (void)asynConcurrent
 10
 11 {
 12
 13     /*
 14
 15      执行任务
 16
 17      dispatch_async
 18
 19      dispatch_sync
 20
 21      */
 22
 23
 24
 25     /*
 26
 27      第一个参数: 队列的名称
 28
 29      第二个参数: 告诉系统需要创建一个并发队列还是串行队列
 30
 31      DISPATCH_QUEUE_SERIAL :串行
 32
 33      DISPATCH_QUEUE_CONCURRENT 并发
 34
 35      */
 36
 37     //    dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_CONCURRENT);
 38
 39
 40
 41     // 系统内部已经给我们提供好了一个现成的并发队列
 42
 43     /*
 44
 45      第一个参数: iOS8以前是优先级, iOS8以后是服务质量
 46
 47      iOS8以前
 48
 49      *  - DISPATCH_QUEUE_PRIORITY_HIGH          高优先级 2
 50
 51      *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      默认的优先级 0
 52
 53      *  - DISPATCH_QUEUE_PRIORITY_LOW:          低优先级 -2
 54
 55      *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:
 56
 57
 58
 59      iOS8以后
 60
 61      *  - QOS_CLASS_USER_INTERACTIVE  0x21 用户交互(用户迫切想执行任务)
 62
 63      *  - QOS_CLASS_USER_INITIATED    0x19 用户需要
 64
 65      *  - QOS_CLASS_DEFAULT           0x15 默认
 66
 67      *  - QOS_CLASS_UTILITY           0x11 工具(低优先级, 苹果推荐将耗时操作放到这种类型的队列中)
 68
 69      *  - QOS_CLASS_BACKGROUND        0x09 后台
 70
 71      *  - QOS_CLASS_UNSPECIFIED       0x00 没有设置
 72
 73
 74
 75      第二个参数: 废物
 76
 77      */
 78
 79     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 80
 81
 82
 83     /*
 84
 85      第一个参数: 用于存放任务的队列
 86
 87      第二个参数: 任务(block)
 88
 89
 90
 91      GCD从队列中取出任务, 遵循FIFO原则 , 先进先出
 92
 93      输出的结果和苹果所说的原则不符合的原因: CPU可能会先调度其它的线程
 94
 95
 96
 97      能够创建新线程的原因:
 98
 99      我们是使用"异步"函数调用
100
101      能够创建多个子线程的原因:
102
103      我们的队列是并发队列
104
105      */
106
107     dispatch_async(queue, ^{
108
109         NSLog(@"任务1  == %@", [NSThread currentThread]);
110
111     });
112
113     dispatch_async(queue, ^{
114
115         NSLog(@"任务2  == %@", [NSThread currentThread]);
116
117     });
118
119     dispatch_async(queue, ^{
120
121         NSLog(@"任务3  == %@", [NSThread currentThread]);
122
123     });
124
125 }

异步加串行:

 1 /*
 2
 3  异步 + 串行:会开启新的线程
 4
 5 但是只会开启一个新的线程
 6
 7  注意: 如果调用 异步函数, 那么不用等到函数中的任务执行完毕, 就会执行后面的代码
 8
 9  */
10
11 - (void)asynSerial
12
13 {
14
15     // 1.创建串行队列
16
17     dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", DISPATCH_QUEUE_SERIAL);
18
19     /*
20
21      能够创建新线程的原因:
22
23      我们是使用"异步"函数调用
24
25      只创建1个子线程的原因:
26
27      我们的队列是串行队列
28
29      */
30
31     // 2.将任务添加到队列中
32
33     dispatch_async(queue, ^{
34
35         NSLog(@"任务1  == %@", [NSThread currentThread]);
36
37     });
38
39     dispatch_async(queue, ^{
40
41         NSLog(@"任务2  == %@", [NSThread currentThread]);
42
43     });
44
45     dispatch_async(queue, ^{
46
47         NSLog(@"任务3  == %@", [NSThread currentThread]);
48
49     });
50
51
52
53     NSLog(@"--------");
54
55 }

同步加串行:

 1 /*
 2
 3  同步 + 串行: 不会开启新的线程
 4
 5  注意点: 如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码
 6
 7  */
 8
 9 - (void)syncSerial
10
11 {
12
13     // 1.创建一个串行队列
14
15     // #define DISPATCH_QUEUE_SERIAL NULL
16
17     // 所以可以直接传NULL
18
19     dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", NULL);
20
21
22
23     // 2.将任务添加到队列中
24
25     dispatch_sync(queue, ^{
26
27         NSLog(@"任务1  == %@", [NSThread currentThread]);
28
29     });
30
31     dispatch_sync(queue, ^{
32
33         NSLog(@"任务2  == %@", [NSThread currentThread]);
34
35     });
36
37     dispatch_sync(queue, ^{
38
39         NSLog(@"任务3  == %@", [NSThread currentThread]);
40
41     });
42
43
44
45     NSLog(@"---------");
46
47 }
48
49  

同步加并发:

 1 /*
 2
 3  同步 + 并发 : 不会开启新的线程
 4
 5  妻管严
 6
 7  */
 8
 9 - (void)syncConCurrent
10
11 {
12
13     // 1.创建一个并发队列
14
15     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
16
17
18
19     // 2.将任务添加到队列中
20
21     dispatch_sync(queue, ^{
22
23         NSLog(@"任务1  == %@", [NSThread currentThread]);
24
25     });
26
27     dispatch_sync(queue, ^{
28
29         NSLog(@"任务2  == %@", [NSThread currentThread]);
30
31     });
32
33     dispatch_sync(queue, ^{
34
35         NSLog(@"任务3  == %@", [NSThread currentThread]);
36
37     });
38
39
40
41     NSLog(@"---------");
42
43 }
44
45  

异步加主队咧:

 1 /*
 2
 3  异步 + 主队列 : 不会创建新的线程, 并且任务是在主线程中执行
 4
 5  */
 6
 7 - (void)asyncMain
 8
 9 {
10
11     // 主队列:
12
13     // 特点: 只要将任务添加到主队列中, 那么任务"一定"会在主线程中执行 \
14
15     无论你是调用同步函数还是异步函数
16
17     dispatch_queue_t queue = dispatch_get_main_queue();
18
19
20
21     dispatch_async(queue, ^{
22
23         NSLog(@"%@", [NSThread currentThread]);
24
25     });
26
27 }
28
29  

主线程中同步加主队列:

 1 /*
 2
 3  如果是在主线程中调用同步函数 + 主队列, 那么会导致死锁
 4
 5  导致死锁的原因:
 6
 7  sync函数是在主线程中执行的, 并且会等待block执行完毕. 先调用
 8
 9  block是添加到主队列的, 也需要在主线程中执行. 后调用
10
11  */
12
13 - (void)syncMain
14
15 {
16
17     NSLog(@"%@", [NSThread currentThread]);
18
19     // 主队列:
20
21     dispatch_queue_t queue = dispatch_get_main_queue();
22
23
24
25     //  如果是调用 同步函数, 那么会等同步函数中的任务执行完毕, 才会执行后面的代码
26
27     // 注意: 如果dispatch_sync方法是在主线程中调用的, 并且传入的队列是主队列, 那么会导致死锁
28
29     dispatch_sync(queue, ^{
30
31         NSLog(@"----------");
32
33         NSLog(@"%@", [NSThread currentThread]);
34
35     });
36
37     NSLog(@"----------");
38
39 }

子线程中同步加主队列:

 1 /*
 2
 3  如果是在子线程中调用 同步函数 + 主队列, 那么没有任何问题
 4
 5  */
 6
 7 - (void)syncMain2
 8
 9 {
10
11     dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
12
13     dispatch_async(queue, ^{
14
15         // block会在子线程中执行
16
17         //        NSLog(@"%@", [NSThread currentThread]);
18
19
20
21         dispatch_queue_t queue = dispatch_get_main_queue();
22
23         dispatch_sync(queue, ^{
24
25             // block一定会在主线程执行
26
27             NSLog(@"%@", [NSThread currentThread]);
28
29         });
30
31     });
32
33     NSLog(@"------------");
34
35 }
36
37  
时间: 2024-07-31 07:17:40

iOS开发——实用技术OC篇&多线程整合的相关文章

iOS开发——实用技术OC篇&amp;单例模式的实实现(ACR&amp;MRC)

单例模式的实实现(ACR&MRC) 在iOS开发中单例模式是一种非常常见的模式,虽然我们自己实现的比较少,但是,系统却提供了不少的到来模式给我们用,比如最常见的UIApplication,Notification等, 那么这篇文章就简单介绍一下,我们开发中如果想要实现单例模式要怎么去实现! 单例模式顾名思义就是只有一个实例,它确保一个类只有一个实例,并且自行实例化并向整个系统提供这个实例.它经常用来做应用程序级别的共享资源控制.这个模式使用频率非常高,通过一个单例类,可以实现在不同窗口之间传递数

iOS开发——实用技术OC篇&amp;UIWebView与JS的交互

UIWebView与JS的交互 事情的起因还是因为项目需求驱动.折腾了两天,由于之前没有UIWebView与JS交互的经历,并且觉得这次在功能上有一定的创造性,特此留下一点文字,方便日后回顾. 我要实现这样一个需求:按照本地的CSS文件展示一串网络获取的带HTML格式的只有body部分的文本,需要自己拼写完整的HTML.除此之外,还需要禁用获取的HTML文本中自带的 < img > 标签自动加载,并把下载图片的操作放在native端来处理,并通过JS将图片在Cache中的地址返回给UIWebv

iOS开发——实用技术OC篇&amp;?Invocation简单介绍

Invocation简单介绍 方法一:运行时方法:(这里在之前的文章定时器的几种方法中说过:www.cnblogs.com/iCocos/p/4694581.html) 1:创建一个签名: NSMethodSignature *singature = [NSMethodSignature signatureWithObjCTypes:"[email protected]:"]; 这里我想如果你仔细的话肯定注意到了:后面的“[email protected]:”,这里是运行时的语法在这里

iOS开发——实用技术OC篇&amp;简单抽屉效果的实现

简单抽屉效果的实现 就目前大部分App来说基本上都有关于抽屉效果的实现,比如QQ/微信等.所以,今天我们就来简单的实现一下.当然如果你想你的效果更好或者是封装成一个到哪里都能用的工具类,那就还需要下一些功夫了,我们这里知识简单的介绍怎么去实现,不过一般我们开发都是找别人做好的,也没必要烂肺时间,除非你真的是大牛或者闲的蛋疼. 其实关于抽屉效果就是界面有三个View,其实一个主View其他两个分别是左边和右边的View,我们分别为他们添加手势,实现左右滑动显示对应的View. 一:所以,首先我们需

iOS开发——实用技术OC篇&amp;8行代码教你搞定导航控制器全屏滑动返回效果

8行代码教你搞定导航控制器全屏滑动返回效果 前言 此次文章,讲述的是导航控制器全屏滑动返回效果,而且代码量非常少,10行内搞定. 效果如图: 如果喜欢我的文章,可以关注我,也可以来小码哥,了解下我们的iOS培训课程.陆续还会有更新ing.... 一.自定义导航控制器 目的:以后需要使用全屏滑动返回功能,就使用自己定义的导航控制器. 二.分析导航控制器侧滑功能 效果:导航控制器默认自带了侧滑功能,当用户在界面的左边滑动的时候,就会有侧滑功能. 系统自带的侧滑效果: 分析: 1.导航控制器的view

iOS开发——实用技术OC篇&amp;事件处理详解

事件处理详解 一:事件处理 事件处理常见属性: 事件类型 @property(nonatomic,readonly) UIEventType     type; @property(nonatomic,readonly) UIEventSubtype  subtype; 事件产生的时间 @property(nonatomic,readonly) NSTimeInterval  timestamp; 事件传递 - hitTest:withEvent: SWIFT func hitTest(_ po

iOS开发——实用技术OC篇&amp;CocoaPods简单粗暴

CocoaPods简单粗暴 直接上代码,不要问为什么,照着做就可以,我也是这么做的,具体的细节,请查看相关文档,网上太多! 1:移除ruby镜像 1 $ gem sources --remove https://rubygems.org/ 2:新增淘宝镜像 1 $ gem sources -a http://ruby.taobao.org/ 3:查看列表 1 $ gem sources -l 4:正式安装 1 sudo gem install cocoapods 接下来就是开始使用了. 查看对应

iOS开发——实用技术OC篇&amp;NSTimer使用注意点及总结

NSTimer使用注意点及总结 总结以下在NSTimer的使用中遇到的一些问题: 1. 不要在dealloc函数中停止并释放NSTimer 如果这样做,会导致对象永远无法调用dealloc函数,也就是会造成内存泄漏. 一个比较合理的解释是NSTimer的回调方法具有retain属性,所以不停止它的情况下被引用对象的retainCount无法降为0,导致内存泄漏的死循环 2.因为要实现类似视频软件里面,UIScrollview定时循环滑动,用到了NSTimer类.在特定时事件情况下需要暂停,和重新

iOS开发——实用技术OC篇&amp;日期处理

日期处理 一:时间截 1 NSString *str=@"1368082020";//时间戳 2 3 NSTimeInterval time=[str doubleValue]+28800;//因为时差问题要加8小时 == 28800 sec 4 5 NSDate *detaildate=[NSDate dateWithTimeIntervalSince1970:time]; 6 7 NSLog(@"date:%@",[detaildate description]