从后台线程执行时关闭 WithCompletionHandler 有奇怪的结果

Posted

技术标签:

【中文标题】从后台线程执行时关闭 WithCompletionHandler 有奇怪的结果【英文标题】:closeWithCompletionHandler has odd results when executed from background thread 【发布时间】:2016-08-22 20:23:04 【问题描述】:

我在 ios 中处理一些批处理文档时遇到了问题,希望在这里能得到一些帮助。我尝试实现的过程使用 iCloud 驱动器上的输入目录,将所有文档拉到那里,并为每个文件添加一条记录到 iCloud 数据库中。现在,“添加 iCloud”代码不在此处,但如果我传递正确的指针,这很容易做到。我想给用户一个进度条,这样他们就可以检查处理情况并了解它的进展情况。这是我经常在 macOS 中使用的一种技术,只是假设它在 iOS 中可以正常工作,但我遇到了困难。

现在奇怪的是,如果我删除在后台线程上运行循环的代码并在主线程上运行加载程序类,则该过程或多或少可以工作。进度条当然不起作用(它永远不会让主线程返回),但文档打开代码被调用了正确的次数,并产生了进程来打开文档和所有这些。当我按原样运行它时,我得到一个异常终止,这似乎表明分配或解除分配过程已损坏。实际上并没有崩溃,只有不断循环的中断。

基本流程如下:

用户从屏幕上选择加载选项和目标目录,然后按“开始”。作为 segue 的一部分,加载器进程被实例化并等待一段时间开始(有更好的方法可以做到这一点,但他们可以等到我构建了基本进程)

用户被转到另一个显示进度条的屏幕。这是它的代码:

//
//  LoadPopover.m
//  TestFindIt
//
//  Created by Joe Ruth on 7/16/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.  
//

#import "LoadPopover.h"

@interface LoadPopover ()
@end

@implementation LoadPopover;

@synthesize localSecondViewController, localLoadCloudDBClass, loadProgressBar,     screenReloadDirectory;
@synthesize loadedDocuments;

- (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view.


- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.


- (IBAction)pressedDoneButton:(id)sender 

    [self dismissViewControllerAnimated:YES completion:nil];



-(void)viewDidAppear:(BOOL)animated 

    [localLoadCloudDBClass addObserver:self
                             forKeyPath:@"iterationcounter"
                                options:NSKeyValueObservingOptionNew
                                context:NULL];

    [localLoadCloudDBClass addObserver:self
                             forKeyPath:@"totalcounter"
                                options:NSKeyValueObservingOptionNew
                                context:NULL];

    loadProgressBar.progress = 0.00;

    self.loadedDocuments = [[NSMutableArray alloc] init];
    localLoadCloudDBClass.loadedDocuments = self.loadedDocuments;

    [localLoadCloudDBClass LoadCloudDBClassRun:self];




- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context   
    

        [[NSOperationQueue mainQueue] addOperationWithBlock:^


        int iterationcounter;
        int totalcounter;

        iterationcounter = (int) localLoadCloudDBClass.iterationcounter;
        totalcounter = (int) localLoadCloudDBClass.totalcounter;

        if (totalcounter != 0) 
            loadProgressBar.progress = (float) iterationcounter / (float) totalcounter;
        else 
            loadProgressBar.progress = 0.00;

    ];


@end

segue 屏幕为加载器类的一些计数器属性建立观察者。

加载器非常简单。现在它只是通过 iCloud 数据库的页面,并为数据库中的每条记录启动一个文档打开过程。在某些时候,我需要对文档打开过程有一点兴趣,但现在它是概念证明。

//
//  LoadCloudDBClass.m
//  TestFindIt
//
//  Created by Joe Ruth on 7/15/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.  
//

#import "LoadCloudDBClass.h"
#import "Item.h"
#import "SaveObject.h"
#import "SaveObjectUIDocument.h"

@implementation LoadCloudDBClass

@synthesize localFindItDataController, screenReloadDirectory, localLoadPopover, iterationcounter, totalcounter;
@synthesize loadedDocuments;

- (void) LoadCloudDBClassRun:(NSObject *) parameterTestFinditCaller 

    dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL);

    dispatch_async(backgroundQueue, ^

        NSError *error;
        unsigned long check_count;
        unsigned long total_count;
        NSURL *fileURL;
        int stopper;

        __block BOOL blockSuccess = NO;
        __block BOOL blockCalled = NO;

        [self setIterationcounter:(NSInteger) 0];
        [self setTotalcounter:(NSInteger) 0];

        NSFileManager *fm = [NSFileManager defaultManager];
        NSURL *rootURL = [fm URLForUbiquityContainerIdentifier:nil];
        NSURL *newFolderTemp = [rootURL URLByAppendingPathComponent:@"Documents" isDirectory:YES];
        NSURL *newFolder = [newFolderTemp URLByAppendingPathComponent:screenReloadDirectory isDirectory:YES];

        NSNumber *isDirectory;
        if (![newFolder getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) return;

        NSArray *theFiles =  [fm contentsOfDirectoryAtURL:newFolder
                               includingPropertiesForKeys:[NSArray arrayWithObject:NSURLNameKey]
                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                error:nil];

       [self setTotalcounter:(NSInteger) [theFiles count]];

        total_count = [theFiles count];
        check_count = 0;

        while ((check_count < total_count) && (total_count != 0)) 

            fileURL = [theFiles objectAtIndex:check_count];

            SaveObjectUIDocument *tempdoc = [[SaveObjectUIDocument alloc] initWithFileURL:fileURL];
            //tempdoc.loadedDocuments = loadedDocuments;

            long set_iterationcounter = iterationcounter + 1;
            [self setIterationcounter:(NSInteger) set_iterationcounter];

            [tempdoc openWithCompletionHandler:^(BOOL success) 
                 NSLog (@" try Open");
                 if (success) 
                    blockSuccess = success;
                    blockCalled = YES;
                    //[self.loadedDocuments addObject:tempdoc];
                    NSLog(@"Opened");
                else
                    NSLog(@"Not Opened");

            ];

            blockCalled = NO;

            NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
            while (!blockCalled && [loopUntil timeIntervalSinceNow] > 0)
            
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                         beforeDate:loopUntil];
            

            check_count++;

        

        stopper=5;

    );




