多线程开发之一 NSThread
每个 iOS 应用程序都有个专门用来更新显示 UI 界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来不好的用户体验。
一般的解决方案就是:将那些耗时的操作放到另外一个线程中去执行,多线程编程就是防止主线程堵塞和增加运行效率的最佳方法。
iOS 支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便,也是 Apple 最推荐使用的方法。
下面根据抽象层次从低到高依次列出 iOS 所支持的多线程编程方法:
-
NSThread :是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销
-
NSOperation:是基于 OC 实现的,NSOperation 以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。
NSOperation 是一个抽象基类,iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation,当然也可以自定义 NSOperation
-
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.