·您现在的位置: 江北区云翼计算机软件开发服务部 >> 文章中心 >> 网站建设 >> app软件开发 >> IOS开发 >> 多线程开发之一NSThread

多线程开发之一NSThread

作者:佚名      IOS开发编辑:admin      更新时间:2022-07-23

每个 iOS 应用程序都有个专门用来更新显示 UI 界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来不好的用户体验。

一般的解决方案就是:将那些耗时的操作放到另外一个线程中去执行,多线程编程就是防止主线程堵塞和增加运行效率的最佳方法。

iOS 支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便,也是 Apple 最推荐使用的方法。

​下面根据抽象层次从低到高依次列出 iOS 所支持的多线程编程方法:

  1. NSThread :是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销

  2. NSOperation:是基于 OC 实现的,NSOperation 以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。

    NSOperation 是一个抽象基类,iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation,当然也可以自定义 NSOperation

  3. Grand Central Dispatch(简称 GCD ,iOS4 才开始支持):提供了一些新特性、运行库来支持多核并行编程,他的关注点更高:如何在多个 CPU 上提升效率

 

效果如下:

ViewController.h

1 #import <UIKit/UIKit.h>
2 
3 @interface ViewController : UITableViewController
4 @PRoperty (copy, nonatomic) NSArray *arrSampleName;
5 
6 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName;
7 
8 @end

ViewController.m

 1 #import "ViewController.h"
 2 #import "FirstSampleViewController.h"
 3 #import "SecondSampleViewController.h"
 4 #import "ThirdSampleViewController.h"
 5 
 6 @interface ViewController ()
 7 - (void)layoutUI;
 8 @end
 9 
10 @implementation ViewController
11 - (void)viewDidLoad {
12     [super viewDidLoad];
13     
14     [self layoutUI];
15 }
16 
17 - (void)didReceiveMemoryWarning {
18     [super didReceiveMemoryWarning];
19     // Dispose of any resources that can be recreated.
20 }
21 
22 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName {
23     if (self = [super initWithStyle:UITableViewStyleGrouped]) {
24         self.navigationItem.title = @"多线程开发之一 NSThread";
25         self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回首页" style:UIBarButtonItemStylePlain target:nil action:nil];
26         
27         _arrSampleName = arrSampleName;
28     }
29     return self;
30 }
31 
32 - (void)layoutUI {
33     
34 }
35 
36 #pragma mark - UITableViewController相关方法重写
37 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
38     return 0.1;
39 }
40 
41 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
42     return 1;
43 }
44 
45 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
46     return [_arrSampleName count];
47 }
48 
49 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
50     static NSString *cellIdentifier = @"cell";
51     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
52     if (!cell) {
53         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
54     }
55     cell.textLabel.text = _arrSampleName[indexPath.row];
56     return cell;
57 }
58 
59 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
60     switch (indexPath.row) {
61         case 0: {
62             FirstSampleViewController *firstSampleVC = [FirstSampleViewController new];
63             [self.navigationController pushViewController:firstSampleVC animated:YES];
64             break;
65         }
66         case 1: {
67             SecondSampleViewController *secondSampleVC = [SecondSampleViewController new];
68             [self.navigationController pushViewController:secondSampleVC animated:YES];
69             break;
70         }
71         case 2: {
72             ThirdSampleViewController *thirdSampleVC = [ThirdSampleViewController new];
73             [self.navigationController pushViewController:thirdSampleVC animated:YES];
74             break;
75             
76             /*
77              类似堆栈的先进后出的原理:
78              返回到(上一级)、(任意级)、(根级)导航
79             [self.navigationController popViewControllerAnimated:YES];
80             [self.navigationController popToViewController:thirdSampleVC animated:YES];
81             [self.navigationController popToRootViewControllerAnimated:YES];
82              */
83         }
84         default:
85             break;
86     }
87 }
88 
89 @end

UIImage+RescaleImage.h

 1 #import <UIKit/UIKit.h>
 2 
 3 @interface UIImage (RescaleImage)
 4 /**
 5  *  根据宽高大小,获取对应的缩放图片
 6  *
 7  *  @param size 宽高大小
 8  *
 9  *  @return 对应的缩放图片
10  */
11 - (UIImage *)rescaleImageToSize:(CGSize)size;
12 
13 @end

UIImage+RescaleImage.m

 1 #import "UIImage+RescaleImage.h"
 2 
 3 @implementation UIImage (RescaleImage)
 4 
 5 - (UIImage *)rescaleImageToSize:(CGSize)size {
 6     CGRect rect = CGRectMake(0.0, 0.0, size.width, size.height);
 7     
 8     UIGraphicsBeginImageContext(rect.size);
 9     [self drawInRect:rect];
10     UIImage *imgScale = UIGraphicsGetImageFromCurrentImageContext();
11     UIGraphicsEndImageContext();
12     
13     return imgScale;
14 }
15 
16 @end

KMImageData.h

