草庐IT

ios - CoreData 保存在 iPad Mini 上的 GCD 后台队列挂起

coder 2024-01-20 原文

我有一个单例类,它使用 GCD(Grand Central Dispatch)队列在后台将 JSON 对象保存到 CoreData 数据库。这在大多数情况下都能完美运行,但在 iPad 2 和 iPad Mini 设备上,我遇到了一些进程卡住问题。

我的设置非常简单。我有一个设置为串行运行的后台调度队列 (backgroundQueue),并且我有一个单独的 NSManagedObjectContext 实例用于后台队列。当我想将某些内容保存到数据库时,我调用开始保存过程的方法,在该方法中,我使用 dispatch_async 在后台线程上调用我的保存逻辑。

所有处理逻辑运行后,我保存后台 MOC,并使用 NSManagedObjectContextDidSaveNotification 将后台 MOC 的更改合并到主线程 MOC。后台线程等待它完成,一旦完成,它将运行队列中的下一个 block (如果有的话)。下面是我的代码。

dispatch_queue_t backgroundQueue;

- (id)init
{
    if (self = [super init]) 
    {
        // init the background queue
        backgroundQueue = dispatch_queue_create(VS_CORE_DATA_MANAGER_BACKGROUND_QUEUE_NAME, DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    [VS_Log logDebug:@"**** Queue Save JSON Objects for Class: %@", managedObjectClass];

    // process in the background queue
    dispatch_async(backgroundQueue, ^(void)
    {
        [VS_Log logDebug:@"**** Dispatch Save JSON Objects for Class: %@", managedObjectClass];

        // create a new process object and add it to the dictionary
        currentRequest = [[VS_CoreDataRequest alloc] init];

        // set the thead name
        NSThread *currentThread = [NSThread currentThread];
        [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

        // if there is not already a background context, then create one
        if (!_backgroundQueueManagedObjectContext)
        {
            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
            if (coordinator != nil)
            {
                // create the background queue
                _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
                [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
                [_backgroundQueueManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
                _backgroundQueueManagedObjectContext.undoManager = nil;

                // listen for the merge changes from context did save notification on the background queue
                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
            }
        }

        // save the JSON dictionary
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

        // save the objects so we can access them later to be re-fetched and returned on the main thread
        if (objects.count > 0)
            [currentRequest.objects addObjectsFromArray:objects];

        // save the object IDs and the completion block to global variables so we can access them after the save
        if (completion)
            currentRequest.completionBlock = completion;

        [VS_Log logDebug:@"**** Save MOC for Class: %@", managedObjectClass];

        // save all changes object context
        [self saveManagedObjectContext];
    });
}

- (void)mergeChangesFromBackground:(NSNotification *)notification
{
    [VS_Log logDebug:@"**** Merge Changes From Background"];

    // save the current request to a local var since we're about to be processing it on the main thread
    __block VS_CoreDataRequest *request = (VS_CoreDataRequest *)[currentRequest copy];
    __block NSNotification *bNotification = (NSNotification *)[notification copy];

    // clear out the request
    currentRequest = nil;

    dispatch_sync(dispatch_get_main_queue(), ^(void)
    {
        [VS_Log logDebug:@"**** Start Merge Changes On Main Thread"];

        // merge changes to the primary context, and wait for the action to complete on the main thread
        [_managedObjectContext mergeChangesFromContextDidSaveNotification:bNotification];

        NSMutableArray *objects = [[NSMutableArray alloc] init];

        // iterate through the updated objects and find them in the main thread MOC
        for (NSManagedObject *object in request.objects)
        {
            NSError *error;
            NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error];
            if (error)
                [self logError:error];

            if (obj)
                [objects addObject:obj];
        }

        // call the completion block
        if (request.completionBlock)
        {
            void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
            saveCompletionBlock(objects, nil);
        }

        [VS_Log logDebug:@"**** Complete Merge Changes On Main Thread"];

        // clear the request
        request = nil;
    });

    [VS_Log logDebug:@"**** Complete Merge Changes From Background"];
}

当问题发生时,一切似乎都运行良好,它进入了 mergeChangesFromBackground: 方法,但没有调用 dispatch_async()。

正如我上面提到的,代码在大多数情况下都能完美运行。只有当我在 iPad 2 或 iPad Mini 上测试并保存大型对象时,它才会出现运行问题。

有没有人知道为什么会这样?

谢谢

** 编辑 **

自从写这篇文章后,我了解了嵌套的 NSManagedObjectContexts。我修改了我的代码以使用它,它似乎运行良好。但是,我仍然有同样的问题。想法?另外,对嵌套 MOC 有什么意见吗?

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    // process in the background queue
    dispatch_async(backgroundQueue, ^(void)
    {
        // create a new process object and add it to the dictionary
        __block VS_CoreDataRequest *currentRequest = [[VS_CoreDataRequest alloc] init];

        // set the thead name
        NSThread *currentThread = [NSThread currentThread];
        [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

        // if there is not already a background context, then create one
        if (!_backgroundQueueManagedObjectContext)
        {
            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
            if (coordinator != nil)
            {
                // create the background queue
                _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
                [_backgroundQueueManagedObjectContext setParentContext:[self managedObjectContext]];
                [_backgroundQueueManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
                _backgroundQueueManagedObjectContext.undoManager = nil;
            }
        }

        // save the JSON dictionary starting at the upper most level of the key path
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

        // if no objects were processed, then return with an error
        if (!objects || objects.count == 0)
        {
            currentRequest = nil;

            // dispatch the completion block
            dispatch_async(dispatch_get_main_queue(), ^(void)
            {
                NSError *error = [self createErrorWithErrorCode:100 description:@"No objects were found for the object mapping"];
                [self logMessage:error.debugDescription];
                completion(nil, error);
            });
        }

        // save the objects so we can access them later to be re-fetched and returned on the main thread
        if (objects.count > 0)
            [currentRequest.objects addObjectsFromArray:objects];

        // save the object IDs and the completion block to global variables so we can access them after the save
        if (completion)
            currentRequest.completionBlock = completion;

        // save all changes object context
        NSError *error = nil;
        [_backgroundQueueManagedObjectContext save:&error];
        if (error)
            [VS_Log logError:error.localizedDescription];

        dispatch_sync(dispatch_get_main_queue(), ^(void)
        {
            NSMutableArray *objects = [[NSMutableArray alloc] init];

            // iterate through the updated objects and find them in the main thread MOC
            for (NSManagedObject *object in currentRequest.objects)
            {
                NSError *error;
                NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error];
                if (error)
                    [self logError:error];

                if (obj)
                    [objects addObject:obj];
            }

            // call the completion block
            if (currentRequest.completionBlock)
            {
                void (^saveCompletionBlock)(NSArray *, NSError *) = currentRequest.completionBlock;
                saveCompletionBlock(objects, nil);
            }
        });

        // clear out the request
        currentRequest = nil;
    });
}

** 编辑 **

大家好,

首先,我要感谢大家对此的意见,因为正是这些意见促使我找到了解决问题的方法。长话短说,我完全放弃了使用 GCD 和自定义后台队列,而是现在使用嵌套上下文和“performBlock”方法在后台线程上执行所有保存。这很好用,我解决了挂断问题。

但是,我现在有一个新的错误。每当我第一次运行我的应用程序时,每当我尝试保存具有子对象关系的对象时,我都会收到以下异常。

-_referenceData64 仅为抽象类定义。定义 -[NSTemporaryObjectID_default _referenceData64]!

下面是我的新代码。

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    [_backgroundManagedObjectContext performBlock:^(void)
    {
        // create a new process object and add it to the dictionary
        VS_CoreDataRequest *currentRequest = [[VS_CoreDataRequest alloc] init];
        currentRequest.managedObjectClass = managedObjectClass;

        // save the JSON dictionary starting at the upper most level of the key path
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundManagedObjectContext level:0];

        if (objects.count == 0)
        {
            currentRequest.error = [self createErrorWithErrorCode:100 description:@"No objects were found for the object mapping"];
            [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidFail:) withObject:currentRequest waitUntilDone:NO];
        }
        else
        {
            // save the objects so we can access them later to be re-fetched and returned on the main thread
            [currentRequest.objects addObjectsFromArray:objects];

            // save the object IDs and the completion block to global variables so we can access them after the save
            currentRequest.completionBlock = completion;

            [_backgroundManagedObjectContext lock];

            @try
            {
                [_backgroundManagedObjectContext processPendingChanges];
                [_backgroundManagedObjectContext save:nil];
            }
            @catch (NSException *exception)
            {
                currentRequest.error = [self createErrorWithErrorCode:100 description:exception.reason];
            }

            [_backgroundManagedObjectContext unlock];

            // complete the process on the main thread
            if (currentRequest.error)
                [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidFail:) withObject:currentRequest waitUntilDone:NO];
            else
                [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidSucceed:) withObject:currentRequest waitUntilDone:NO];

            // clear out the request
            currentRequest = nil;
        }
    }];
}

