Multithreading annd Grand Central Dispatch on ios for Beginners Tutorial-多线程和GCD的入门教程...

本文主要是介绍Multithreading annd Grand Central Dispatch on ios for Beginners Tutorial-多线程和GCD的入门教程...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Multithreading annd Grand Central Dispatch on ios for Beginners Tutorial-多线程和GCD的入门教程

原文链接:Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial

Have you ever written an app where you tried to do something,and there was a long pause while the UI was unresponsive?This is usually a sign that your app needs multithreading!In this tutorial,you'll get hands on experience with the core multithreading API available on ios:Grand Central Dispatch.You'll take an app that doesn't use multithreading at all(and hence is very unresponsive),and convert it to use multithreading.You'll be shocked by the difference!

This tutorial assumes you are familiar with the basics of ios development.If you are completely new to ios development,you should check out some of the other tutorials on this site first.Without further ado,take a swig of soda or chew some bubble gum and begin this tutorial at the same time - and you're already on your way to multithreading!

pic:Convert a slow, unresponsive app to a speedy cheetah with Grand Central Dispatch!

Why Should I Care?

“Ahem, so why are you telling me this? Why should I care? I don’t care. What’d you have for lunch today?”

If you’re like a certain puppet, you might still be skeptical why you should care about all this multithreading business.

So let’s show you why with a practical example of an app that doesn’t use multithreading at all.

Download the starter project, open it up with Xcode, and compile and run. You’ll see a free game art pack from vickiwenderlich.com displayed on the screen:

ImageGrabber - an unresponsive and synchronous sample app

The app is called ImageGrabber, and its job is to go through the HTML of this web page and retrieve all of the images linked within, and display them in a table view so you can look at them more closely.

The cool part is it even downloads zip files and looks for images inside the zip files, such as the free game art zip linked on the site!

Go ahead and tap the “Grab!” button to see if it works.

…waiting…

…waiting…

…waiting…

Tomato-San is angry!

Tomato-San is angry!

Wow! It finally worked, but that took forever! The app was parsing the HTML, downloading the images and the zip file, and unzipping the zip file, all on the main thread.

The end result was the user had to sit there for a significant amount of time waiting, not sure if the app was working at all!

The consequences of this are dire: the user might quit the app, the OS might terminate the app for taking too long, or you might get an angry Tomato attacking your treehouse.

Luckily, multithreading comes to the rescue! Instead of putting all of this heavy-duty work on the main thread, we’ll move it to the background with some simple APIs provided by Apple.

Multithreading… and cats!

If you’re already familiar with the concept of multithreading, feel free to skip to the next section. But if you’re completely new – read ahead!

When you think of a program running, you can think of it like a cat with big arrow pointing to the line it’s currently on. The cat moves the arrow as the program advances through its logic, one step at a time.

Multithreading is like a cat with an arrow.

Multithreading is like a bunch of cats with a arrows.
Image credit: Diego Grez

The problem with the Image Grabber app is we’re basically exhausting our poor cat by doing all the work in the main thread. So before the app could redraw the UI or respond to user events, it has to finish all of that time intensive work of downloading files, parsing HTML, etc.

Don't overwork your cat - or the main thread!

Don't overwork your cat - or the main thread!
Image credit: Diego Grez

So how do we give our overworked cat a break? The solution is simple – buy more cats! (In fact I have a friend who is really good at this!)

That way your main cat can be responsible for updating the UI and responding to user events, while your other cats are going around the background downloading files, parsing HTML, and jumping on the tables (get off!)

This is the gist of multithreaded programming. Just like these cats running around performing tasks, a process is broken down into multiple threads of execution.

On iOS, the methods you’re used to implementing (like viewDidLoad, button tap callbacks, etc.) all run on the main thread. You don’t want to perform time intensive work on the main thread, or else you’ll get an unresponsive UI and an overworked cat!

Kids, Do Not Do This At Home

Let’s take a look at the current code and discuss how it works – and why it’s bad!