1 #import <Foundation/Foundation.h>
2 
3 @interface KMImageData : NSObject
4 @property (assign, nonatomic) NSInteger index;
5 @property (strong, nonatomic) NSData *data;
6 
7 - (instancetype)initWithData:(NSData *)data withIndex:(NSInteger)index;
8 
9 @end

KMImageData.m

 1 #import "KMImageData.h"
 2 
 3 @implementation KMImageData
 4 
 5 - (instancetype)initWithData:(NSData *)data withIndex:(NSInteger)index {
 6     if (self = [super init]) {
 7         _data = data;
 8         _index = index;
 9     }
10     return self;
11 }
12 
13 @end

FirstSampleViewController.h

1 #import <UIKit/UIKit.h>
2 
3 @interface FirstSampleViewController : UIViewController
4 @property (assign, nonatomic) CGSize rescaleImageSize;
5 
6 @property (strong, nonatomic) IBOutlet UIImageView *imgV;
7 @property (strong, nonatomic) IBOutlet UIButton *btnLoadImage;
8 
9 @end

FirstSampleViewController.m

 1 #import "FirstSampleViewController.h"
 2 #import "UIImage+RescaleImage.h"
 3 
 4 @interface FirstSampleViewController ()
 5 - (void)layoutUI;
 6 - (void)updateImage:(NSData *)imageData;
 7 - (void)loadImageFromNetwork;
 8 @end
 9 
10 @implementation FirstSampleViewController
11 
12 - (void)viewDidLoad {
13     [super viewDidLoad];
14     
15     [self layoutUI];
16 }
17 
18 - (void)didReceiveMemoryWarning {
19     [super didReceiveMemoryWarning];
20     // Dispose of any resources that can be recreated.
21 }
22 
23 - (void)dealloc {
24     _imgV.image = nil;
25 }
26 
27 - (void)layoutUI {
28     CGFloat width = [[UIScreen mainScreen] bounds].size.width; //bounds 返回整个屏幕大小;applicationFrame 返回去除状态栏后的屏幕大小
29     CGFloat height = width * 150.0 / 190.0;
30     _rescaleImageSize = CGSizeMake(width, height);
31     
32     //NSString *path = [[NSBundle mainBundle] pathForResource:@"PictureNo@2x" ofType:@"png"];
33     //_imgV.image = [UIImage imageWithContentsOfFile:path];
34     
35     _btnLoadImage.tintColor = [UIColor darkGrayColor];
36     _btnLoadImage.layer.masksToBounds = YES;
37     _btnLoadImage.layer.cornerRadius = 10.0;
38     _btnLoadImage.layer.borderColor = [UIColor grayColor].CGColor;
39     _btnLoadImage.layer.borderWidth = 1.0;
40 }
41 
42 - (void)updateImage:(NSData *)imageData {
43     UIImage *img = [UIImage imageWithData:imageData];
44     _imgV.image = [img rescaleImageToSize:_rescaleImageSize];
45 }
46 
47 - (void)loadImageFromNetwork {
48     NSURL *url = [NSURL URLWithString:@"http://images.apple.com/v/macbook/c/overview/images/hero_static_large.jpg"];
49     NSData *data = [NSData dataWithContentsOfURL:url];
50     
51     /*将数据显示到UI控件,注意只能在主线程中更新UI;
52      另外 performSelectorOnMainThread 方法是 NSObject 的分类方法,每个 NSObject 对象都有此方法;
53      它调用的 selector 方法是当前调用控件的方法,例如使用 UIImageView 调用的时候 selector 就是 UIImageView 的方法
54      withObject:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装)
55      waitUntilDone:是否线程任务完成执行
56      */
57     [self performSelectorOnMainThread:@selector(updateImage:)
58                            withObject:data
59                         waitUntilDone:YES];
60 }
61 
62 - (IBAction)loadImage:(id)sender {
63     //方法一:使用线程对象实例方法创建一个线程
64     //NSThread *thread = [[NSThread alloc] initWithTarget:self
65     //                                           selector:@selector(loadImageFromNetwork)
66     //                                             object:nil];
67     //[thread start]; //启动一个线程;注意启动一个线程并非就一定立即执行,而是处于就绪状态,当系统调度时才真正执行
68     
69     //方法二:使用线程类方法创建一个线程
70     [NSThread detachNewThreadSelector:@selector(loadImageFromNetwork)
71                              toTarget:self
72                            withObject:nil];
73 }
74 
75 @end

FirstSampleViewController.xib

 1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyaccessControl="none" useAutolayout="YES" useTraitCollections="YES">
 3     <dependencies>
 4         <deployment identifier="iOS"/>
 5         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
 6     </dependencies>
 7     <objects>
 8         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="FirstSampleViewController">
 9             <connections>