@end

文档编码如下:

这是.h文件

//
//  SaveObject.h
//  TestFindIt
//
//  Created by Joe Ruth on 1/10/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface SaveObject : NSObject <NSCoding> 

    NSData *_sxmlData;



- (id)initWithData:(NSData *)in_xmlData;

@property (nonatomic, copy) NSData *sxmlData;

@end

这是随附的 .m 文件

//
//  SaveObject.m
//  TestFindIt
//
//  Created by Joe Ruth on 1/10/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.
//

#import "SaveObject.h"

@implementation SaveObject

@synthesize sxmlData = _sxmlData;

- (id)initWithData:(NSData *)in_xmlData 
    if ((self = [super init])) 
        self.sxmlData = in_xmlData;


   
    return self;


- (id)init 
    return [self initWithData:nil];


#pragma mark NSCoding

#define kVersionKey @"Version"
#define kDataKey @"Data"
#define kSaveObjectXMLData @"SaveObjectXMLData"

//- (void)encodeWithCoder:(NSCoder *)encoder 
//    [encoder encodeInt:1 forKey:kVersionKey];
//    [encoder encodeObject:self.sxmlData forKey:kDataKey];
//

//- (id)initWithCoder:(NSCoder *)decoder 
//    [decoder decodeIntForKey:kVersionKey];
//    NSData * outxmlData = [decoder decodeObjectForKey:kDataKey];
//    NSLog(@">>>>>>>>>>>>>>>>>>> %@",outxmlData);
//    return [self initWithData:outxmlData];
//

#pragma mark -

#pragma mark NSCoding Protocol

- (void)encodeWithCoder:(NSCoder *)coder 
    [coder encodeObject:self.sxmlData forKey:kSaveObjectXMLData];


- (id)initWithCoder:(NSCoder *)coder  
    self = [super init];

    if (self != nil) 


       self.sxmlData = [coder decodeObjectForKey:kSaveObjectXMLData];
    

    return self;


@end

这里是高级文档编码.h文件

//
//  SaveObjectUIDocument.h
//  TestFindIt
//
//  Created by Joe Ruth on 1/10/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.
//

#import <UIKit/UIKit.h>

@class SaveObject;


@interface SaveObjectUIDocument : UIDocument 

    SaveObject *_SaveObject;



@property (strong, nonatomic) SaveObject *SaveObject;

@结束

以及随附的 .m 文件。稍后 iCloud 添加代码会放在这里,我需要确保正确传递打开的 MOC 对象地址。

//
//  Created by Joe Ruth on 1/10/16.
//  Copyright © 2016 Joe Ruth. All rights reserved.
//

#import "SaveObjectUIDocument.h"
#import "SaveObject.h"

#define kArchiveKey @"SaveObjectXMLData"

@interface SaveObjectUIDocument ()
@property (nonatomic, strong) NSData * data;
@end

@implementation SaveObjectUIDocument;

@synthesize SaveObject = _SaveObject;

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 
    if ([contents length] > 0) 
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:contents];
        self.SaveObject = [unarchiver decodeObjectForKey:kArchiveKey];
        [unarchiver finishDecoding];
    else 
        self.SaveObject = [[SaveObject alloc] initWithData:nil];
    return YES;



- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:self.SaveObject forKey:kArchiveKey];
    [archiver finishEncoding];

    return data;



- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted 
    NSLog(@"Error: %@ userInfo=%@", error.localizedDescription, error.userInfo);
    [super handleError:error userInteractionPermitted:userInteractionPermitted];



@end

【问题讨论】:

对此有一个自我回答。强制 openWithCompletionHandler: 进程到主线程并且它工作。去图吧。 【参考方案1】:

对此有一个自我回答。强制 openWithCompletionHandler: 进程到主线程并且它工作。去搞清楚。

【讨论】:

以上是关于从后台线程执行时关闭 WithCompletionHandler 有奇怪的结果的主要内容,如果未能解决你的问题,请参考以下文章

c#线程之前台线程后台线程及使用

后台线程(daemon)

从后台线程更新 UI 是一种不好的做法,为啥? [关闭]

Discord.py 从后台线程关闭 Bot

浏览器页面关闭后台线程会中断吗

在后台线程中删除文件