在 ipad 应用程序中处理许多 ASIHttpRequest 和核心数据操作的最佳方法

发布于 2024-11-08 22:44:14 字数 561 浏览 0 评论 0原文

我当前为 iPad 开发的应用程序涉及处理许多网络请求并将处理后的结果保存在核心数据中。

场景如下 - 应用程序需要下载我在网格视图中显示的对象的图像,该视图总共可以显示 30 个对象。每个对象最多可以包含 15 张 png 图像(也在网格中)。由于服务器的实现方式(意味着我没有实现它并且无法轻松更改它),每个图像必须单独请求,因此我需要为每个对象发出最多 15 个请求,而不是下载所有图像的 1 个请求15 张图片。

对于每个对象,我当前使用 ASINetworkQueue 对 15 个图像请求进行排队。队列完成后,我创建该对象的缩略图快照及其要在网格中显示的图像,然后将所有 png 文件保留到核心数据。

目前,除了由 ASI 异步处理的网络请求之外,我正在主线程上运行所有内容,但由于请求太多,应用程序 UI 基本上处于锁定状态,直到所有请求都得到处理并将结果保存到核心数据中。

我遇到的一种解决方案是在单独的线程中执行核心数据操作和写入或使用中央调度。另一种方法是仅下载可见对象的图像,并在用户向下滚动时下载其余部分。

我正在寻找其他建议来帮助保持主用户界面的响应能力,或者更好的方法来构建网络和核心数据操作。谢谢。

The current app I'm developing for the iPad involves handling many network requests and persisting the processed results in core data.

The scenario is follows - the application needs to download images for objects I'm displaying in a grid view, which can show a total of 30 objects. Each object can consist of up to 15 png images (also in a grid). Due to the way the server is implemented (meaning I didn't implement it and can't change it easily), each image must be requested separately, so I need to make up to 15 requests per object versus just 1 request to download all 15 images.

For each object, I'm currently using an ASINetworkQueue to queue up the 15 image requests. Once the queue finishes, I create a thumbnail snapshot of the object with its images to display in the grid, then persist all the png files to core data.

I'm currently running everything on the main thread except the network requests which are handled by ASI asynchronously, but since there are so many requests, the app UI is essentially locked until all the requests are processed and results saved to core data.

One solution I came across was doing the core data operations and writes in a separate thread or using grand central dispatch. Another is to only download the images for the visible objects, and download the rest when the user scrolls down.

I'm looking for other suggestions to help keep the main ui responsive, or better ways to structure the network and core data operations. Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

胡渣熟男 2024-11-15 22:44:14

首先,避免在 Core Data 中存储大的 blob,保存缩略图就可以了(尽管您应该针对它优化模型),但是在 Documents 文件夹中重建后,您应该存储完整的图像。

您绝对应该使用队列,NSOperationQueue 或 ASI 网络队列。我在我的应用程序中做了类似的事情,它有多个依赖项。因此,对于 30 个对象中的每一个,当下载 15 个图像时,您需要调用一个块(或工作函数)。理想情况下,您希望在主线程之外完成这项工作。将所有这些要求放在一起,我想说您至少需要两个队列,一个用于网络请求,一个用于工作块,并且您应该使用 NSBlockOperations,这使整个事情变得更加容易。所以,代码会是这样的...

// Loop through the objects
for (NSArray *objectParts in objectsToDownload) {

    // Create our Object
    Object *obj = [Object insertIntoManagedObjectContext:self.moc];

    // This is the block which will do the post processing for the object
    NSBlockOperation *processBlock = [NSBlockOperation blockOperationWithBlock:^{

        // Do post processing here, be very careful with multi-threading CoreData
        // it's likely you'll need some class to dispatch you MOCs which have all
        // all the observers set up.

        // We're gonna assume that all the sub-images have been stored in a instance
        // variable:
        [obj performPostProcessing];

    }];

    // Given the list of 15 images which form each object
    for (NSURL *part in objectParts) {

        // Create the ASI request for this part
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:part];

        // Configure the request
        [request setDelegate:self];
        [request setDidFailSelector:@selector(partRequestDidFail:)];
        [request setDidFinishSelector:@selector(partRequestDidFinish:)];

        // Store the object in the UserInfo dictionary
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:obj, @"Object", nil];
        [request setUserInfo:userInfo];

        // Add it as a dependency
        [processBlock addDependency:request];

        // Add it to our network queue
        [networkQueue addOperation:request];
    }

    // Add the processBlock to our worker queue
    [workerQueue addOperation:processBlock];
}

