objective-c - COCOA: 无法点击取消按钮,UI 挂起且无响应,如何处理?

标签 objective-c cocoa

请让我详细说明我在做什么以及正在发生什么。我是 cocoa 和 Objective-C 的新手。请宽容一点。谢谢。

我在做什么

我正在创建一个文件搜索实用程序,用户将在其中输入关键字,cocoa 应用程序将搜索整个系统文件,如果关键字存在于文件路径中,则会将其显示给用户。

概念验证:http://1drv.ms/23J0WJQ

问题

当点击“取消”按钮时,用户界面会变得无响应。

编码部分从这里开始:

图书馆

/// this funciton will be called with
/// keyword : the word that should be present in the file path
/// selector : a function tha will be called when ever the function will find a matching file
/// selector: object of the class that has the funciton (selector)
-(NSMutableArray *)searchWholeSystem:(NSString *)keyword
                            selector:(SEL)aselector
                              target:(id)atarget
{
    /// _cancelSearch is a property which can be set by the user of the class
    /// when the scann will be started it will be set to as default : NO
    _cancelSearch = NO;

    /// list of all the files that are matched with the keyword
    NSMutableArray* fileList = [[NSMutableArray alloc]init];

    ///autoreleasepool to release the unused memory
    @autoreleasepool {

        ///getMainDirectories is local function that will get the user direcotires or the target direcoties where the search will be done
        NSMutableArray* directories = [self getMainDirectories];

        ///one by one search all the direcoties for the matching files
        for (NSString* dir in directories) {

            NSMutableArray* resultList = [self getAllFiles:dir tag:keyword target:atarget selector:aselector];

            for (int i = 0; i<[resultList count]; i++) {

                ///if cancel then return the as yet result array
                if (_cancelSearch)
                    return fileList;

                NSString* path = [resultList objectAtIndex:i];

                GenericResultModel* sfile = [[GenericResultModel alloc]init];

                sfile.Column1 = [path lastPathComponent];
                sfile.Column2 = path;


                [fileList addObject:sfile];

            }
        }



        return fileList;
    }
}

///getAllFiles will be having
///sPath : source path where the search will be performed
///tag: tag is the keyword that need to found in the path
/// selector : a function tha will be called when ever the function will find a matching file
/// selector: object of the class that has the funciton (selector)
-(NSMutableArray*)getAllFiles:(NSString*)sPath tag:(NSString*)_tag target:(id)atarget selector:(SEL)aselector
{

    //  fileList is the result that will contain all the file names that has the _tag
    NSMutableArray* fileList = [[NSMutableArray alloc]init];


    @autoreleasepool {

        //   _tag is the keyword that should be present in the file name
        _tag = [_tag lowercaseString];



        ///getting all contents of the source path
        NSArray *contentOfDirectory=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:sPath error:NULL];

        for(int i=0; i < [contentOfDirectory count]; i++)
        {
            if (_cancelSearch)
                return fileList;

            NSString *filePathInDirectory = [contentOfDirectory objectAtIndex:i];

            NSString* fullPath = [NSString stringWithFormat:@"%@/%@", sPath, filePathInDirectory];

            if ([FMH isdirOrApp:fullPath])
            {
                if ([[fullPath lowercaseString] rangeOfString:_tag].location != NSNotFound && [fileList indexOfObject:fullPath] == NSNotFound)
                {
                    [fileList addObject:fullPath];
                    [atarget performSelector:aselector withObject:fullPath];
                }

                if (_cancelSearch)
                    return fileList;


                NSMutableArray* files =  [self getAllFiles:fullPath tag:_tag target:atarget selector:aselector];

                for (NSString* f in files)
                {
                    if ([[f lowercaseString] rangeOfString:_tag].location != NSNotFound && [fileList indexOfObject:fullPath] == NSNotFound)
                    {
                        [fileList addObject:f];
                        [atarget performSelector:aselector withObject:f];
                    }

                    if (_cancelSearch)
                        return fileList;
                }
            }
            else
            {
                NSString* fileN = [fullPath lastPathComponent];
                if ([[fileN lowercaseString] rangeOfString:_tag].location != NSNotFound && [fileList indexOfObject:fullPath] == NSNotFound)
                {
                    [fileList addObject:fullPath];
                    [atarget performSelector:aselector withObject:fullPath];
                }

                if (_cancelSearch)
                    return fileList;
            }
        }

    }

    return fileList;

}

从 UI 调用库函数

-(void)cancelClick:(id)sender
{
    macSearch.cancelSearch = YES; ///setting the cancel to yes so that the library function may come to know that the search is canceled and now stop searching return what ever is searched
}

-(void)wholeSystemScan:(id)sender
{

    if([[searchKeyword stringValue] length] < 1)
    {
        [[[MessageBoxHelper alloc] init] ShowMessageBox:@"Please enter a keyword to search." Title:@"Information" IsAlert:YES];

        return;
    }

    [self ScanStartDisableControls];

    NSString* keyw = [searchKeyword stringValue];


    dispatch_queue_t backgroundQueue = dispatch_queue_create("com.techheal.fileSearch", 0);

    dispatch_async(backgroundQueue, ^{

        [macSearch searchWholeSystem:keyw selector:@selector(refreshSelector:)  target:self];

        [self ScanCompletedEnableControls];

    });

}

最佳答案