10                 <outlet property="btnLoadImage" destination="sLs-f1-Gzc" id="kX8-J0-v0V"/>
11                 <outlet property="imgV" destination="4Qp-uk-KAb" id="RM3-Ha-glh"/>
12                 <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
13             </connections>
14         </placeholder>
15         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
16         <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
17             <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
18             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19             <subviews>
20                 <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="PictureNo.png" translatesAutoresizingMaskIntoConstraints="NO" id="4Qp-uk-KAb">
21                     <rect key="frame" x="205" y="225" width="190" height="150"/>
22                     <constraints>
23                         <constraint firstAttribute="height" constant="150" id="Sip-Wd-idU"/>
24                         <constraint firstAttribute="height" constant="150" id="VwM-i1-atB"/>
25                         <constraint firstAttribute="width" constant="190" id="mUh-Bu-tUd"/>
26                         <constraint firstAttribute="width" constant="190" id="mdJ-1c-QFa"/>
27                         <constraint firstAttribute="width" constant="190" id="sVS-bU-Ty9"/>
28                         <constraint firstAttribute="height" constant="150" id="uMG-oN-J56"/>
29                         <constraint firstAttribute="height" constant="150" id="vws-Qw-UrB"/>
30                     </constraints>
31                     <variation key="default">
32                         <mask key="constraints">
33                             <exclude reference="SIp-Wd-idU"/>
34                             <exclude reference="VwM-i1-atB"/>
35                             <exclude reference="mUh-Bu-tUd"/>
36                             <exclude reference="mdJ-1c-QFa"/>
37                             <exclude reference="sVS-bU-Ty9"/>
38                             <exclude reference="uMG-oN-J56"/>
39                             <exclude reference="vws-Qw-UrB"/>
40                         </mask>
41                     </variation>
42                 </imageView>
43                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sLs-f1-Gzc">
44                     <rect key="frame" x="230" y="500" width="140" height="50"/>
45                     <constraints>
46                         <constraint firstAttribute="width" constant="140" id="1jv-9K-mdH"/>
47                         <constraint firstAttribute="height" constant="50" id="Q2w-vR-9ac"/>
48                     </constraints>
49                     <state key="normal" title="加载网络图片">
50                         <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
51                     </state>
52                     <connections>
53                         <action selector="loadImage:" destination="-1" eventType="touchUpInside" id="fdy-Ln-5oS"/>
54                     </connections>
55                 </button>
56             </subviews>
57             <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
58             <constraints>
59                 <constraint firstItem="4Qp-uk-KAb" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="205" id="2a2-mS-WFa"/>
60                 <constraint firstItem="sLs-f1-Gzc" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="ES4-wl-RBz"/>
61                 <constraint firstItem="4Qp-uk-KAb" firstAttribute="centerY" secondItem="i5M-Pr-FkT" secondAttribute="centerY" id="MUJ-WA-sUf"/>
62                 <constraint firstItem="4Qp-uk-KAb" firstAttribute="centerX" secondItem="sLs-f1-Gzc" secondAttribute="centerX" id="Q8a-1k-DzJ"/>
63                 <constraint firstAttribute="bottom" secondItem="4Qp-uk-KAb" secondAttribute="bottom" constant="71" id="V0a-9y-Dwa"/>
64                 <constraint firstAttribute="bottom" secondItem="sLs-f1-Gzc" secondAttribute="bottom" constant="50" id="VMG-CV-eeq"/>
65                 <constraint firstItem="4Qp-uk-KAb" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="-71" id="gqW-Wq-4Zv"/>
66                 <constraint firstItem="sLs-f1-Gzc" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="kNf-6d-EJ8"/>
67             </constraints>
68             <variation key="default">
69                 <mask key="constraints">
70                     <exclude reference="2a2-mS-WFa"/>
71                     <exclude reference="V0a-9y-Dwa"/>
72                     <exclude reference="gqW-Wq-4Zv"/>
73                     <exclude reference="ES4-wl-RBz"/>
74                 </mask>
75             </variation>
76         </view>
77     </objects>
78     <resources>
79         <image name="PictureNo.png" width="190" height="150"/>
80     </resources>
81 </document>

SecondSampleViewController.h

1 #import <UIKit/UIKit.h>
2 
3 @interface SecondSampleViewController : UIViewController
4 @property (assign, nonatomic) CGSize rescaleImageSize;
5 @property (strong, nonatomic) NSMutableArray *mArrImageView;
6 
7 @property (strong, nonatomic) IBOutlet UIButton *btnLoadImage;
8 
9 @end

