NSThread、NSOperation、GCD 总结:
-
无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度(CPU 空闲时就会执行)
-
更新 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_async(dispatch_get_main_queue(), ^{}) //在主线程中,这里改用同步调用 dispatch_sync 的话就会造成线程阻塞情况
-
NSThread 适合轻量级多线程开发,控制线程顺序比较难,同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)
-
对于简单的多线程开发建议使用 NSObject 的扩展方法完成,而不必使用 NSThread
-
可以使用 NSThread 的 currentThread 方法取得当前线程,使用 sleepForTimeInterval: 方法让当前线程挂起
-
NSOperation 进行多线程开发可以控制线程总数及线程依赖关系
-
创建一个 NSOperation 不应该直接调用 start 方法(如果直接 start 则会在主线程中调用)而是应该放到 NSOperationQueue 中启动
-
相比 NSInvocationOperation 推荐使用 NSBlockOperation,代码简单,同时由于闭包性使他没有传参问题
-
NSOperation 是对 GCD 面向对象的 OC 封装。而 GCD 基于 C 语言开发,效率更高。建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择 NSOperation,否则使用 GCD
-
在 GCD 中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效
-
在 GCD 中一个操作是多线程还是单线程执行,取决于当前队列类型和执行方法。只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)
效果如下:
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 5 @interface ViewController () 6 - (void)layoutUI; 7 @end 8 9 @implementation ViewController 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 [self layoutUI]; 14 } 15 16 - (void)didReceiveMemoryWarning { 17 [super didReceiveMemoryWarning]; 18 // Dispose of any resources that can be recreated. 19 } 20 21 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName { 22 if (self = [super initWithStyle:UITableViewStyleGrouped]) { 23 self.navigationItem.title = @"多线程开发之三 GCD"; 24 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回首页" style:UIBarButtonItemStylePlain target:nil action:nil]; 25 26 _arrSampleName = arrSampleName; 27 } 28 return self; 29 } 30 31 - (void)layoutUI { 32 } 33 34 #pragma mark - UITableViewController相关方法重写 35 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 36 return 0.1; 37 } 38 39 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 40 return 1; 41 } 42 43 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 44 return [_arrSampleName count]; 45 } 46 47 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 48 static NSString *cellIdentifier = @"cell"; 49 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 50 if (!cell) { 51 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 52 } 53 cell.textLabel.text = _arrSampleName[indexPath.row]; 54 return cell; 55 } 56 57 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 58 switch (indexPath.row) { 59 case 0: { 60 FirstSampleViewController *firstSampleVC = [FirstSampleViewController new]; 61 [self.navigationController pushViewController:firstSampleVC animated:YES]; 62 break; 63 } 64 case 1: { 65 SecondSampleViewController *secondSampleVC = [SecondSampleViewController new]; 66 [self.navigationController pushViewController:secondSampleVC animated:YES]; 67 break; 68 } 69 default: 70 break; 71 } 72 } 73 74 @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
Common.h
1 #import <Foundation/Foundation.h> 2 3 @interface Common : NSObject 4 + (NSURL *)randomImageURL; 5 6 @end
Common.m
1 #import "Common.h" 2 3 static NSArray *arrPartOfURL = nil; 4 @implementation Common 5 6 + (NSURL *)randomImageURL { 7 if (arrPartOfURL == nil) { 8 arrPartOfURL = @[ 9 @"chronograph", 10 @"color", 11 @"modular", 12 @"utility", 13 @"mickey_mouse", 14 @"simple", 15 @"motion", 16 @"solar", 17 @"astronomy", 18 @"timer", 19 @"alarm", 20 @"world_clock" 21 ]; 22 } 23 24 NSUInteger randomVal = (arc4random() % 12); //0-11的随机数 25 NSString *imageURLStr = [NSString stringWithFormat:@"http://images.apple.com/v/watch/e/timekeeping/images/%@_large.jpg", arrPartOfURL[randomVal]]; 26 return [NSURL URLWithString:imageURLStr]; 27 } 28 29 @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 #import "Common.h" 4 5 @interface FirstSampleViewController () 6 - (void)layoutUI; 7 - (void)updateImage:(NSData *)imageData; 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 self.view.backgroundColor = [UIColor colorWithWhite:0.957 alpha:1.000]; 29 30 CGFloat width = [[UIScreen mainScreen] bounds].size.width; //bounds 返回整个屏幕大小;applicationFrame 返回去除状态栏后的屏幕大小 31 CGFloat height = width * 243.0 / 221.0; 32 const CGFloat percentVal = 3.0 / 4.0; //以屏幕宽度的3/4比例显示 33 _rescaleImageSize = CGSizeMake(width * percentVal, height * percentVal); 34 35 //NSString *path = [[NSBundle mainBundle] pathForResource:@"PictureNo@2x" ofType:@"png"]; 36 //_imgV.image = [UIImage imageWithContentsOfFile:path]; 37 38 _btnLoadImage.tintColor = [UIColor darkGrayColor]; 39 _btnLoadImage.layer.masksToBounds = YES; 40 _btnLoadImage.layer.cornerRadius = 10.0; 41 _btnLoadImage.layer.borderColor = [UIColor grayColor].CGColor; 42 _btnLoadImage.layer.borderWidth = 1.0; 43 } 44 45 - (void)updateImage:(NSData *)imageData { 46 UIImage *img = [UIImage imageWithData:imageData]; 47 _imgV.image = [img rescaleImageToSize:_rescaleImageSize]; 48 } 49 50 - (IBAction)loadImage:(id)sender { 51 /* 52 GCD(Grand Central Dispatch):中央调度 53 相比 NSThread 和 NSOperation 来说,他的显著优点是:对于多核运算更加有效 54 1、GCD 的一个重要概念是队列,他的核心理念:将多个任务添加到 dispatch queue(调度队列)中,系统会为我们管理这些 dispath queue,为我们在多个线程上分配并执行任务,我们不需要直接启动和管理后台线程。 55 2、系统提供了许多预定义的 dispatch queue,包括可以保证始终在主线程上执行工作的 dispatch queue。也可以创建自己的dispatch queue,而且可以创建任意多个。GCD的 dispatch queue 严格遵循 FIFO(先进先出)原则,添加到 dispatch queue 的任务将始终按照加入 dispatch queue 的顺序启动。 56 3、dispatch queue按先进先出的顺序,串行或并发地执行任务 57 1> serial dispatch queue(串行调度队列):一次只能执行一个任务,当前任务完成后才开始出列并启动下一个任务 58 2> concurrent dispatch queue(并发调度队列):则尽可能多地启动任务并发执行 59 60 同步和异步决定了要不要开启新的线程 61 同步:在当前线程中执行任务,不具备开启新线程的能力(如果当前线程是主线程的话,会造成线程阻塞,一般比较少用) 62 异步:在新的线程中执行任务,具备开启新线程的能力 63 64 串行和并发决定了任务的执行方式 65 串行:一个任务执行完毕后,再执行下一个任务 66 并发:多个任务并发(同时)执行 67 */ 68 69 /* 70 系统给每个应用提供了四个全局并发队列,整个应用内全局共享,他们的区别是通过「全局并发队列优先级」;优先级越高,队列越先被列入执行计划中 71 #define DISPATCH_QUEUE_PRIORITY_HIGH 2 72 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 73 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) 74 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 75 */ 76 77 //全局并发队列 78 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 79 80 //自定义串行队列;第二个参数为 NULL 或 DISPATCH_QUEUE_SERIAL 表示串行队列,而为 DISPATCH_QUEUE_CONCURRENT 表示并发队列,但一般用 dispatch_get_global_queue 方法获取并发队列 81 //dispatch_queue_t customSerialQueue = dispatch_queue_create("Custom Queue", NULL); 82 83 //主队列;属于串行队列 84 dispatch_queue_t mainQueue = dispatch_get_main_queue(); 85 86 87 //方法一:自定义串行队列同步:不会开启新线程、串行执行任务 88 // dispatch_sync(customSerialQueue, ^{ 89 // NSLog(@"自定义串行队列同步,线程:%@", [NSThread currentThread]); //所在的线程,这里就是在主线程 90 // NSURL *url = [Common randomImageURL]; 91 // NSData *data = [NSData dataWithContentsOfURL:url]; 92 // 93 // dispatch_async(mainQueue, ^{ //主队列异步(同步也一样):不会开启新线程、串行执行任务 94 // NSLog(@"主队列异步(同步也一样),线程:%@", [NSThread currentThread]); //主队列所对应的主线程 95 // [self updateImage:data]; 96 // }); 97 // }); 98 99 //方法二:自定义串行队列异步:开启新线程、串行执行任务 100 // dispatch_async(customSerialQueue, ^{ 101 // NSLog(@"自定义串行队列异步,线程:%@", [NSThread currentThread]); //开启的新线程 102 // NSURL *url = [Common randomImageURL]; 103 // NSData *data = [NSData dataWithContentsOfURL:url]; 104 // 105 // dispatch_async(mainQueue, ^{ //主队列异步(同步也一样):不会开启新线程、串行执行任务 106 // NSLog(@"主队列异步(同步也一样),线程:%@", [NSThread currentThread]); //主队列所对应的主线程 107 // [self updateImage:data]; 108 // }); 109 // }); 110 111 112 //方法三:全局并发队列同步:不会开启新线程、串行执行任务 113 // dispatch_sync(globalQueue, ^{ 114 // NSLog(@"全局并发队列同步,线程:%@", [NSThread currentThread]); //所在的线程,这里就是在主线程 115 // NSURL *url = [Common randomImageURL]; 116 // NSData *data = [NSData dataWithContentsOfURL:url]; 117 // 118 // dispatch_async(mainQueue, ^{ //主队列异步(同步也一样):不会开启新线程、串行执行任务 119 // NSLog(@"主队列异步(同步也一样),线程:%@", [NSThread currentThread]); //主队列所对应的主线程 120 // [self updateImage:data]; 121 // }); 122 // }); 123 124 125 //方法四:全局并发队列异步:开启新线程、并发执行任务 126 dispatch_async(globalQueue, ^{ 127 NSLog(@"全局并发队列异步,线程:%@", [NSThread currentThread]); //开启的新线程 128 NSURL *url = [Common randomImageURL]; 129 NSData *data = [NSData dataWithContentsOfURL:url]; 130 131 dispatch_async(mainQueue, ^{ //主队列异步(同步也一样):不会开启新线程、串行执行任务 132 NSLog(@"主队列异步(同步也一样),线程:%@", [NSThread currentThread]); //主队列所对应的主线程 133 [self updateImage:data]; 134 }); 135 }); 136 } 137 138 @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 "UIImage+RescaleImage.h" 3 #import "Common.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:(NSData *)imageData withImageIndex:(NSInteger)imageIndex; 12 -(NSData *)requestData:(NSInteger)imageIndex; 13 - (void)loadImageFromNetwork:(NSInteger)imageIndex; 14 - (void)loadImageByDispatchAsync; 15 - (void)loadImageByDispatchApply; 16 - (void)loadImageByDispatchGroupAsync; 17 - (void)additionalInfo; 18 @end 19 20 @implementation SecondSampleViewController 21 22 - (void)viewDidLoad { 23 [super viewDidLoad]; 24 25 [self layoutUI]; 26 } 27 28 - (void)didReceiveMemoryWarning { 29 [super didReceiveMemoryWarning]; 30 // Dispose of any resources that can be recreated. 31 } 32 33 - (void)dealloc { 34 _mArrImageView = nil; 35 } 36 37 - (void)layoutUI { 38 self.view.backgroundColor = [UIColor colorWithWhite:0.957 alpha:1.000]; 39 40 CGFloat width = ([[UIScreen mainScreen] bounds].size.width - ((kColumnCount + 1) * kCellSpacing)) / kColumnCount; 41 _rescaleImageSize = CGSizeMake(width, width); 42 43 CGFloat heightOfStatusAndNav = 20.0 + 44.0; 44 NSString *path = [[NSBundle mainBundle] pathForResource:@"PictureNo@2x" ofType:@"png"]; 45 UIImage *img = [UIImage imageWithContentsOfFile:path]; 46 _mArrImageView = [NSMutableArray arrayWithCapacity:kRowCount * kColumnCount]; 47 //初始化多个图片视图 48 for (NSUInteger i=0; i<kRowCount; i++) { 49 for (NSUInteger j=0; j<kColumnCount; j++) { 50 UIImageView *imgV = [[UIImageView alloc] initWithFrame: 51 CGRectMake(_rescaleImageSize.width * j + kCellSpacing * (j+1), 52 _rescaleImageSize.height * i + kCellSpacing * (i+1) + heightOfStatusAndNav, 53 _rescaleImageSize.width, 54 _rescaleImageSize.height)]; 55 imgV.image = img; 56 [self.view addSubview:imgV]; 57 [_mArrImageView addObject:imgV]; 58 } 59 } 60 61 _btnLoadImage.tintColor = [UIColor darkGrayColor]; 62 _btnLoadImage.layer.masksToBounds = YES; 63 _btnLoadImage.layer.cornerRadius = 10.0; 64 _btnLoadImage.layer.borderColor = [UIColor grayColor].CGColor; 65 _btnLoadImage.layer.borderWidth = 1.0; 66 } 67 68 - (void)updateImage:(NSData *)imageData withImageIndex:(NSInteger)imageIndex { 69 UIImage *img = [UIImage imageWithData:imageData]; 70 UIImageView *imgVCurrent = _mArrImageView[imageIndex]; 71 imgVCurrent.image = [img rescaleImageToSize:_rescaleImageSize]; 72 } 73 74 -(NSData *)requestData:(NSInteger)imageIndex { 75 //对于多线程操作,建议把线程操作放到 @autoreleasepool 中 76 @autoreleasepool { 77 NSURL *url = [Common randomImageURL]; 78 NSData *data = [NSData dataWithContentsOfURL:url]; 79 return data; 80 } 81 } 82 83 - (void)loadImageFromNetwork:(NSInteger)imageIndex { 84 NSData *data = [self requestData:imageIndex]; 85 86 NSLog(@"Current thread:%@, imageIndex:%ld", [NSThread currentThread], (long)imageIndex); //dispatch_async、dispatch_apply、dispatch_group_async 的全局并发队列,会开启多个新线程去执行多个任务;按系统管理,也可能存在一个线程执行两个任务的情况 87 88 dispatch_async(dispatch_get_main_queue(), ^{ //主队列异步(同步也一样):不会开启新线程、串行执行任务 89 NSLog(@"主队列异步(同步也一样),线程:%@", [NSThread currentThread]); //主队列所对应的主线程 90 [self updateImage:data withImageIndex:imageIndex]; 91 }); 92 } 93 94 #pragma mark - 三种方法实现多线程并发执行多个任务,并且优先加载最后一张图片 95 - (void)loadImageByDispatchAsync { 96 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //全局并发队列 97 98 dispatch_async(globalQueue, ^{ 99 NSInteger len = kRowCount * kColumnCount - 1; 100 101 NSLog(@"全局并发队列异步,线程:%@", [NSThread currentThread]); //开启的新线程 102 [self loadImageFromNetwork:len]; //优先加载最后一张图片 103 104 for (NSUInteger i=0; i<len; i++) { 105 dispatch_async(globalQueue, ^{ 106 [self loadImageFromNetwork:i]; 107 }); 108 } 109 }); 110 } 111 112 - (void)loadImageByDispatchApply { 113 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //全局并发队列 114 115 //使用 dispatch_async,全局并发队列异步:开启新线程、并发执行任务;此时会并发无序地逐个图片加载并显示出来 116 //PS:如果使用 dispatch_sync,全局并发队列同步:不会开启新线程、串行执行任务;此时会等到全部图片加载完才全部显示出来 117 dispatch_async(globalQueue, ^{ 118 size_t len = kRowCount * kColumnCount - 1; 119 120 NSLog(@"全局并发队列异步,线程:%@", [NSThread currentThread]); //开启的新线程 121 [self loadImageFromNetwork:len]; //优先加载最后一张图片 122 123 //使用 dispatch_apply 或 dispatch_apply_f 来循环迭代执行任务;前提是迭代的任务相互独立,而且任务执行顺序是无关紧要的; 124 //他每次循环迭代会将指定的任务提交到 queue,queue 会开启多个新线程(线程会尽量复用,线程更少,系统多线程之间切换调度更快)去执行多个任务,执行顺序是不会像 for 循环那样有序执行; 125 //但是他像 for 循环一样会在所有任务循环迭代执行完后才返回,会阻塞当前线程,所以只用于并发队列。如果用于串行队列,会造成死锁,无法正常执行 126 dispatch_apply(len, globalQueue, ^(size_t i) { 127 [self loadImageFromNetwork:i]; 128 }); 129 }); 130 } 131 132 - (void)loadImageByDispatchGroupAsync { 133 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //全局并发队列 134 135 //使用 dispatch_async,全局并发队列异步:开启新线程、并发执行任务;此时会并发无序地逐个图片加载并显示出来 136 //PS:如果使用 dispatch_sync,全局并发队列同步:不会开启新线程、串行执行任务;此时会等到全部图片加载完才全部显示出来 137 dispatch_async(globalQueue, ^{ 138 NSInteger len = kRowCount * kColumnCount - 1; 139 140 NSLog(@"全局并发队列异步,线程:%@", [NSThread currentThread]); //开启的新线程 141 [self loadImageFromNetwork:len]; //优先加载最后一张图片 142 143 dispatch_group_t group = dispatch_group_create(); //自定义组 144 for (NSUInteger i=0; i<len; i++) { 145 //把一系列任务提交到队列中,并统一关联到自定义组中;这样并发全部执行完毕后,能通过 dispatch_group_notify 进行后续处理 146 dispatch_group_async(group, globalQueue, ^{ 147 [self loadImageFromNetwork:i]; 148 }); 149 } 150 151 //自定义组通知;会根据队列类型决定是否开启新线程;如果是并发队列就会开启新线程(实际上是:复用自定义组执行最后一个任务的对应线程),否则是串行队列就不会 152 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 153 NSLog(@"自定义组通知,线程:%@", [NSThread currentThread]); 154 NSLog(@"全部的 dispatch_group_async 任务执行完毕后,做一些事情,只执行一次"); 155 }); 156 }); 157 } 158 159 - (void)additionalInfo { 160 //dispatch_suspend(queue); //挂起队列方法;当前正在执行的任务不会停下来,只是不再继续执行未启动的任务 161 //dispatch_resume(queue); //恢复队列方法;确保他跟 dispatch_suspend 成对调用 162 163 //单次执行任务方法;方法是线程安全的 164 static dispatch_once_t onceToken; 165 dispatch_once(&onceToken, ^{ 166 NSLog(@"dispatch_once:单次执行任务;只会执行一次,重复调用方法也不会重复执行(可用于应用程序中初始化全局数据的情况,例如:单例模式)"); 167 }); 168 169 //类似 - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay 的延时执行方法 170 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 171 NSLog(@"dispatch_after:延时执行任务,这里2.0秒后做一些事情"); 172 }); 173 } 174 175 - (IBAction)loadImage:(id)sender { 176 //三种方法实现多线程并发执行多个任务,并且优先加载最后一张图片 177 //方法一:dispatch_async 178 //[self loadImageByDispatchAsync]; 179 180 //方法二:dispatch_apply 181 //[self loadImageByDispatchApply]; 182 183 //方法三:dispatch_group_async 184 [self loadImageByDispatchGroupAsync]; 185 186 187 //附加信息 188 [self additionalInfo]; 189 } 190 191 @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>
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
输出结果:
方法三:dispatch_group_async
1 2015-08-30 17:19:44.168 GCDDemo[4776:70114] 全局并发队列异步,线程:<NSThread: 0x7f9511c7a920>{number = 4, name = (null)} 2 2015-08-30 17:19:44.168 GCDDemo[4776:70265] dispatch_once:单次执行任务;只会执行一次,重复调用方法也不会重复执行(可用于应用程序中初始化全局数据的情况,例如:单例模式) 3 2015-08-30 17:19:44.229 GCDDemo[4776:70265] Current thread:<NSThread: 0x7f9511c7a920>{number = 4, name = (null)}, imageIndex:11 4 2015-08-30 17:19:44.230 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 5 2015-08-30 17:19:44.283 GCDDemo[4776:70265] Current thread:<NSThread: 0x7f9511c7a920>{number = 4, name = (null)}, imageIndex:0 6 2015-08-30 17:19:44.283 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 7 2015-08-30 17:19:44.301 GCDDemo[4776:70248] Current thread:<NSThread: 0x7f9511f2b3f0>{number = 2, name = (null)}, imageIndex:1 8 2015-08-30 17:19:44.301 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 9 2015-08-30 17:19:44.516 GCDDemo[4776:70269] Current thread:<NSThread: 0x7f9511ef52f0>{number = 5, name = (null)}, imageIndex:2 10 2015-08-30 17:19:44.516 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 11 2015-08-30 17:19:44.887 GCDDemo[4776:70241] Current thread:<NSThread: 0x7f9511d94230>{number = 6, name = (null)}, imageIndex:6 12 2015-08-30 17:19:44.888 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 13 2015-08-30 17:19:44.999 GCDDemo[4776:70299] Current thread:<NSThread: 0x7f9511d93d40>{number = 7, name = (null)}, imageIndex:3 14 2015-08-30 17:19:44.999 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 15 2015-08-30 17:19:45.152 GCDDemo[4776:70301] Current thread:<NSThread: 0x7f9511d78f90>{number = 8, name = (null)}, imageIndex:5 16 2015-08-30 17:19:45.152 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 17 2015-08-30 17:19:45.190 GCDDemo[4776:70300] Current thread:<NSThread: 0x7f9511ef0340>{number = 9, name = (null)}, imageIndex:4 18 2015-08-30 17:19:45.190 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 19 2015-08-30 17:19:45.240 GCDDemo[4776:70304] Current thread:<NSThread: 0x7f9511d8e9d0>{number = 10, name = (null)}, imageIndex:8 20 2015-08-30 17:19:45.240 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 21 2015-08-30 17:19:45.314 GCDDemo[4776:70305] Current thread:<NSThread: 0x7f9511ecd030>{number = 11, name = (null)}, imageIndex:10 22 2015-08-30 17:19:45.314 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 23 2015-08-30 17:19:45.443 GCDDemo[4776:70302] Current thread:<NSThread: 0x7f9511d8fc50>{number = 12, name = (null)}, imageIndex:7 24 2015-08-30 17:19:45.443 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 25 2015-08-30 17:19:45.615 GCDDemo[4776:70303] Current thread:<NSThread: 0x7f9511d91740>{number = 13, name = (null)}, imageIndex:9 26 2015-08-30 17:19:45.615 GCDDemo[4776:70114] 主队列异步(同步也一样),线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 27 2015-08-30 17:19:45.620 GCDDemo[4776:70114] 自定义组通知,线程:<NSThread: 0x7f9511d0a9a0>{number = 1, name = main} 28 2015-08-30 17:19:45.620 GCDDemo[4776:70114] 全部的 dispatch_group_async 任务执行完毕后,做一些事情,只执行一次 29 2015-08-30 17:19:46.360 GCDDemo[4776:70114] dispatch_after:延时执行任务,这里2.0秒后做一些事情
方法一:dispatch_async
1 2015-08-30 17:15:08.490 GCDDemo[4668:66939] 全局并发队列异步,线程:<NSThread: 0x7fc5be00c810>{number = 2, name = (null)} 2 2015-08-30 17:15:08.490 GCDDemo[4668:66894] dispatch_once:单次执行任务;只会执行一次,重复调用方法也不会重复执行(可用于应用程序中初始化全局数据的情况,例如:单例模式) 3 2015-08-30 17:15:08.862 GCDDemo[4668:66939] Current thread:<NSThread: 0x7fc5be00c810>{number = 2, name = (null)}, imageIndex:11 4 2015-08-30 17:15:08.862 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 5 2015-08-30 17:15:09.062 GCDDemo[4668:66981] Current thread:<NSThread: 0x7fc5be05ac90>{number = 4, name = (null)}, imageIndex:3 6 2015-08-30 17:15:09.062 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 7 2015-08-30 17:15:09.066 GCDDemo[4668:66939] Current thread:<NSThread: 0x7fc5be00c810>{number = 2, name = (null)}, imageIndex:0 8 2015-08-30 17:15:09.067 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 9 2015-08-30 17:15:09.323 GCDDemo[4668:66941] Current thread:<NSThread: 0x7fc5bbf60040>{number = 5, name = (null)}, imageIndex:2 10 2015-08-30 17:15:09.323 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 11 2015-08-30 17:15:09.323 GCDDemo[4668:66940] Current thread:<NSThread: 0x7fc5be05bf50>{number = 6, name = (null)}, imageIndex:1 12 2015-08-30 17:15:09.324 GCDDemo[4668:66942] Current thread:<NSThread: 0x7fc5bbd05bf0>{number = 7, name = (null)}, imageIndex:4 13 2015-08-30 17:15:09.327 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 14 2015-08-30 17:15:09.330 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 15 2015-08-30 17:15:09.486 GCDDemo[4668:66991] Current thread:<NSThread: 0x7fc5bbd1f880>{number = 8, name = (null)}, imageIndex:7 16 2015-08-30 17:15:09.486 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 17 2015-08-30 17:15:10.156 GCDDemo[4668:66988] Current thread:<NSThread: 0x7fc5be05ae70>{number = 9, name = (null)}, imageIndex:5 18 2015-08-30 17:15:10.156 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 19 2015-08-30 17:15:10.208 GCDDemo[4668:66990] Current thread:<NSThread: 0x7fc5bbd26b00>{number = 10, name = (null)}, imageIndex:6 20 2015-08-30 17:15:10.209 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 21 2015-08-30 17:15:10.239 GCDDemo[4668:66992] Current thread:<NSThread: 0x7fc5bbe9ff30>{number = 11, name = (null)}, imageIndex:8 22 2015-08-30 17:15:10.239 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 23 2015-08-30 17:15:10.491 GCDDemo[4668:66894] dispatch_after:延时执行任务,这里2.0秒后做一些事情 24 2015-08-30 17:15:10.838 GCDDemo[4668:66993] Current thread:<NSThread: 0x7fc5bbea3940>{number = 12, name = (null)}, imageIndex:9 25 2015-08-30 17:15:10.838 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main} 26 2015-08-30 17:15:11.489 GCDDemo[4668:66989] Current thread:<NSThread: 0x7fc5bbea4dd0>{number = 13, name = (null)}, imageIndex:10 27 2015-08-30 17:15:11.490 GCDDemo[4668:66894] 主队列异步(同步也一样),线程:<NSThread: 0x7fc5bbe280d0>{number = 1, name = main}
方法二:dispatch_apply
1 2015-08-30 17:15:50.873 GCDDemo[4694:67547] 全局并发队列异步,线程:<NSThread: 0x7fe57149bb60>{number = 2, name = (null)} 2 2015-08-30 17:15:50.873 GCDDemo[4694:67503] dispatch_once:单次执行任务;只会执行一次,重复调用方法也不会重复执行(可用于应用程序中初始化全局数据的情况,例如:单例模式) 3 2015-08-30 17:15:50.973 GCDDemo[4694:67547] Current thread:<NSThread: 0x7fe57149bb60>{number = 2, name = (null)}, imageIndex:11 4 2015-08-30 17:15:50.974 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 5 2015-08-30 17:15:51.018 GCDDemo[4694:67546] Current thread:<NSThread: 0x7fe5714f8490>{number = 4, name = (null)}, imageIndex:1 6 2015-08-30 17:15:51.018 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 7 2015-08-30 17:15:51.078 GCDDemo[4694:67546] Current thread:<NSThread: 0x7fe5714f8490>{number = 4, name = (null)}, imageIndex:4 8 2015-08-30 17:15:51.078 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 9 2015-08-30 17:15:51.087 GCDDemo[4694:67562] Current thread:<NSThread: 0x7fe5717bd740>{number = 5, name = (null)}, imageIndex:2 10 2015-08-30 17:15:51.087 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 11 2015-08-30 17:15:51.200 GCDDemo[4694:67547] Current thread:<NSThread: 0x7fe57149bb60>{number = 2, name = (null)}, imageIndex:0 12 2015-08-30 17:15:51.200 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 13 2015-08-30 17:15:51.271 GCDDemo[4694:67562] Current thread:<NSThread: 0x7fe5717bd740>{number = 5, name = (null)}, imageIndex:6 14 2015-08-30 17:15:51.271 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 15 2015-08-30 17:15:51.628 GCDDemo[4694:67549] Current thread:<NSThread: 0x7fe57152d910>{number = 6, name = (null)}, imageIndex:3 16 2015-08-30 17:15:51.628 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 17 2015-08-30 17:15:51.701 GCDDemo[4694:67546] Current thread:<NSThread: 0x7fe5714f8490>{number = 4, name = (null)}, imageIndex:5 18 2015-08-30 17:15:51.701 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 19 2015-08-30 17:15:51.800 GCDDemo[4694:67547] Current thread:<NSThread: 0x7fe57149bb60>{number = 2, name = (null)}, imageIndex:7 20 2015-08-30 17:15:51.800 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 21 2015-08-30 17:15:52.063 GCDDemo[4694:67549] Current thread:<NSThread: 0x7fe57152d910>{number = 6, name = (null)}, imageIndex:9 22 2015-08-30 17:15:52.064 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 23 2015-08-30 17:15:52.109 GCDDemo[4694:67562] Current thread:<NSThread: 0x7fe5717bd740>{number = 5, name = (null)}, imageIndex:8 24 2015-08-30 17:15:52.110 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 25 2015-08-30 17:15:52.295 GCDDemo[4694:67546] Current thread:<NSThread: 0x7fe5714f8490>{number = 4, name = (null)}, imageIndex:10 26 2015-08-30 17:15:52.295 GCDDemo[4694:67503] 主队列异步(同步也一样),线程:<NSThread: 0x7fe571428b10>{number = 1, name = main} 27 2015-08-30 17:15:52.874 GCDDemo[4694:67503] dispatch_after:延时执行任务,这里2.0秒后做一些事情