1. 面试题1
1.1dispatch_async是开启一个新的子线程去执行任务
- (void)interview1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
[self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
NSLog(@"3---%@",[NSThread currentThread]);
});
}
- (void)test1{
NSLog(@"2---%@",[NSThread currentThread]);
}
// ***************打印结果***************
2020-04-19 13:56:08.835219+0800 tableView[58245:4591321] 1---<NSThread: 0x600000be5340>{number = 4, name = (null)}
2020-04-19 13:56:08.835526+0800 tableView[58245:4591321] 3---<NSThread: 0x600000be5340>{number = 4, name = (null)}
解释:
performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器(即使延时时间时0秒)。由于异步函数dispatch_async是开启一个新的子线程去执行任务,而子线程默认是没有启动Runloop的,所以并不会执行test1方法。
我们可以手动启动runloop来确保test1被调用,也就是在block里面添加一行代码[[NSRunLoop currentRunLoop] run];。
1.2 dispatch_async是开启一个新的子线程去执行任务,开启 runloop
- (void)interview1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
[self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
//子线程启动Runloop
[[NSRunLoop currentRunLoop] run];
NSLog(@"3---%@",[NSThread currentThread]);
});
}
1.3 如果把异步函数改为同步函数,我们再来看下运行结果:
- (void)interview1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
[self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
NSLog(@"3---%@",[NSThread currentThread]);
});
}
- (void)test1{
NSLog(@"2---%@",[NSThread currentThread]);
}
//***************打印结果***************
2020-04-19 14:01:12.454790+0800 tableView[58276:4601632] 1---<NSThread: 0x600003b348c0>{number = 1, name = main}
2020-04-19 14:01:12.455013+0800 tableView[58276:4601632] 3---<NSThread: 0x600003b348c0>{number = 1, name = main}
2020-04-19 14:01:12.472526+0800 tableView[58276:4601632] 2---<NSThread: 0x600003b348c0>{number = 1, name = main}
解释:
同步函数添加的任务是在当前线程中执行,当前线程就是主线程,而主线程的Runloop是启动的,所以test1会调用。虽然延迟时间时0秒,但是添加到Runloop中的计时器不是立马触发的,而是要先唤醒Runloop,这是需要消耗一定时间的,所以会先打印3再打印2。
1.4 我们再把performSelector:withObject:afterDelay:替换成performSelector:withObject:看看运行结果:
- (void)interview1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
[self performSelector:@selector(test1) withObject:nil];
NSLog(@"3---%@",[NSThread currentThread]);
});
}
- (void)test1{
NSLog(@"2---%@",[NSThread currentThread]);
}
//***************打印结果***************
2020-04-19 14:08:50.985883+0800 tableView[58314:4617977] 1---<NSThread: 0x600000ab6d40>{number = 1, name = main}
2020-04-19 14:08:50.986063+0800 tableView[58314:4617977] 2---<NSThread: 0x600000ab6d40>{number = 1, name = main}
2020-04-19 14:08:50.986206+0800 tableView[58314:4617977] 3---<NSThread: 0x600000ab6d40>{number = 1, name = main}
解释:
performSelector:withObject:函数是不涉及到计时器的,所以不会添加到Runloop中,所以是按照1、2、3的顺序执行。
注意:performSelector系列方法中只要是方法名中包含afterDelay、waitUntilDone的都是和计时器有关的,都要注意前面出现的这些问题。
2. 面试题2
2.1 目标线程不存在了
- (void)interview2{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
[thread start];
[self performSelector:@selector(test2) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test2{
NSLog(@"2---%@",[NSThread currentThread]);
}
// ***************运行结果(闪退)***************
2020-04-19 14:13:31.005567+0800 tableView[58341:4629013] 1---<NSThread: 0x600003a56bc0>{number = 7, name = (null)}
2020-04-19 14:13:37.768611+0800 tableView[58341:4629006] XPC connection interrupted
解释:
从运行结果可以看出闪退的原因是target thread exited(目标线程退出)。因为test2方法是在线程thread上执行的,但是线程thread在执行完NSLog(@"1---%@",[NSThread currentThread]);这句代码后就结束了,所以等到执行test2方法时线程thread已经不存在了(严格来说是线程对象是还存在的,只是已经失活了,不能再执行任务了)。
2.2 如果想要代码能正常运行,我们可以利用Runloop知识来保活线程。
先向当前runloop中添加一个source(如果runloop中一个source、NSTimer或Obserer都没有的话就会退出),然后启动runloop。也就是在线程thread的block中添加2行代码,如下所示:
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
// 线程保活
// 先向当前runloop中添加一个source(如果runloop中一个source、NSTimer或Obserer都没有的话就会退出)
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSRunLoopCommonModes];
// 然后启动runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
// ***************运行结果***************
2020-04-19 14:18:50.505233+0800 tableView[58371:4641568] 1---<NSThread: 0x600000cb2d40>{number = 6, name = (null)}
2020-04-19 14:18:50.505542+0800 tableView[58371:4641568] 2---<NSThread: 0x600000cb2d40>{number = 6, name = (null)}
3.面试题 3
- (void)interview3{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
});
NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
// ***************运行结果***************
2020-04-19 14:33:32.197774+0800 tableView[58399:4664434] 执行任务1--<NSThread: 0x600002df55c0>{number = 1, name = main}
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
执行完任务1后就被卡死了。
解释:
interview3方法是在主线程中执行,执行完任务1后,通过同步函数向主队列(串行队列)添加任务2,由于同步添加的任务必须马上执行,而串行队列中当前任务(interview3)还没执行完,就没法安排任务2执行,所以要等当前正在执行的任务(interview3)执行完了后才能执行任务2,而interview3又要等任务2执行完了才会继续往下执行,这样就造成了相互等待而死锁。
4.面试题 4
- (void)interview4{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
});
NSLog(@"执行任务3--%@",[NSThread currentThread]);
}
// ***************运行结果***************
2020-04-19 14:44:26.897578+0800 tableView[58416:4682620] 执行任务1--<NSThread: 0x600002c160c0>{number = 1, name = main}
2020-04-19 14:44:26.897755+0800 tableView[58416:4682620] 执行任务3--<NSThread: 0x600002c160c0>{number = 1, name = main}
2020-04-19 14:44:26.913389+0800 tableView[58416:4682620] 执行任务2--<NSThread: 0x600002c160c0>{number = 1, name = main}
解释:
这和前面一个面试题相比只是把同步函数换成了异步函数。执行完任务1后,通过异步函数添加任务2,虽然异步函数有开启子线程的能力,但是由于是在主队列中,主队列的任务都是在主线程中执行,所以并不会开启子线程。由于是异步函数添加的任务2,所以不必等待任务2就可以继续往下执行,等当前任务(interview4)完成后串行队列再安排执行任务2。所以并不会造成死锁。
5.面试题 5
- (void)interview5{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
// ***************打印结果(打印1、5、2后卡死)***************
2020-04-19 14:48:27.861703+0800 tableView[58450:4691623] 执行任务1--<NSThread: 0x600002f420c0>{number = 1, name = main}
2020-04-19 14:48:27.861906+0800 tableView[58450:4691623] 执行任务5--<NSThread: 0x600002f420c0>{number = 1, name = main}
2020-04-19 14:48:27.861945+0800 tableView[58450:4691687] 执行任务2--<NSThread: 0x600002f1ad80>{number = 5, name = (null)}
(lldb)
Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
解释:
首先打印任务1,然后自己创建了一个串行队列,并通过异步函数向这个队列中添加一个任务块(block1),异步函数会开启一个子线程并将block1放入子线程中去执行,开启子线程是要耗时的,而且异步任务不需要等待就可以继续执行它后面的代码,所以打印任务5在block1前面执行。
再来看block1任务块,先打印任务2,然后通过同步函数添加的block2任务块需要立马执行,而block1所在的队列是串行队列,block1任务块还没执行完,所以要先等block1执行,而block1又要等block2执行完了才能继续往下执行,所以就造成了相互等待而死锁。
6.面试题 6
- (void)interview6{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue2, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
// ***************打印结果***************
2020-04-19 15:01:12.257992+0800 tableView[58483:4713121] 执行任务1--<NSThread: 0x60000087d100>{number = 1, name = main}
2020-04-19 15:01:12.258178+0800 tableView[58483:4713121] 执行任务5--<NSThread: 0x60000087d100>{number = 1, name = main}
2020-04-19 15:01:12.258209+0800 tableView[58483:4713226] 执行任务2--<NSThread: 0x60000080c580>{number = 4, name = (null)}
2020-04-19 15:01:12.258338+0800 tableView[58483:4713226] 执行任务3--<NSThread: 0x60000080c580>{number = 4, name = (null)}
2020-04-19 15:01:12.258468+0800 tableView[58483:4713226] 执行任务4--<NSThread: 0x60000080c580>{number = 4, name = (null)}
解释:
这个和面试题5相比就是新加了一个队列(不管是串行队列还是并发队列都一样),block1任务块和block2任务块分别放在不同的队列中。
先打印任务1再打印任务5和前面是一样的。然后异步函数会开启子线程去执行block1任务块,block1中先打印任务2,然后通过同步函数向另一个队列中添加block2任务块,由于两个block属于不同的队列,block2可以立马被安排执行而不会死锁,所以接着是打印任务3,最后打印任务4。
7.面试题 7
- (void)interview7{
NSLog(@"执行任务1--%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行任务2--%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"执行任务3--%@",[NSThread currentThread]);
});
NSLog(@"执行任务4--%@",[NSThread currentThread]);
});
NSLog(@"执行任务5--%@",[NSThread currentThread]);
}
// ***************打印结果***************
2020-04-19 15:05:54.747680+0800 tableView[58502:4722580] 执行任务1--<NSThread: 0x60000131a0c0>{number = 1, name = main}
2020-04-19 15:05:54.747939+0800 tableView[58502:4722580] 执行任务5--<NSThread: 0x60000131a0c0>{number = 1, name = main}
2020-04-19 15:05:54.747987+0800 tableView[58502:4722642] 执行任务2--<NSThread: 0x600001374440>{number = 3, name = (null)}
2020-04-19 15:05:54.748182+0800 tableView[58502:4722642] 执行任务3--<NSThread: 0x600001374440>{number = 3, name = (null)}
2020-04-19 15:05:54.748333+0800 tableView[58502:4722642] 执行任务4--<NSThread: 0x600001374440>{number = 3, name = (null)}
解释:
这个和面试题5相比是把串行队列换成了并发队列。
先打印任务1再打印任务5和前面是一样的。然后异步函数会开启子线程去执行block1任务块,block1中先打印任务2,然后通过同步函数向并发队列中添加block2任务块,并发队列不需要等前一个任务完成就可以安排下一个任务执行,所以block2可以立马执行打印任务3,最后再打印任务4。
八、Dispatch Group的使用
假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现
// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
return [UIImage imageWithData:data];
}
- (void)downloadImages {
//异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
UIImage *image1 = [self imageWithURLString:url1];
// 下载第二张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
UIImage *image2 = [self imageWithURLString:url2];
// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
});
});
}
虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:
我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
下面用Dispatch Group优化上面的代码:
// 根据url获取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 这里并没有自动释放UIImage对象
return [[UIImage alloc] initWithData:data];
}
- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步下载图片
dispatch_async(queue, ^{
// 创建一个组
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 关联一个任务到group 异步
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第一张图片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});
// 关联一个任务到group 异步
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下载第二张图片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});
// 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁
// 在这里释放图片资源
[image1 release];
[image2 release];
});
// 释放group
dispatch_release(group);
});
}
dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
网友评论