The root view controller in the app is WebViewController. When you tap the button (grabTapped) it gets the HTML of the current page, and passes it to the ImageListViewController.

In the ImageListViewController’s viewDidLoad, it creates a new ImageManager and calls process. This class, along with ImageInfo, contain all of the time-intensive code, such as parsing the HTML, pulling down the images off the network, and unzipping files.

Let’s see how these two files work:

  • ImageManager:processHTML: Uses regular expression matching to search for links in the HTML. This could potentially be time intensive, depending on how large the HTML is. For every zip file it finds, it calls retrieveZip. For every image it finds, it creates a new ImageInfo object, with the initWithSourceURL initializer.
  • ImageInfo:initWithSourceURL: Calls getImage to retrieve the image over the network with the synchronous [NSData dataWithContentsOfURL:…] method. Much like the [NSString stringWithContentsOfURL:…] method, this method blocks the flow of execution until it’s complete, which could be a very long time! You almost never want to use this method in your apps.
  • ImageInfo:retrieveZip: Similar to the above, uses the dreaded [NSData dataWithContentsOfURL:…] which halts the thread until it completes (do not use!) When it’s done, it calls processZip.
  • ImageInfo:processZip: Uses the ZipArchive library to save the downloaded data to disk, unzip it, and look for images inside. Writing to disk and unzipping like this can be a very slow operation, so it’s another instance of work that really shouldn’t be on the main thread.

You might also notice some calls to a delegate method of ImageManager – imageInfosAvailable. This is how the ImageManager notifies the table view when there are new entries to be displayed in the table.

Take a look through and make sure you understand the current flow of execution – and why it’s so bad. You might also find it useful to run it and look at the console log as it runs, and you’ll see some NSLog statements showing where the code is as it runs.

Once you have a good idea of how it currently works, let’s move on and improve it with some multithreading!

Downloading Asynchronously

Let’s start by replacing the slowest operation with asynchronous calls – the downloading of the files.

It’s actually not that difficult to do this with the built-in Apple classes – NSURLRequest and NSURLConnection – but I’m a fan of some wrapper classes that make this even easier – ASIHTTPRequest.

We’re going to use this to asynchronously download the files, so let’s add it to your project.

If you don’t have ASIHTTPRequest already, first download it. Once you have it downloaded, right click your ImageGrabber project entry in groups and files, select New Group, and name the new group ASIHTTPRequest. Then drag all of the files from the ASIHTTPRequest\Classes directory (ASIAuthenticationDialog.h and several others, but IMPORTANT! don’t add the subfolders such as ASIWebPageRequest, CloudFiles, S3, and Tests.) into the new ASIHTTPRequest group. Make sure “Copy items into destination group’s folder (if needed)” is selected, and click Finish.

Also repeat this for the two files in ASIHTTPRequest\External\Reachability, as these are dependencies of the project.

The last step to add ASIHTTPRequest is you need to link your project against a few required frameworks. To do this, click on your ImageGrabber project entry in Groups & Files, click the PromoTest target, choose the Build Phases tab, and expand the Link Binary with Libraries section. Click the plus button in this section, and choose CFNetwork.framework. Then repeat this for SystemConfiguration.framework and MobileCoreServices.framework.

Now it’s time to replace the old bad synchronous code with the new good asynchronouse code!

Open up ImageManager.m and make the following changes:

// Add to top of file
#import "ASIHTTPRequest.h"// Replace retrieveZip with the following
- (void)retrieveZip:(NSURL *)sourceURL {NSLog(@"Getting %@...", sourceURL);__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];[request setCompletionBlock:^{NSLog(@"Zip file downloaded.");NSData *data = [request responseData];[self processZip:data sourceURL:sourceURL];        }];[request setFailedBlock:^{NSError *error = [request error];NSLog(@"Error downloading zip file: %@", error.localizedDescription);}];[request startAsynchronous];    
}

This sets up an ASIHTTPRequest with a given URL. It sets up a block of code to run when the request finishes, and one to run if the requst fails for some reason.

