可可:无法点击取消按钮,UI挂起且无响应,如何处理



请让我详细说明我在做什么和正在发生的事情。我是可可和objective-c的新手。请宽容一点。谢谢。

我在做什么

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

POC:http://1drv.ms/23J0WJQ

问题

当单击"取消"按钮时,UI 会动手并变得无响应。

编码部分从这里开始:

图书馆

/// 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

库"搜索文件.h"文件

@protocol SearchFileDelegate <NSObject>
- (void)completionWithSearchList:(NSArray*)serachList;
@end
@interface SearchFile : NSOperation
@property(nonatomic, weak)id<SearchFileDelegate> delegate;
-(id)initWithFileName:(NSString*)fileName;
-(void)startSearch;
-(void)stopSearch;
@end

"搜索文件.m"文件

#import "SearchFile.h"
@interface SearchFile ()
@property(nonatomic, strong)NSMutableArray* searchList;
@property(nonatomic, strong)NSString* fileName;
@end
@implementation SearchFile
-(id)initWithFileName:(NSString*)fileName
{
  self = [super init];
  self.fileName = fileName;
  return self;
}
- (void)main
{
  // add logic to search file.
  for (<#initialization#>; <#condition#>; <#increment#>) {
    // check for operation being canceled
      if([self isCancelled])
      {
        break;
      }
  }
    /
  // finally if search is complete or canceled
  [self.delegate completionWithSearchList:self.searchList];
}
-(void)startSearch
{
  [self start];
}
-(void)stopSearch
{
  [self cancel];
}

NSOperation界面将处理您的所有要求。调用接口需要设置SearchFile的委托属性并实现 - -(void)completionWithSearchList:(NSArray*)serachList ,调用

-(void)startSearch;
-(void)stopSearch

需要时.您也可以根据需要继续进行修改,以处理任何错误条件

最新更新