您真的应该使用 NSOperationQueue 来处理 NSOperation 的执行。当您自己对操作调用 -start 时,操作是在调用 -start 的线程(即主线程)上进行的。这可能不是您想要的,因为通过在主线程上完成所有工作,只有在操作完成后才能尝试更新 UI。要解决该问题,只需使用 NSOperationQueue

您在 init 方法中创建了 NSOperationQueue (queue)。然后你的 start: 方法看起来像这样:

-(void)start:(id)sender
{
    [DataList removeAllObjects];
    [tableView reloadData];

    NSString* keyw = [searchTextBox stringValue];

    searcher = [[MacFileSearchReVamp alloc] initWithFileName:keyw selector:@selector(refreshSelector:)  target:self];

    searcher.delegate = self;

    [queue addOperation:searcher];
//    [searcher startSearch];
}

你可以在这里看到,我们没有直接调用 startSearch,而是简单地将 searcher 对象添加到 queue 中,它负责执行在后台线程上操作。

您的stop: 方法变为:

- (IBAction)stop:(id)sender
{
    [queue cancelAllOperations];
//    [searcher stopSearch];
}

然后,解决当您有大量搜索结果时卡住 UI 的性能问题。您当前代码中发生的事情是后台线程如此快速地找到结果并尝试调用主线程来更新主线程因工作重载而变得无响应的每个结果。为了缓解这种情况,您需要减少对主线程的调用来更新 UI。虽然有很多方法可以做到这一点,但一种方法是简单地让后台线程将其结果存储 0.5 秒,然后调用主线程并传递这些结果。然后它每 0.5 秒重复一次,直到完成。虽然不完美,但应该可以提高响应能力。

此外,虽然可能不需要进行以下更改,但对我来说它们似乎是一种更清晰的设计。当你想从在后台线程中运行的 NSOperation 对象进行通信时,要执行一些应该在主线程上完成的更新 UI 之类的操作,让操作对象本身担心确保在主线程上调用刷新选择器。因此,删除 dispatch_async 调用,并将刷新选择器更改为接受路径数组的方法:

-(void)refreshSelectorWithPaths:(NSArray *)resultPaths
{
    for (NSString *resultPath in resultPaths) {
        GenericResultModel* sfile = [[GenericResultModel alloc]init];
        sfile.Column1 = [resultPath lastPathComponent];
        sfile.Column2 = resultPath;
        [DataList addObject:sfile];
    }
    [tableView reloadData];
}

您应该删除检查 DataList 是否已包含该条目的代码,因为随着结果数量的增加,这对性能来说将是灾难性的,并且考虑到更新的 NSOperation 代码,这将是不必要的。

#define MD_PROGRESS_UPDATE_TIME_INTERVAL 0.5

-(NSMutableArray*)getAllFiles:(NSString*)sPath tag:(NSString*)_tag target:(id)atarget selector:(SEL)aselector {
    //  fileList is the result that will contain all the file names that has the _tag
    NSMutableArray* fileList = [[NSMutableArray alloc]init];
    NSMutableArray *fullPaths = [NSMutableArray array];

    @autoreleasepool {
        //   _tag is the keyword that should be present in the file name
        _tag = [_tag lowercaseString];

 /* subpathsOfDirectoryAtPath:error: gets all subpaths recursively 
  eliminating need for calling this method recursively, and eliminates
   duplicate results */
        NSArray *subpaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:sPath error:NULL];

        NSUInteger subpathsCount = subpaths.count;
        NSDate *progressDate = [NSDate date];

        for (NSUInteger i = 0; i < subpathsCount; i++) {
            if ([self isCancelled]) break;

            NSString *subpath = [subpaths objectAtIndex:i];
            if ([[[subpath lastPathComponent] lowercaseString] rangeOfString:_tag].location != NSNotFound) {
                NSString *fullPath = [sPath stringByAppendingPathComponent:subpath];
                [fileList addObject:fullPath];
                [fullPaths addObject:fullPath];
                if (ABS([progressDate timeIntervalSinceNow]) > MD_PROGRESS_UPDATE_TIME_INTERVAL) {
                    [atarget performSelectorOnMainThread:aselector withObject:fullPaths waitUntilDone:NO];
                    [fullPaths removeAllObjects];
                    progressDate = [NSDate date];
                }
            }
        }
        if (fullPaths.count) [atarget performSelectorOnMainThread:aselector withObject:fullPaths waitUntilDone:NO];
    }
    return fileList;
}

以上代码使用 fullPaths 来存储每 0.5 秒间隔的完整路径。结果找到,路径被添加到fullPaths,然后我们检查距离上次我们告诉主线程刷新是否已经过了0.5秒。如果有,我们调用刷新选择器,然后从 fullPaths 数组中删除这些条目。

这是您的概念验证的改进版本(更新后性能增强):

http://www.markdouma.com/developer/mySearch.zip

关于objective-c - COCOA: 无法点击取消按钮,UI 挂起且无响应,如何处理?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35174831/

相关文章:

objective-c - 闪屏问题,Xcode/iOS 6/Phonegap

swift - 绑定(bind)到 NSToolbarItem 的 Cocoa 连接是否会阻止取消初始化?

objective-c - Xcode调试问题

使用 xCode 3.2.4 进行 iPhone 开发

objective-c - NSWindow 类似 Quicksilver

Objective-C:@[]和[[NSArray alloc] initWithCapacity:0]有什么区别

ios - 检查 tabBar 是否为 nil

ios - 在 iOS 的 Tableview 单元格上自动播放视频

objective-c - NSTableView 上的圆角

cocoa - 通过标签号获取 NSTextField ?