SecondSampleViewController.m

  1 #import "SecondSampleViewController.h"
  2 #import "KMImageData.h"
  3 #import "UIImage+RescaleImage.h"
  4 
  5 #define kRowCount 4
  6 #define kColumnCount 3
  7 #define kCellSpacing 10.0
  8 
  9 @interface SecondSampleViewController ()
 10 - (void)layoutUI;
 11 - (void)updateImage:(KMImageData *)imageData;
 12 -(KMImageData *)requestData:(NSInteger)imageIndex;
 13 - (void)loadImageFromNetwork:(NSNumber *)imageIndex;
 14 @end
 15 
 16 @implementation SecondSampleViewController
 17 
 18 - (void)viewDidLoad {
 19     [super viewDidLoad];
 20     
 21     [self layoutUI];
 22 }
 23 
 24 - (void)didReceiveMemoryWarning {
 25     [super didReceiveMemoryWarning];
 26     // Dispose of any resources that can be recreated.
 27 }
 28 
 29 - (void)dealloc {
 30     _mArrImageView = nil;
 31 }
 32 
 33 - (void)layoutUI {
 34     CGFloat width = ([[UIScreen mainScreen] bounds].size.width - ((kColumnCount + 1) * kCellSpacing)) / kColumnCount;
 35     _rescaleImageSize = CGSizeMake(width, width);
 36     
 37     CGFloat heightOfStatusAndNav = 20.0 + 44.0;
 38     NSString *path = [[NSBundle mainBundle] pathForResource:@"PictureNo@2x" ofType:@"png"];
 39     UIImage *img = [UIImage imageWithContentsOfFile:path];
 40     _mArrImageView = [NSMutableArray arrayWithCapacity:kRowCount * kColumnCount];
 41     //初始化多个图片视图
 42     for (NSUInteger i=0; i<kRowCount; i++) {
 43         for (NSUInteger j=0; j<kColumnCount; j++) {
 44             UIImageView *imgV = [[UIImageView alloc] initWithFrame:
 45                                  CGRectMake(_rescaleImageSize.width * j + kCellSpacing * (j+1),
 46                                             _rescaleImageSize.height * i + kCellSpacing * (i+1) + heightOfStatusAndNav,
 47                                             _rescaleImageSize.width,
 48                                             _rescaleImageSize.height)];
 49             imgV.image = img;
 50             [self.view addSubview:imgV];
 51             [_mArrImageView addObject:imgV];
 52         }
 53     }
 54     
 55     _btnLoadImage.tintColor = [UIColor darkGrayColor];
 56     _btnLoadImage.layer.masksToBounds = YES;
 57     _btnLoadImage.layer.cornerRadius = 10.0;
 58     _btnLoadImage.layer.borderColor = [UIColor grayColor].CGColor;
 59     _btnLoadImage.layer.borderWidth = 1.0;
 60 }
 61 
 62 - (void)updateImage:(KMImageData *)imageData {
 63     UIImage *img = [UIImage imageWithData:imageData.data];
 64     UIImageView *imgVCurrent = _mArrImageView[imageData.index];
 65     imgVCurrent.image = [img rescaleImageToSize:_rescaleImageSize];
 66 }
 67 
 68 -(KMImageData *)requestData:(NSInteger)imageIndex {
 69     //对于多线程操作,建议把线程操作放到 @autoreleasepool 中
 70     @autoreleasepool {
 71         if (imageIndex != kRowCount * kColumnCount - 1) { //虽然设置了最后一个线程的优先级为1.0,但无法保证他是第一个加载的。所以我们这里让其他线程挂起0.5秒,这样基本能保证最后一个线程第一个加载,除非网络非常差的情况
 72             [NSThread sleepForTimeInterval:0.5];
 73         }
 74         
 75         NSURL *url = [NSURL URLWithString:@"http://images.apple.com/v/macbook/c/overview/images/hero_static_large.jpg"];
 76         NSData *data = [NSData dataWithContentsOfURL:url];
 77         KMImageData *imageData = [[KMImageData alloc] initWithData:data
 78                                                          withIndex:imageIndex];
 79         return imageData;
 80     }
 81 }
 82 
 83 - (void)loadImageFromNetwork:(NSNumber *)imageIndex {
 84     NSLog(@"Current thread:%@",[NSThread currentThread]);
 85     
 86     /*将数据显示到UI控件,注意只能在主线程中更新UI;
 87      另外 performSelectorOnMainThread 方法是 NSObject 的分类方法,每个 NSObject 对象都有此方法;
 88      它调用的 selector 方法是当前调用控件的方法,例如使用 UIImageView 调用的时候 selector 就是 UIImageView 的方法
 89      withObject:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装)
 90      waitUntilDone:是否线程任务完成执行
 91      */
 92     [self performSelectorOnMainThread:@selector(updateImage:)
 93                            withObject:[self requestData:[imageIndex integerValue]]
 94                         waitUntilDone:YES];
 95 }
 96 
 97 - (IBAction)loadImage:(id)sender {
 98     for (NSUInteger i=0, len=kRowCount * kColumnCount; i<len; i++) {
 99         NSThread *thread = [[NSThread alloc] initWithTarget:self
100                                                    selector:@selector(loadImageFromNetwork:)
101                                                      object:[NSNumber numberWithInteger:i]];
102         thread.name = [NSString stringWithFormat:@"Thread %lu", (unsigned long)i];
103         thread.threadPriority = i == len-1 ? 1.0 : 0.0; //设置线程优先级;0.0-1.0,值越高优先级越高,这样可以提高他被优先加载的机率,但是他也未必就第一个加载。因为首先其他线程是先启动的,其次网络状况我们没办法修改
104         [thread start];
105     }
106 }
107 
108 @end