然后您还需要编写委托方法,didFinish 看起来像这样...

- (void)partRequestDidFinish:(ASIHTTPRequest *)request {
    // Remember this is one the main thread, so any heavy lifting should be 
    // put inside a block operation, and queued, which will complicate the 
    // dependencies somewhat, but is possible.

    // Get the result data
    NSData *data = [request responseData];

    // Get the object that it belongs to from the user info dic
    Object *obj = [[request userInfo] objectForKey:@"Object"];

    // Keep track of the partial data in the object
    [obj storePartialDataForPostProcessing:data];
}

所有这些都将进入连接到服务器的类中,并且创建你的对象,所以它不是一个视图控制器或任何东西,只是一个常规的 NSObject 子类。它将需要有两个队列、一个托管对象上下文(并且很可能是一个返回另一个 MOC 供您在线程中使用的方法,如下所示:)

// Fends a MOC suitable for use in the NSBlockOperations
- (NSManagedObjectContext *)moc {
    // Get a blank managed object context
    NSManagedObjectContext *aContext = [[UIApplication sharedApplication] managedObjectContext;
[aContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(mergeChangesFromMOC:) name:NSManagedObjectContextDidSaveNotification object:aContext];
    return aContext;

- (void)mergeChangesFromMOC:(NSNotification *)aNotification {
    @try {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:[aNotification object]];      
    }
    @catch (NSException * e) {
        NSLog(@"Stopping on exception: %@", [e description]);
    }
    @finally {}
}

还需要以某种方式挂钩来监视进度,重新- 排队失败的下载、取消并在最后保存 MOC 重新排队失败的下载非常棘手。

因此,为了澄清,在您的委托方法中,您将下载的图像存储在 a 中。你的临时实例变量然后,当所有 15 个依赖项完成后,您就可以访问该实例变量并完成您的工作。

First of all, avoid storing large blobs in Core Data, saving the thumbnails is fine (although you should optimize your model for it), but you should store the full image once it's reconstructed in the Documents folder.

You should definitely use a queue, either NSOperationQueue or ASI network queue. I do something similar in my app which has multiple dependencies. So, for each of the 30 objects you need to have a block (or work function) get called when the 15 images have downloaded. You ideally want to do this work off the main thread. Put all these requirements together, and I'd say you need at least two queues, one for network requests and one for worker blocks, and you should use NSBlockOperations which makes the whole thing much easier. So, the code would be something like this...

// Loop through the objects
for (NSArray *objectParts in objectsToDownload) {

    // Create our Object
    Object *obj = [Object insertIntoManagedObjectContext:self.moc];

    // This is the block which will do the post processing for the object
    NSBlockOperation *processBlock = [NSBlockOperation blockOperationWithBlock:^{

        // Do post processing here, be very careful with multi-threading CoreData
        // it's likely you'll need some class to dispatch you MOCs which have all
        // all the observers set up.

        // We're gonna assume that all the sub-images have been stored in a instance
        // variable:
        [obj performPostProcessing];

    }];

    // Given the list of 15 images which form each object
    for (NSURL *part in objectParts) {

        // Create the ASI request for this part
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:part];

        // Configure the request
        [request setDelegate:self];
        [request setDidFailSelector:@selector(partRequestDidFail:)];
        [request setDidFinishSelector:@selector(partRequestDidFinish:)];

        // Store the object in the UserInfo dictionary
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:obj, @"Object", nil];
        [request setUserInfo:userInfo];

        // Add it as a dependency
        [processBlock addDependency:request];

        // Add it to our network queue
        [networkQueue addOperation:request];
    }

    // Add the processBlock to our worker queue
    [workerQueue addOperation:processBlock];
}