- (void)backgroundSaveProcessDidFail:(VS_CoreDataRequest *)request
{
    if (request.error)
        [self logError:request.error];

    if (request.completionBlock)
    {
        void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
        saveCompletionBlock(nil, request.error);
    }
}

- (void)backgroundSaveProcessDidSucceed:(VS_CoreDataRequest *)request
{
    // get objects from main thread
    NSArray *objects = nil;
    if (request.objects.count > 0)
    {
        NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:[NSString stringWithFormat:@"%@", request.managedObjectClass]];
        fetchReq.predicate = [NSPredicate predicateWithFormat:@"self IN %@", request.objects];
        objects = [self executeFetchRequest:fetchReq];
    }

    // call the completion block
    if (request.completionBlock)
    {
        void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
        saveCompletionBlock(objects, nil);
    }
}

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        // create the MOC for the backgroumd thread
        _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_backgroundManagedObjectContext setPersistentStoreCoordinator:coordinator];
        [_backgroundManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
        _backgroundManagedObjectContext.undoManager = nil;

        // create the MOC for the main thread
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setParentContext:_backgroundManagedObjectContext];
        [_managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    }

    return _managedObjectContext;
}

你们中有人知道为什么会发生这种崩溃吗?

最佳答案

您可能需要考虑这种上下文架构(未经测试的代码):
mainManagedObjectContext 将以相同的方式初始化,但使用 NSMainQueueConcurrencyType 并且没有观察者(顺便说一句,记得删除你的观察者)