SecondSampleViewController.xib

 1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
 3     <dependencies>
 4         <deployment identifier="iOS"/>
 5         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
 6     </dependencies>
 7     <objects>
 8         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SecondSampleViewController">
 9             <connections>
10                 <outlet property="btnLoadImage" destination="F5h-ZI-gGL" id="I40-e2-bAa"/>
11                 <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
12             </connections>
13         </placeholder>
14         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
15         <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
16             <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
17             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18             <subviews>
19                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F5h-ZI-gGL">
20                     <rect key="frame" x="230" y="530" width="140" height="50"/>
21                     <constraints>
22                         <constraint firstAttribute="height" constant="50" id="HWd-Xc-Wk7"/>
23                         <constraint firstAttribute="width" constant="140" id="vrH-qE-PNK"/>
24                     </constraints>
25                     <state key="normal" title="加载网络图片">
26                         <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
27                     </state>
28                     <connections>
29                         <action selector="loadImage:" destination="-1" eventType="touchUpInside" id="Hgw-q8-lHy"/>
30                     </connections>
31                 </button>
32             </subviews>
33             <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
34             <constraints>
35                 <constraint firstAttribute="bottom" secondItem="F5h-ZI-gGL" secondAttribute="bottom" constant="20" id="jPY-fY-9XJ"/>
36                 <constraint firstAttribute="centerX" secondItem="F5h-ZI-gGL" secondAttribute="centerX" id="rH1-sV-pST"/>
37             </constraints>
38         </view>
39     </objects>
40 </document>

ThirdSampleViewController.h

 1 #import <UIKit/UIKit.h>
 2 
 3 @interface ThirdSampleViewController : UIViewController
 4 @property (assign, nonatomic) CGSize rescaleImageSize;
 5 @property (strong, nonatomic) NSMutableArray *mArrImageView;
 6 @property (strong, nonatomic) NSMutableArray *mArrThread;
 7 
 8 @property (strong, nonatomic) IBOutlet UIButton *btnLoadImage;
 9 @property (strong, nonatomic) IBOutlet UIButton *btnStopLoadImage;
10 
11 @end

