斯坦福大学iOS公开课笔记(10) -多线程和UIScrollerView

这节课主要讲了多线程的基础知识,然后讲了UIScrollView,在最后通过一个下载网络图片并且支持放大缩小的Demo来加深对多线程的理解和UIScrollView的使用。

多线程

多线程就是将程序的执行路径分为不同的路径,可能在同一时间运行。通过这样的方法使程序有更好的响应能力。

队列

代码中的队列就和现实中的队列是一样的,就是排好队的一群人或者是一些准备做的事情。队列中是一群block。根据队列的不同,当他们排到前面的时候,就会被取出来然后运行。

队列包括两种,串行队列和并行队列。

  • 串行队列:一个一个的执行,只有当上一个任务执行完了之后,下一个任务才会执行。
  • 并行队列:同时执行,执行顺序上互相不发生影响。

主线程

在所有的线程中有一个线程是最重要的,就是主线程,主线程中一般会进行UI方面的处理,响应多点触控。
他特殊主要是因为:

  • 1.我们不希望主线程会发生阻塞。
  • 2.我们需要在主线程中进行UI中的大部分内容。

另外主线程只有在“安静”的时候才会执行队列中的内容,“安静”是指用户没有对手机进行操作的时候。

子线程

除去主线程其他的线程都可以称之为子线程,在子线程中我们一般会运行一些比较耗时的工作,比如渲染、网络请求等等。

获取主队列

1
2
3
4
5
//NSThread
NSOperationQueue *mainQ = [NSOperationQueue mainQueue]; //封装好的类方法
//GCD
dispatch_queue_t mainQ = dispatch_get_main_queue(); //C层,更底层的方法

创建一个子队列

1
dispatch_queue_t otherQ = dispatch_queue_create("name",NULL); //name是一个非NSString类型的const char*

第一个参数"name"代表这个队列的名字,第二个参数NULL用来区分并行还是串行,NULL表示的是串行队列。

获取主队列并做一些东西事情

1
2
3
4
5
6
7
//NSThread
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
//GCD
dispatch_async(dispatch_get_main_queue(), ^{
[call aMethod];
});

多线程部分的更多应用会在后边的demo里使用,所以这里就不多做介绍了。

UIScrollView

UIScrollview是一种滚动视图,他可以横屏滚动,也可以竖屏滚动,还可以进行缩放,而且可以在这之中添加更多UIView

滑动

UIScrollView有几个特殊的属性用来帮助我们更好的使用它。

  • 滚动区域大小:contentSize,这个属性表示整个滚动的区域有多大。
  • 当前显示区域:contentOffset,这个属性可以看到当前屏幕中左上角的位置在整个滚动区域的坐标。
  • 用户所示区域的边界:bounds,这个就是滚动视图的边界。

另外还有几个比较特殊的方法。

  • 获取当前显示区域:

    1
    CGRect visibleRect = [scrollView converRect:scrollView.bounds toView: subView];
  • 使用代码完成滚动:在视图中指定一个区域,让他移动过去

    1
    - (void)scrollRectToVisible:(CGRect)aRect animated:(BOOL)animated;

缩放

缩放也有几个特殊的属性。

  • 设置最小缩放比例:scrollView.minimumZoomScale = 0.5;
  • 设置最大缩放比例:scrollView.maximumZoomScale = 2.0;

另外也有一些代理方法:

  • 获取那个界面在进行缩放

    1
    (UIView *)viewForZoomingInScrollView:(UIScrollView *)sender;
  • 放大某一个指定区域:

    1
    - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;

Demo

这个Demo主要是演示使用多线程下载网络图片,并且显示在UIScrollView中,实现缩放的功能。

首先先创建一个新的UIViewController,用来下载和显示图片,给他添加一个公有属性imageURL作为接收的图片地址,和私有的属性imageViewimage用来显示图片和获取图片。

1
2
3
4
5
@interface ImageViewController : UIViewController
@property (nonatomic ,strong) NSURL *imageURL;
@end
1
2
3
4
5
6
@interface ImageViewController ()
@property (nonatomic ,strong) UIImageView *imageView;
@property (nonatomic ,strong) UIImage *image;
@end