- (void) mergeChangesFromBackground:(NSNotification*)notification
{
    __block __weak NSManagedObjectContext* context = self.managedObjectContext;
    [context performBlockAndWait:^{
        [context mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (NSManagedObjectContext*) bgContext
{
    if (_bgContext) {
        return _bgContext;
    }

    NSPersistentStoreCoordinator* coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_bgContext setPersistentStoreCoordinator:coordinator];
    [_bgContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    [_bgContext setUndoManager:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(mergeChangesFromBackground:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:_bgContext];
    return _bgContext;
}

- (void) doSomethingOnBGContextWithoutBlocking:(void(^)(NSManagedObjectContext*))contextBlock
{
    __block __weak NSManagedObjectContext* context = [self bgContext];
    [context performBlock:^{
        NSThread *currentThread = [NSThread currentThread];
        NSString* prevName = [currentThread name];
        [currentThread setName:@"BGContextThread"];

        contextBlock(context);

        [context reset];
        [currentThread setName:prevName];
    }];
}

关于ios - CoreData 保存在 iPad Mini 上的 GCD 后台队列挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15820629/

有关ios - CoreData 保存在 iPad Mini 上的 GCD 后台队列挂起的更多相关文章

  1. ruby-on-rails - date_field_tag,如何设置默认日期? [ rails 上的 ruby ] - 2

    我想设置一个默认日期,例如实际日期,我该如何设置?还有如何在组合框中设置默认值顺便问一下,date_field_tag和date_field之间有什么区别? 最佳答案 试试这个:将默认日期作为第二个参数传递。youcorrectlysetthedefaultvalueofcomboboxasshowninyourquestion. 关于ruby-on-rails-date_field_tag,如何设置默认日期?[rails上的ruby],我们在StackOverflow上找到一个类似的问

  2. ruby-on-rails - openshift 上的 rails 控制台 - 2

    我将我的Rails应用程序部署到OpenShift,它运行良好,但我无法在生产服务器上运行“Rails控制台”。它给了我这个错误。我该如何解决这个问题?我尝试更新ruby​​gems,但它也给出了权限被拒绝的错误,我也无法做到。railsc错误:Warning:You'reusingRubygems1.8.24withSpring.UpgradetoatleastRubygems2.1.0andrun`gempristine--all`forbetterstartupperformance./opt/rh/ruby193/root/usr/share/rubygems/rubygems

  3. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  4. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  5. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  6. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  7. ruby-on-rails - rspec - 如何检查方法是否存在? - 2

    我的模型有defself.empty_building//stuffend我怎样才能对这个现有的进行rspec?,已经尝试过:describe"empty_building"dosubject{Building.new}it{shouldrespond_to:empty_building}endbutgetting:Failure/Error:it{shouldrespond_to:empty_building}expected#torespondto:empty_building 最佳答案 你有一个类方法self.empty_bu

  8. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  9. ruby - 如何在 ruby​​ 中运行后台线程? - 2

    我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

  10. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

随机推荐