Then it calls startAsynchronous. This method returns immediately so the main thread can continue going about its business such as animating the UI and responding to user input. In the meantime, the OS will automatically run the code to download the zip file on a background thread, and call one of the callback blocks when it completes or fails!

Similarly, switch to ImageInfo.m and make similar changes there:

// Add to top of file
#import "ASIHTTPRequest.h"// Replace getImage with the following
- (void)getImage {NSLog(@"Getting %@...", sourceURL);__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:sourceURL];[request setCompletionBlock:^{NSLog(@"Image downloaded.");NSData *data = [request responseData];image = [[UIImage alloc] initWithData:data];}];[request setFailedBlock:^{NSError *error = [request error];NSLog(@"Error downloading image: %@", error.localizedDescription);}];[request startAsynchronous];    
}

This is pretty much the same as the other code – it runs the download in the background, and when it completes sets the image instance variable to the result.

Let’s see if it works! Compile and run and tap “Grab!” and viola – it quickly switches to the detail tab rather than having a super-long pause! However there’s one major problem:

Update a row in a UITableView when image loads

The images don’t show up in the table view after they’re downloaded! You can get them to show up by scrolling the table up and down (which works because the data for the row is reloaded after it goes offscreen), but that is kind of a hack. How can we fix this?

Introducing NSNotifications

One easy way to send updates from one part of your code to another is Apple’s built-in NSNotification system.

It’s quite simple. You get the NSNotificationCenter singleton (via [NSNotificationCenter defaultCenter]) and:

  1. If you have an update you want to send, you call postNotificationName. You just give it a unique string you make up (such as “com.razeware.imagegrabber.imageupdated”) and an object (such as the ImageInfo that just finished downloading its image).
  2. If you want to find out when this update happens, you call addObserver:selector:name:object. In our case the ImageListViewController will want to know when this happens so it can reload the appropriate table view cell. A good spot to put this is in viewDidLoad.
  3. Don’t forget to call removeObserver:name:object when the view gets unloaded. Otherwise, the notification system might try to call a method on an unloaded view (or worse an unallocated object), which would be a bad thing!

So let’s try this out. Open up ImageInfo.m and make the following modification:

// Add inside getImage, right after image = [[UIImage alloc] initWithData:data];
[[NSNotificationCenter defaultCenter] postNotificationName:@"com.razeware.imagegrabber.imageupdated" object:self];

So once the image is downloaded, we post a notification and pass in this object that just got updated (self).

Next switch to ImageListViewController.m and make the following modifications:

// At end of viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageUpdated:) name:@"com.razeware.imagegrabber.imageupdated" object:nil];// At end of viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"com.razeware.imagegrabber.imageupdated" object:nil];// Add new method
- (void)imageUpdated:(NSNotification *)notif {ImageInfo * info = [notif object];int row = [imageInfos indexOfObject:info];NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];NSLog(@"Image for row %d updated!", row);[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];}

This registers for the notification in viewDidUnload, basically saying “hey call imageUpdated when this notifiation arrives!” It also deregisters appropriately in viewDidUnload.

The imageUpdated callback looks inside the array of imageInfos for the passed in object. Once it finds it, it gets the indexPath of that row, and tells the table view to reload that row.

Compile and run, and now you’ll see the images pop in as they’re downloaded!

Asynchronous image loading

Grand Central Dispatch and Dispatch Queues, Oh My!

There’s still a problem with our app. If you tap the “Grab!” button and keep scrolling up and down continuously as soon as the detail view loads, after the zip file downloads you’ll see the entire UI freeze as it’s saving and unzipping the zip file.

This is because the completion block in ASIHTTPRequest gets called in the main thread, and we called the code to process the zip file within the main thread:

[request setCompletionBlock:^{NSLog(@"Zip file downloaded.");NSData *data = [request responseData];[self processZip:data sourceURL:sourceURL]; // Ack - heavy work on main thread!
}];

So how can we run this heavy work in the background?