ThirdSampleViewController.m

  1 #import "ThirdSampleViewController.h"
  2 #import "KMImageData.h"
  3 #import "UIImage+RescaleImage.h"
  4 
  5 #define kRowCount 4
  6 #define kColumnCount 3
  7 #define kCellSpacing 10.0
  8 
  9 @interface ThirdSampleViewController ()
 10 - (void)layoutUI;
 11 - (void)updateImage:(KMImageData *)imageData;
 12 -(KMImageData *)requestData:(NSInteger)imageIndex;
 13 - (void)loadImageFromNetwork:(NSNumber *)imageIndex;
 14 @end
 15 
 16 @implementation ThirdSampleViewController
 17 
 18 - (void)viewDidLoad {
 19     [super viewDidLoad];
 20     
 21     [self layoutUI];
 22 }
 23 
 24 - (void)didReceiveMemoryWarning {
 25     [super didReceiveMemoryWarning];
 26     // Dispose of any resources that can be recreated.
 27 }
 28 
 29 - (void)dealloc {
 30     _mArrImageView = nil;
 31     _mArrThread = nil;
 32 }
 33 
 34 - (void)layoutUI {
 35     CGFloat width = ([[UIScreen mainScreen] bounds].size.width - ((kColumnCount + 1) * kCellSpacing)) / kColumnCount;
 36     _rescaleImageSize = CGSizeMake(width, width);
 37     
 38     CGFloat heightOfStatusAndNav = 20.0 + 44.0;
 39     NSString *path = [[NSBundle mainBundle] pathForResource:@"PictureNo@2x" ofType:@"png"];
 40     UIImage *img = [UIImage imageWithContentsOfFile:path];
 41     _mArrImageView = [NSMutableArray arrayWithCapacity:kRowCount * kColumnCount];
 42     //初始化多个图片视图
 43     for (NSUInteger i=0; i<kRowCount; i++) {
 44         for (NSUInteger j=0; j<kColumnCount; j++) {
 45             UIImageView *imgV = [[UIImageView alloc] initWithFrame:
 46                                  CGRectMake(_rescaleImageSize.width * j + kCellSpacing * (j+1),
 47                                             _rescaleImageSize.height * i + kCellSpacing * (i+1) + heightOfStatusAndNav,
 48                                             _rescaleImageSize.width,
 49                                             _rescaleImageSize.height)];
 50             imgV.image = img;
 51             [self.view addSubview:imgV];
 52             [_mArrImageView addObject:imgV];
 53         }
 54     }
 55     
 56     void (^beautifulButton)(UIButton *, UIColor *) = ^(UIButton *btn, UIColor *tintColor) {
 57         btn.tintColor = tintColor;
 58         btn.layer.masksToBounds = YES;
 59         btn.layer.cornerRadius = 10.0;
 60         btn.layer.borderColor = [UIColor grayColor].CGColor;
 61         btn.layer.borderWidth = 1.0;
 62     };
 63     
 64     beautifulButton(_btnLoadImage, [UIColor darkGrayColor]);
 65     beautifulButton(_btnStopLoadImage, [UIColor redColor]);
 66 }
 67 
 68 - (void)updateImage:(KMImageData *)imageData {
 69     UIImage *img = [UIImage imageWithData:imageData.data];
 70     UIImageView *imgVCurrent = _mArrImageView[imageData.index];
 71     imgVCurrent.image = [img rescaleImageToSize:_rescaleImageSize];
 72 }
 73 
 74 -(KMImageData *)requestData:(NSInteger)imageIndex {
 75     //对于多线程操作,建议把线程操作放到 @autoreleasepool 中
 76     @autoreleasepool {
 77         if (imageIndex != kRowCount * kColumnCount - 1) { //虽然设置了最后一个线程的优先级为1.0,但无法保证他是第一个加载的。所以我们这里让其他线程挂起0.5秒,这样基本能保证最后一个线程第一个加载,除非网络非常差的情况
 78             [NSThread sleepForTimeInterval:0.5];
 79         }
 80         
 81         NSURL *url = [NSURL URLWithString:@"http://images.apple.com/v/macbook/c/overview/images/hero_static_large.jpg"];
 82         NSData *data = [NSData dataWithContentsOfURL:url];
 83         KMImageData *imageData = [[KMImageData alloc] initWithData:data
 84                                                          withIndex:imageIndex];
 85         return imageData;
 86     }
 87 }
 88 
 89 - (void)loadImageFromNetwork:(NSNumber *)imageIndex { //子线程中执行
 90     //线程状态:thread.isExecuting(是否执行中)、thread.isFinished(是否已完成)、thread.isCancelled(是否已取消)
 91     //thread.isMainThread(是否主线程)、[NSThread isMultiThreaded](是否多线程)
 92     
 93     KMImageData *imageData = [self requestData:[imageIndex integerValue]];
 94     
 95     NSThread *thread = _mArrThread[[imageIndex integerValue]];
 96     if (thread.isCancelled) { //判断线程是否已经取消,如是就退出当前线程;这里注意需让 exit 操作有足够的时间进行占用资源的释放,否则有可能出现异常;比如 Demo 中:当点击「停止加载」按钮后,再次快速点击「加载网络图片」按钮
 97         NSLog(@"Current thread:%@ will be cancelled.", thread);
 98         [NSThread exit];
 99     } else {
100 //        //在后台执行一个方法,本质就是重新创建一个线程来执行方法
101 //        [self performSelectorInBackground:@selector(updateImage:) withObject:imageData];
102 //        
103 //        /*将数据显示到UI控件,注意只能在主线程中更新UI;
104 //         另外 performSelectorOnMainThread 方法是 NSObject 的分类方法,每个 NSObject 对象都有此方法;它调用的 selector 方法是当前调用控件的方法,例如使用 UIImageView 调用的时候 selector 就是 UIImageView 的方法
105 //         withObject:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装)
106 //         waitUntilDone:是否线程任务完成后执行
107 //         */
108 //        [self performSelectorOnMainThread:@selector(updateImage:)
109 //                               withObject:imageData
110 //                            waitUntilDone:YES];
111         
112         @try {
113             //在指定的线程上执行一个方法,需要用户创建一个线程对象实例
114             [self performSelector:@selector(updateImage:)
115                          onThread:thread
116                        withObject:imageData
117                     waitUntilDone:YES];
118         }
119         @catch (NSException *exception) {
120             NSLog(@"Exception: %@", [exception description]);
121         }
122     }
123 }
124 
125 - (IBAction)loadImage:(id)sender {
126     NSUInteger len = kRowCount * kColumnCount;
127     _mArrThread = [NSMutableArray arrayWithCapacity:len];
128     
129     //循环创建多个线程
130     for (NSUInteger i=0; i<len; i++) {
131         NSThread *thread = [[NSThread alloc] initWithTarget:self
132                                                    selector:@selector(loadImageFromNetwork:)
133                                                      object:[NSNumber numberWithInteger:i]];
134         thread.name = [NSString stringWithFormat:@"Thread %lu", (unsigned long)i];
135         thread.threadPriority = i == len-1 ? 1.0 : 0.0; //设置线程优先级;0.0-1.0,值越高优先级越高,这样可以提高他被优先加载的机率,但是他也未必就第一个加载。因为首先其他线程是先启动的,其次网络状况我们没办法修改
136         [_mArrThread addObject:thread];
137     }
138     
139     //循环启动线程
140     for (NSUInteger i=0; i<len; i++) {
141         NSThread *thread = _mArrThread[i];
142         [thread start];
143     }
144 }
145 
146 - (IBAction)stopLoadImage:(id)sender { //主线程中执行
147     for (NSUInteger i=0, len=kRowCount * kColumnCount; i<len; i++) {
148         NSThread *thread = _mArrThread[i];
149         if (!thread.isFinished) { //判断线程是否完成,如果没有完成则设置为取消状态;取消状态仅仅是改变了线程状态而言,并不能终止线程
150             [thread cancel];
151         }
152     }
153 }
154 
155 @end