并且懒加载实现imageView,重写他们的getter和setter方法。

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
- (void)setImageURL:(NSURL *)imageURL
{
_imageURL = imageURL;
self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_imageURL]]; //会阻塞主线程
}
- (UIImageView *)imageView
{
if(!_imageView)
{
_imageView = [[UIImageView alloc] init];
}
return _imageView;
}
- (UIImage *)image
{
return self.imageView.image;
}
- (void)setImage:(UIImage *)image
{
self.imageView.image = image;
[self.imageView sizeToFit];
}

接下来添加segue,在跳转的时候传过去图片的地址,就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[ImageViewController class]])
{
ImageViewController *ivc = (ImageViewController *)segue.destinationViewController;
if([segue.identifier isEqualToString:@"doggie"])
{
ivc.imageURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://pic.sc.chinaz.com/files/pic/pic9/201508/apic14052.jpg"]];
}
ivc.title = segue.identifier;
}
}
logo

这里第一张我特意选了一张较小的图,发现除了图片太宽没办法玩去能显示外,其他的还是可以的。但是第二张我特意选择了一张很大的图片,可以看到中间有很长时间的卡顿,因为下载图片的时候是在主线程用了很长时间阻塞了线程,所以一直没办法跳到下一个页面,而且同样的这一张图片显示的区域很小只能看到一点点。

解决图片太大的问题

ok,那我们先来解决一下图片太大的问题,我们先给我们的页面添加一个UIScrollView,并且需要在图片的setter中添加一个改变他的contentSize的处理,注意!一定要记得设置contentSize。不仅在设置图片的时候,在scrollView的setter方法里同样也要设置,并且我们也给他添加一个缩放的功能。注意!添加缩放功能一定要实现代理方法

1
2
3
4
5
6
7
- (void)setImage:(UIImage *)image
{
self.imageView.image = image;
[self.imageView sizeToFit];
self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)setScrollView:(UIScrollView *)scrollView
{
_scrollView = scrollView;
_scrollView.minimumZoomScale = 0.2; //设置最小缩放比例
_scrollView.maximumZoomScale = 2.0; //设置最大缩放比例
_scrollView.delegate = self;
self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
logo

这样就解决了图片太大的问题。

解决卡顿的问题

解决卡顿的问题我们只需要把费时间的请求图片的问题放置到另外的子线程上来进行就可以了。

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
- (void)setImageURL:(NSURL *)imageURL
{
_imageURL = imageURL;
// self.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:_imageURL]]; //会阻塞主线程
[self startDownloadingImage];
}
- (void)startDownloadingImage
{
self.image = nil; //清空当前图片信息
if(self.imageURL)
{
NSURLRequest *request = [NSURLRequest requestWithURL:self.imageURL];
//ephemeralSessionConfiguration 临时会话配置,下载什么内容,下完就完了。
//defaultSessionConfiguration 下载多个文件的时候或者让这个会话保持活动状态执行多项内容。
//backgroundSessionConfigurationWithIdentifier: 后台下载模式,表示即使用户切换或者停止应用也会继续下载,需要使用代理
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request
completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//如果没有错误
if(!error)
{
//防止图片地址被修改
if([request.URL isEqual:self.imageURL])
{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^{
//需要再主队列进行
self.image = image;
});
//另外一种方法,在主线程上设置
// [self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
}
}
}];
[task resume];
}
}
logo

这样修改以后,我们可以明显的看到所有的按钮点击反应都非常的迅速,即使图片没有下载下来但是也不会卡顿导致我的按钮无法点击。但是还有一个问题当我们点击第二个图片的时候我们发现图片一直没有出来导致页面一片空白,用户根本不知道发生了什么。所以我们需要添加一个指示器。

添加指示器

指示器我们可以直接拖入一个UIActivityIndicatorView到storyboard中,并且设置他的属性如下

logo

然后与代码相关联,并且在开始下载的部分和设置图片的部分分别开始和结束动画

1
[self.spinner startAnimating];
1
[self.spinner stopAnimating];
logo

以上就是这节课中的内容。