Well, iOS 3.2 introduced a very simple (and very efficient) way to do this via the Grand Central Dispatch system. Basically, whenever you want to run something in the background, you just call dispatch_async and pass in some code to run.

Grand Central Dispatch will handle all of the details for you – it will create a new thread if it needs to, or reuse an old one if one is available.

When you call dispatch_async, you pass in a dispatch queue. You can think of this as an list that stores all the blocks that you pass in, first in first out.

You can make your own dispatch queues (via dispatch_create), or you can get a special dispatch queue for the main thread (via dispatch_get_main_queue). We’ll be making a background queue called “backgroundQueue” that we’ll use to run processing tasks in the background, like parsing XML or saving/unzipping zip files.

Dispatch Queues, Locks, and Cat Food

A dispatch queue is set up by default to be serial – this means only one block of code from the queue runs at a time. This can be pretty convenient, because you can use this behaviour to protect shared data.

If you aren’t familiar with locks in multithreading, think back to our earlier example about cats. What would happen if two cats wanted to go to the cat food dish at the same time? Big problems, that’s what!

But what if we made all of our cats get in a line instead. And we’d say “hey cat, if you want to access this cat dish, you have to stand in this line!” If only life were this easy!

Maybe GCD really stands for Grand Cat Dispatch?

Maybe GCD really stands for Grand Cat Dispatch?

That’s the basic idea behind using dispatch queues to protect data. You set up your code so that a particular data structure is only accessed by code running within a particular dispatch queue. Then since dispatch queues run blocks serially, you’re guaranteed that only one will access the data structure at a time.

In this app we have two data structures we have to protect:

  1. The linkURLs array inside ImageListViewController. To protect this, we’ll structure our code so that this is only ever touched in the main thread.
  2. The pendingZips variable inside ImageManager. To protect this, we’ll structure our code so that this is only ever touched in our “backgroundQueue”.

OK enough chat about Grand Central Dispatch – let’s try it out!

Grand Central Dispatch in Practice

Start by opening up ImageGrabber.h and make the following changes:

// Add to top of file
#import <dispatch/dispatch.h>// Add new instance variable
dispatch_queue_t backgroundQueue;

To use Grand Central Dispatch, you first need to import . We also predeclare the dispatch queue we’ll be using to run our background processing tasks.

Next open up ImageGrabber.m and make the following changes:

// 1) Add to bottom of initWithHTML:delegate
backgroundQueue = dispatch_queue_create("com.razeware.imagegrabber.bgqueue", NULL);        // 2) Add to top of dealloc
dispatch_release(backgroundQueue);// 3) Modify process to be the following
- (void)process {    dispatch_async(backgroundQueue, ^(void) {[self processHtml];});    
}// 4) Modify call to processZip inside retrieveZip to be the following
dispatch_async(backgroundQueue, ^(void) {[self processZip:data sourceURL:sourceURL];
});// 5) Modify call to delegate at the end of processHTML **AND** processZip to be the following
dispatch_async(dispatch_get_main_queue(), ^(void) {[delegate imageInfosAvailable:imageInfos done:(pendingZips==0)];
});

These are all simple but important calls, so let’s discuss each one in turn.

  1. This creates the dispatch queue. When you create a dispatch queue you need to give it a unique name as a string. One good way to create unique names is to use reverse DNS notation like this.
  2. When you create a dispatch queue, don’t forget to release it! For this queue we’ll release it when the ImageManager is deallocated.
  3. The old process just ran processHTML directly, hence ran it in the main thread blocking the UI as the HTML was parsed. Now, we run it in the background on the backgroundQueue we created, with a simple call to dispatch_async!
  4. Similarly, after we download the zip we get a callback in the main thread from ASIHTTPRequeset saying “hey, I’m done!” Instead of blocking the UI as we save and unzip the zip file like we did before, now we run it on the background queue. This is also important to make sure that the pendingZips variable is protected.
  5. We want to make sure that we call the delegate method within the context of the main thread. First, to make sure that the linkURLs array in the view controller is only accessed via the main thread, according to our strategy discussion earlier. Second because that method interacts with UIKit objects, and UIKit objects can only be used by the main thread.