Then you'll also need to write the delegate methods, the didFinish one would look something like this...

- (void)partRequestDidFinish:(ASIHTTPRequest *)request {
    // Remember this is one the main thread, so any heavy lifting should be 
    // put inside a block operation, and queued, which will complicate the 
    // dependencies somewhat, but is possible.

    // Get the result data
    NSData *data = [request responseData];

    // Get the object that it belongs to from the user info dic
    Object *obj = [[request userInfo] objectForKey:@"Object"];

    // Keep track of the partial data in the object
    [obj storePartialDataForPostProcessing:data];
}

And all of that would go into your class which connects to your server and creates your objects, so it's not a view controller or anything, just a regular NSObject subclass. It will need to have two queues, a managed object context (and more than likely a method which returns another MOC for you to use in threads, something like this:

// Fends a MOC suitable for use in the NSBlockOperations
- (NSManagedObjectContext *)moc {
    // Get a blank managed object context
    NSManagedObjectContext *aContext = [[UIApplication sharedApplication] managedObjectContext;
[aContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(mergeChangesFromMOC:) name:NSManagedObjectContextDidSaveNotification object:aContext];
    return aContext;

}

- (void)mergeChangesFromMOC:(NSNotification *)aNotification {
    @try {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:[aNotification object]];      
    }
    @catch (NSException * e) {
        NSLog(@"Stopping on exception: %@", [e description]);
    }
    @finally {}
}

You'll also need to hook in some way of monitoring progress, re-queuing failed downloads, cancelling, and saving the MOC at the end. Re-queuing failed downloads is quite tricky. Anyway, hope that helps.

So, just to clarify, in your delegate method, you'd store the downloaded image in a temporary instance variable on your Object. Then when all 15 dependencies finish, you can access that instance variable and do your work.

恍梦境° 2024-11-15 22:44:14

首先,1 个队列应该足以满足所有图像请求。

您可能想要做的是保留对请求的引用,以便在不再需要该对象时可以取消它们。

对于锁定,图像需要考虑一些事项:

  1. 它们是压缩的,因此在成为 UIImage 之前需要先进行膨胀,这对 CPU 来说非常沉重。
  2. 如果您想写入文件系统,该进程就会被锁定。在另一个队列中执行以避免锁定。
  3. 将 Blob 存储到 CoreData、将文件路径作为字符串存储在 Core Data 中并使用队列从磁盘获取它从来都不是一个好主意。

只需使用 3 个不同的 NSOperationQueue 将使您的应用程序响应更快:
1 用于 ASIHTTPRequests(不要创建新的,使用 startAsynchronous 的默认值)
1 用于将图像写入磁盘
1 用于从磁盘获取图像

To kick things off, 1 Queue should be enough for all the image requests.

What you might want to do is keep references to the request so you can cancel them if the object is no longer needed.

For the locking, there's a few things to consider with images:

  1. They are compressed, so they need to be inflated before being UIImages, that is very heavy on the CPU.
  2. If you ever want to write to the filesystem, that process is locking. Do it in another Queue to avoid locking.
  3. It's never a good idea to store Blob into CoreData, store the file path as a string in Core Data and fetch it from disk using a queue

Just using 3 different NSOperationQueue will make your app much more responsive:
1 for ASIHTTPRequests (Don't create a new one, use the default with startAsynchronous)
1 for image writing to disk
1 for image fetching from disk

萌酱 2024-11-15 22:44:14

既然您将显示图像以供查看,为什么不使用 SDwebImage:

SDImageCache 管理异步下载队列,将下载器与图像缓存存储联系起来,维护内存缓存和可选的磁盘缓存。磁盘缓存写入操作是异步执行的,因此不会给 UI 增加不必要的延迟。

[imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]]

https://github.com/rs/SDWebImage

Since you will display image to view, why don't you SDwebImage:

SDImageCache manages an asynchronous download queue, ties the downloader with the image cache store, maintains a memory cache and an optional disk cache. Disk cache write operations are performed asynchronous so it doesn't add unnecessary latency to the UI.

[imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]]

https://github.com/rs/SDWebImage

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文