ThirdSampleViewController.xib

 1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 2 <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
 3     <dependencies>
 4         <deployment identifier="iOS"/>
 5         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
 6     </dependencies>
 7     <objects>
 8         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ThirdSampleViewController">
 9             <connections>
10                 <outlet property="btnLoadImage" destination="F5h-ZI-gGL" id="I40-e2-bAa"/>
11                 <outlet property="btnStopLoadImage" destination="gnu-KE-bGq" id="tOA-t9-OA9"/>
12                 <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
13             </connections>
14         </placeholder>
15         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
16         <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
17             <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
18             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19             <subviews>
20                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="F5h-ZI-gGL">
21                     <rect key="frame" x="20" y="530" width="130" height="50"/>
22                     <constraints>
23                         <constraint firstAttribute="height" constant="50" id="HWd-Xc-Wk7"/>
24                         <constraint firstAttribute="width" constant="130" id="vrH-qE-PNK"/>
25                     </constraints>
26                     <state key="normal" title="加载网络图片">
27                         <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
28                     </state>
29                     <connections>
30                         <action selector="loadImage:" destination="-1" eventType="touchUpInside" id="Hgw-q8-lHy"/>
31                     </connections>
32                 </button>
33                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gnu-KE-bGq">
34                     <rect key="frame" x="450" y="530" width="130" height="50"/>
35                     <constraints>
36                         <constraint firstAttribute="height" constant="50" id="1R7-22-hMk"/>
37                         <constraint firstAttribute="width" constant="130" id="64l-yF-IFO"/>
38                     </constraints>
39                     <state key="normal" title="停止加载">
40                         <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
41                     </state>
42                     <connections>
43                         <action selector="loadImage:" destination="-1" eventType="touchUpInside" id="PyX-RW-cbL"/>
44                         <action selector="stopLoadImage:" destination="-1" eventType="touchUpInside" id="1Xa-Ek-D4B"/>
45                     </connections>
46                 </button>
47             </subviews>
48             <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
49             <constraints>
50                 <constraint firstItem="F5h-ZI-gGL" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" constant="20" id="Glo-vW-SXd"/>
51                 <constraint firstAttribute="trailing" secondItem="gnu-KE-bGq" secondAttribute="trailing" constant="20" id="IdF-1v-bg2"/>
52                 <constraint firstAttribute="bottom" secondItem="gnu-KE-bGq" secondAttribute="bottom" constant="20" id="cyx-dg-K8M"/>
53                 <constraint firstAttribute="bottom" secondItem="F5h-ZI-gGL" secondAttribute="bottom" constant="20" id="jPY-fY-9XJ"/>
54                 <constraint firstAttribute="centerX" secondItem="F5h-ZI-gGL" secondAttribute="centerX" id="rH1-sV-pST"/>
55             </constraints>
56             <variation key="default">
57                 <mask key="constraints">
58                     <exclude reference="rH1-sV-pST"/>
59                 </mask>
60             </variation>
61         </view>
62     </objects>
63 </document>

AppDelegate.h

1 #import <UIKit/UIKit.h>
2 
3 @interface AppDelegate : UIResponder <UIApplicationDelegate>
4 
5 @property (strong, nonatomic) UIWindow *window;
6 @property (strong, nonatomic) UINavigationController *navigationController;
7 
8 @end

AppDelegate.m

 1 #import "AppDelegate.h"
 2 #import "ViewController.h"
 3 
 4 @interface AppDelegate ()
 5 
 6 @end
 7 
 8 @implementation AppDelegate
 9 
10 
11 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
12     _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
13     ViewController *viewController = [[ViewController alloc] initWithSampleNameArray:@[@"请求单张网络图片(解决线程阻塞)", @"请求多张网络图片(多个线程并发)", @"请求多张网络图片(线程状态)"]];
14     _navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
15     _window.rootViewController = _navigationController;
16     //[_window addSubview:_navigationController.view]; //当_window.rootViewController关联时,这一句可有可无
17     [_window makeKeyAndVisible];
18     return YES;
19 }
20 
21 - (void)applicationWillResignActive:(UIApplication *)application {
22 }
23 
24 - (void)applicationDidEnterBackground:(UIApplication *)application {
25 }
26 
27 - (void)applicationWillEnterForeground:(UIApplication *)application {
28 }
29 
30 - (void)applicationDidBecomeActive:(UIApplication *)application {
31 }
32 
33 - (void)applicationWillTerminate:(UIApplication *)application {
34 }
35 
36 @end