That’s it! Compile and run your code, and ImageGrabber should behave much more responsively!

A responsive app with Grand Central Dispatch

But Wait!

If you’ve been programming on iOS for a while, you may have heard of these fancy things called NSOperations, and operation queues. You might wonder when you should use them, and when you should use Grand Central Dispatch.

Well, NSOperations are simply an API built on top of Grand Central Dispatch. So when you’re using NSOperations, you’re really still using Grand Central Dispatch.

It’s just that NSOperations give you some fancy features that you might like. You can make some operations dependent on other operations, reorder queues after you sumbit items, and other things like that.

In fact, ImageGrabber is already using NSOperations and operation queues! ASIHTTPRequest uses them under the hood, and you can configure the operation queue it uses for different behavior if you’d like.

So which should you use? Whichever makes sense for your app. For this app it’s pretty simple so we just used Grand Central Dispatch directly, no need for the fancy features of NSOperation. But if you need them for your app, feel free to use it!

Where To Go From Here?

Here is a sample project with all of the code from the above tutorial.

You now have some practical experience with using asynchronous operations and grand central dispatch on iOS. But this tutorial has barely scratched the surface – there’s a lot more you can learn!

I’d first suggest listening to the great Apple videos related to Grand Central Dispatch. Both WWDC 2010 and 2011 have some videos that are a great introduction to what’s available.

And if you really want to get into things, Mike Ash has some great articles on Grand Central Dispatch that you might want to check out.

If you have any questions, comments, or suggestions, please join the forum discussion below!

posted on 2016-06-12 18:42  RunningSnail 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/tgycoder/p/5578497.html

这篇关于Multithreading annd Grand Central Dispatch on ios for Beginners Tutorial-多线程和GCD的入门教程...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/493316

相关文章

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

JAVA- 多线程

一,多线程的概念 1.并行与并发 并行:多个任务在同一时刻在cpu 上同时执行并发:多个任务在同一时刻在cpu 上交替执行 2.进程与线程 进程:就是操作系统中正在运行的一个应用程序。所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看 到进程) 线程:是程序运行的基本执行单元。当操作系统执行一个程序时, 会在系统中建立一个进程,该进程必须至少建立一个线

Weex入门教程之4,获取当前全局环境变量和配置信息(屏幕高度、宽度等)

$getConfig() 获取当前全局环境变量和配置信息。 Returns: config (object): 配置对象;bundleUrl (string): bundle 的 url;debug (boolean): 是否是调试模式;env (object): 环境对象; weexVersion (string): Weex sdk 版本;appName (string): 应用名字;

Weex入门教程之3,使用 Vue 开发 Weex 页面

环境安装 在这里简略地介绍下,详细看官方教程 Node.js 环境 Node.js官网 通常,安装了 Node.js 环境,npm 包管理工具也随之安装了。因此,直接使用 npm 来安装 weex-toolkit。 npm 是一个 JavaScript 包管理工具,它可以让开发者轻松共享和重用代码。Weex 很多依赖来自社区,同样,Weex 也将很多工具发布到社区方便开发者使用。

Weex入门教程之2,Android Studio安装Weex插件

插件位置及描述 https://plugins.jetbrains.com/idea/plugin/8460-weex 貌似对windows还不是很支持,先放着吧。 安装 插件功能 先预览下都有什么功能 安装完成Weex插件后,如果在main toolbar找不到这些功能图标,那么就需要手动添加到main toolbar 添加到main toolbar 红框内就是

Weex入门教程之1,了解Weex

【资料合集】Weex Conf回顾集锦:讲义PDF+活动视频! PDF分享:链接:http://pan.baidu.com/s/1hr8RniG 密码:fa3j 官方教程:https://weex-project.io/cn/v-0.10/guide/index.html 用意 主要是介绍Weex,并未涉及开发方面,好让我们开始开发之前充分地了解Weex到底是个什么。 以下描述主要摘取于