iOS 4 GCD 问题
我查看了 WWDC 2010 的一些演示文稿,还阅读了有关块和并发性的大部分文档,并且有几个关于在 Grand Central Dispatch 中使用具有串行队列的块的问题。 我有一个 iOS 4 项目,它有一个滚动视图和一个图像信息字典 - 图像的 url 等。我想使用 GCD 和块来下载图像并将它们放入我的滚动视图中,这样就不会阻塞主线程。我编写了以下似乎有效的代码:
for (NSDictionary* dict in images)
{
dispatch_async(image_queue, ^{
NSString* urlString = [dict objectForKey:@"url"];
NSURL* url = [NSURL URLWithString:urlString];
NSData* imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage* image = [UIImage imageWithData:imageData];
UIImageView* imageView = // initialize imageView with image;
dispatch_async(dispatch_get_main_queue(), ^{
[self.scrollView addSubview:imageView];
});
[imageData release];
});
}
我有两个问题:
根据并发指南,我不应该从封闭范围中捕获非标量类型的变量 - 在我的代码中,我捕获 dict,它是一个 NSDictionary*目的。如果我不允许捕获它,那么我应该如何编写代码?块是否只捕获实际使用的封闭范围中的变量?
如果我在通过串行调度队列获取所有图像之前离开当前 ViewController,会发生什么情况?我不认为他们知道创建它们的 ViewController 已经消失了,那么当他们执行完成处理程序(我将图像视图插入主线程上的滚动视图)时会发生什么?它会导致错误还是什么?当 ViewController 消失时,如何取消串行队列上的任何剩余操作?
此致,
I've looked at some of the presentations form WWDC 2010 and also read most of the documents on blocks and concurrency and have a couple of questions regarding using blocks with serial queues in Grand Central Dispatch.
I have an iOS 4 project that has a scrollview and a dictionary of image information - urls to the images and so on. I want to use GCD and blocks to download the images and put them in my scrollview thus not blocking the main thread. I have writen the following code which seems to work:
for (NSDictionary* dict in images)
{
dispatch_async(image_queue, ^{
NSString* urlString = [dict objectForKey:@"url"];
NSURL* url = [NSURL URLWithString:urlString];
NSData* imageData = [[NSData alloc] initWithContentsOfURL:url];
UIImage* image = [UIImage imageWithData:imageData];
UIImageView* imageView = // initialize imageView with image;
dispatch_async(dispatch_get_main_queue(), ^{
[self.scrollView addSubview:imageView];
});
[imageData release];
});
}
I have two questions:
According to the concurrency guide I should not capture variables from the enclosing scope that are non scalar types - in my code I capture dict which is an NSDictionary* object. If I am not allowed to capture it, how should I then write the code? Does a block only capture variables from the enclosing scope that are actually used?
What happens if I leave the current ViewController before all the images are fetched through the serial dispatch queue? I don't think that they are aware that the ViewController that created them is gone so what happens when they execute the completion handler where I insert the image views into my scrollview on the main thread? Does it cause an error or what? And how can I cancel any remaining operations on the serial queue when my ViewController disappears?
Best regards,
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
虽然这是一个棘手的问题,但这对于理解并发指南试图告诉您的内容非常重要:指针是标量类型。所以你可以捕获你想要的块内的指针......但是你必须认识到它们指向的内存的生命周期! NSDictionary * is-a-kind-of id,当你在块中引用一个 id 时,如果该块被复制(通过dispatch_async()),运行时将负责保留该 id,然后在该块被复制时释放它本身被释放。是的,块仅捕获在其内部引用的变量。
既然你现在知道异步块已经对自身进行了保留,那么应该清楚(呃)(模内存管理错误)你的 ViewController 在块完成之前不能“消失”。所以它不会崩溃 - 但你正确地注意到,当你实际上不打算再使用结果时,你确实需要一种方法来取消这种异步工作。一种简单但有效的模式是在异步块的开头进行测试,检查是否仍应完成工作。
While it's a niggling point, this is important to understanding what the concurrency guide is trying to tell you: Pointers are scalar types. So you can capture pointers inside of blocks all you want... BUT you have to be cognizant of the lifetime of the memory they point to! NSDictionary * is-a-kind-of id, and when you reference an id in a block, the runtime takes responsibility for retaining the id if the block is copied (which it is by dispatch_async()) and then releasing it when the block itself is deallocated. And yes, a block only captures variables that are referenced within it.
Since you now know that the async block has done a retain on self, it should be clear(er) that (modulo memory management errors) your ViewController can't "disappear" until the block is done. So it's not going to crash -- but you're correct to note that you really want a way to cancel this kind of async work when you're not actually planning to use the results anymore. One simple-but-effective pattern is to put a test at the beginning of your async block that checks to see if the work should still be done.
既然其他人已经回答了你的两个问题,我想对你的代码发表评论,并建议你不要使用 GCD 进行图像等网络查询。它们的主要问题是它们都会同时运行。根据下载的数量,您可能会创建太多的同时连接,这可能会导致蜂窝连接陷入困境,并且如果用户在争夺宝贵的资源时没有开始出现图像,则最终可能会认为出现了问题网络。
尝试使用
maxConcurrentOperationCount
值为 2 或 3 的NSOperationQueue
。这将允许您对可能无限量的网络请求进行排队,但最多只有几个并行执行。由于您可以访问设备的网络状态,因此您可以有条件地将其增加到 8(例如用于 wifi 连接)。GCD 的第二个问题是取消挂起的操作有点麻烦。如果您的用户输入视图控制器然后推回,则根据您编写代码的方式,GCD 块将保留视图控制器,防止其被释放,并且实际上所有网络操作都必须完成,直到控制器可以被已释放(因此在
dealloc
中取消连接没有意义)。我通过监控代理并快速进入视图并返回的艰难方式了解到了这一点。看到挂起的连接如何对我原本不知情的应用程序造成大约 20 秒的额外网络带宽损坏,真是令人震惊。
tl;dr 使用网络队列,GCD 用于图像处理/缩放/GUI 更新。
Since others have answered your two questions, I would like to comment on your code and recommend you to not use GCD for network queries like images. The main problem with them is that they will all run concurrently. Depending on the number of downloads, you may create way too many simultaneous connections that may bog down a cellular connection, and the user may end up thinking somethings wrong if the images don't start appearing while they are fighting for the precious network.
Try to use an
NSOperationQueue
with amaxConcurrentOperationCount
value of 2 or 3. This will allow you to queue a potentially infinite amount of network requests but have at most a few perform in parallel. Since you can access the network status of the device you can conditionally increase it to say 8 for wifi connections.And the second problem with GCD, is that it is a little bit cumbersome to cancel pending operations. If your user enters a view controller and then pushes back, depending on how you have programmed your code, GCD blocks will retain the view controller, prevent it from being released, and actually all the network operations will have to finish until the controller can be released (so no point in cancelling the connections in
dealloc
).I learned this the hard way monitoring a proxy and going quickly inside a view and back. It was really appalling to see how the pending connections inflicted about 20 seconds of extra network bandwidth damage to my otherwise unaware application.
tl;dr use a queue for the network, GCD for the image processing/scaling/GUI updating.
@Kaelin Colclasure:对于第一个问题,它似乎更可能是多线程应用程序中共享状态的问题:对于整数类型,您有一个按值复制(也适用于指针),但是当您使用指针引用的对象时,您'将会遇到与缺少锁定相关的所有问题(您在这里提到的对象生命周期问题的一种变体)。
@Kaelin Colclasure : for 1st question it seems more likely a problem of shared state in multithreaded application: for integral type you have a copy by value (which applies to pointers also), but when you will use an object referenced by a pointer you'll have all the problems related to absence of locking (kind of variation on object lifetime problem you mentioned here).
这是第一点的实际区别:
如果我将标量传递到 GCD 队列中使用的块中,那么在该块内我正在处理原始数据的副本。更改在块之外将不可见。 (对此有一些警告,例如
__block
修饰符,但一般来说这是正确的。)如果我将一个
NSMutableDictionary
传递到一个块中,则会更改字典将在块外可见——这是因为您保留了对字典的引用并且没有进行深层复制。无论哪种情况,都会为您执行内存管理,即复制标量变量或保留对象。
由于您无法在初始化后更改 NSDictionary 的内容,因此您可能会发现块会自动为您做正确的事情。
对于第二点,内存管理几乎是自动的,除非您需要处理可变对象的副本。
Here's the practical difference for your first point:
If I pass a scalar into a block used in a GCD queue, then inside the block I am working with a copy of the original data. Changes will not be visible outside the block. (There are caveats to this, the
__block
modifier for instance but in general this is correct.)If I pass, say, an
NSMutableDictionary
into a block, changes to the dictionary will be visible outside the block -- this is because you retained a reference to the dictionary and did not take a deep copy.In either case memory management is performed for you, that is either the scalar variable is copied or objects are retained.
Since you can't change the contents of an
NSDictionary
after it has been initialised, you'll probably find that blocks automatically do The Right Thing for you.To the second point, the memory management is pretty much automatic unless you need to work on a copy of a mutable object.
在进行 iOS BLE 开发时需要注意一些限制和要求。
第一个涉及 iOS 模拟器。有趣的是,曾经,iOS 模拟器确实支持蓝牙开发(WWDC 2012 蓝牙 101 视频提到了这一功能),但在 WWDC 2013 上,苹果宣布模拟器将不再支持蓝牙。
从表面上看,这似乎很不幸。然而,使用 BLE 在设备上进行开发可以获得更好、更准确的体验。此外,由于 iPhone 4s(2011 年)之后生产的所有 iPhone 都具有蓝牙 4.0(以及扩展后的 BLE),因此大多数 iOS 开发人员已经拥有支持它的设备。我们几乎不再需要问这个问题,但如果您正在为其开发应用程序或 BLE 设备,但如果您确实拥有一组受限的设备类型,那么最好知道谁可以使用它。
在进行核心蓝牙开发时要记住的另一个重要要求是,Apple 将与 BLE 设备交互的大部分责任交给了应用程序开发人员。在管理蓝牙方面,iOS 本身管理和维护的内容很少。操作系统中管理的一件事是“设置”>“设置”中显示的连接。蓝牙应用程序。
There are some limitations and requirements to be aware of when doing BLE development for iOS.
The first involves the iOS Simulator. Interestingly, at one time, the iOS Simulator did support Bluetooth development (the WWDC 2012 Bluetooth 101 video makes a reference to this functionality), but at WWDC 2013, Apple announced that Bluetooth support would no longer be in the Simulator.
On the surface this appears unfortunate. However, developing on a device is a better and more accurate experience with BLE. Additionally, since all iPhones made after the iPhone 4s (2011) have Bluetooth 4.0 — and by extension, BLE — most iOS developers already have devices that support it. We almost don't even need to ask the question anymore, but it's good to know who can use it in case you do have a constrained set of device types for which you're developing apps or BLE devices.
Another important requirement to remember when doing Core Bluetooth development is that Apple places the majority of the responsibility of the interactions with BLE devices on the app developer. Very little is managed and maintained by iOS itself with respect to managing Bluetooth. One thing that's managed in the OS is the connection that appears in the Settings > Bluetooth app.