输出结果:

 1 2015-08-24 22:18:38.945 NSThreadDemo[5361:204621]  INFO: Reveal Server started (Protocol Version 18).
 2 2015-08-24 22:18:57.863 NSThreadDemo[5361:204847] Current thread:<NSThread: 0x7f9f60cd1f80>{number = 5, name = Thread 0}
 3 2015-08-24 22:18:57.863 NSThreadDemo[5361:204848] Current thread:<NSThread: 0x7f9f60c74800>{number = 6, name = Thread 1}
 4 2015-08-24 22:18:57.863 NSThreadDemo[5361:204849] Current thread:<NSThread: 0x7f9f60cb8f30>{number = 7, name = Thread 2}
 5 2015-08-24 22:18:57.863 NSThreadDemo[5361:204850] Current thread:<NSThread: 0x7f9f60cb8500>{number = 8, name = Thread 3}
 6 2015-08-24 22:18:57.864 NSThreadDemo[5361:204851] Current thread:<NSThread: 0x7f9f60cd0c60>{number = 9, name = Thread 4}
 7 2015-08-24 22:18:57.865 NSThreadDemo[5361:204857] Current thread:<NSThread: 0x7f9f60dee540>{number = 11, name = Thread 6}
 8 2015-08-24 22:18:57.864 NSThreadDemo[5361:204856] Current thread:<NSThread: 0x7f9f60cd2150>{number = 10, name = Thread 5}
 9 2015-08-24 22:18:57.866 NSThreadDemo[5361:204859] Current thread:<NSThread: 0x7f9f60daf730>{number = 13, name = Thread 8}
10 2015-08-24 22:18:57.866 NSThreadDemo[5361:204860] Current thread:<NSThread: 0x7f9f60db8530>{number = 14, name = Thread 9}
11 2015-08-24 22:18:57.866 NSThreadDemo[5361:204862] Current thread:<NSThread: 0x7f9f60f3f7e0>{number = 16, name = Thread 11}
12 2015-08-24 22:18:57.866 NSThreadDemo[5361:204858] Current thread:<NSThread: 0x7f9f60db2390>{number = 12, name = Thread 7}
13 2015-08-24 22:18:57.867 NSThreadDemo[5361:204861] Current thread:<NSThread: 0x7f9f60f231c0>{number = 15, name = Thread 10}
14 
15 2015-08-24 22:19:07.073 NSThreadDemo[5361:204952] Current thread:<NSThread: 0x7f9f63010930>{number = 39, name = Thread 10} will be cancelled.
16 2015-08-24 22:19:07.691 NSThreadDemo[5361:204951] Current thread:<NSThread: 0x7f9f60df0210>{number = 38, name = Thread 9} will be cancelled.
17 2015-08-24 22:19:07.697 NSThreadDemo[5361:204957] Current thread:<NSThread: 0x7f9f60db8ee0>{number = 29, name = Thread 0} will be cancelled.
18 2015-08-24 22:19:07.729 NSThreadDemo[5361:204958] Current thread:<NSThread: 0x7f9f60db2390>{number = 30, name = Thread 1} will be cancelled.
19 2015-08-24 22:19:07.857 NSThreadDemo[5361:204949] Current thread:<NSThread: 0x7f9f63011190>{number = 36, name = Thread 7} will be cancelled.
20 2015-08-24 22:19:08.025 NSThreadDemo[5361:204960] Current thread:<NSThread: 0x7f9f6300c2d0>{number = 32, name = Thread 3} will be cancelled.
21 2015-08-24 22:19:08.047 NSThreadDemo[5361:204959] Current thread:<NSThread: 0x7f9f60dd4200>{number = 31, name = Thread 2} will be cancelled.
22 2015-08-24 22:19:08.191 NSThreadDemo[5361:204942] Current thread:<NSThread: 0x7f9f60db8ee0>{number = 29, name = Thread 0} will be cancelled.
23 2015-08-24 22:19:08.250 NSThreadDemo[5361:204965] Current thread:<NSThread: 0x7f9f630110b0>{number = 37, name = Thread 8} will be cancelled.
24 2015-08-24 22:19:08.416 NSThreadDemo[5361:204962] Current thread:<NSThread: 0x7f9f60df7bc0>{number = 34, name = Thread 5} will be cancelled.
25 2015-08-24 22:19:08.464 NSThreadDemo[5361:204963] Current thread:<NSThread: 0x7f9f60dc5ca0>{number = 35, name = Thread 6} will be cancelled.
26 2015-08-24 22:19:08.495 NSThreadDemo[5361:204964] Current thread:<NSThread: 0x7f9f63011190>{number = 36, name = Thread 7} will be cancelled.
27 2015-08-24 22:19:08.661 NSThreadDemo[5361:204966] Current thread:<NSThread: 0x7f9f60df0210>{number = 38, name = Thread 9} will be cancelled.
28 2015-08-24 22:19:10.033 NSThreadDemo[5361:204961] Current thread:<NSThread: 0x7f9f60da3330>{number = 33, name = Thread 4} will be cancelled.
29 2015-08-24 22:19:10.056 NSThreadDemo[5361:204967] Current thread:<NSThread: 0x7f9f63010930>{number = 39, name = Thread 10} will be cancelled.