2016-02-03 5 views
3

Пожалуйста, позвольте мне уточнить, что я делаю и что происходит. Я новичок в какао и объектив-с. Пожалуйста, будьте немного снисходительны. Спасибо.COCOA: Невозможно нажать кнопку ОТМЕНА, пользовательский интерфейс зависает и не отвечает, как с этим бороться?

Что я делаю

Я создаю утилиту поиска файлов, где пользователь может ввести ключевое слово и приложение какао будет искать целые системные файлы и, если ключевое слово присутствует в файле пути тогда это шоу это пользователю.

ДОУ: http://1drv.ms/23J0WJQ

Проблема

При нажатии на кнопку CANCEL интерфейс получает руку и перестает отвечать на запросы.

Coding часть начинается здесь:

библиотека

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

} 

вызова функции библиотеки из пользовательского интерфейса

-(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]; 

    }); 

} 
+0

по мне вы сделали комплексную реализацию немного. Если вы в порядке, я могу предложить вам переработанную структуру, в которой вы можете пойти дальше. Просто дайте мне знать –

+0

Несомненно, большое спасибо @ aman.sood 'killerof99kings' - мой идентификатор skype в случае необходимости –

ответ

2

Вы должны действительно использовать NSOperationQueue для обработки исполнения NSOperation. Когда вы вызываете -start на операцию самостоятельно, операция выполняется в потоке, в котором вызывается -start, а именно основной поток. Это, вероятно, не то, что вы хотите, так как, выполняя всю работу над основным потоком, попытки обновления пользовательского интерфейса не смогут пройти до завершения операции. Чтобы исправить эту проблему, просто используйте NSOperationQueue.

Вы создаете NSOperationQueue (queue) в методе init. Тогда ваш метод 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, когда у вас есть большое количество результатов поиска. Что происходит в вашем текущем коде, так это то, что фоновый поток быстро находит результаты и пытается вызвать основной поток для обновления с каждым результатом, который главный поток получает из-за работы, поэтому становится невосприимчивым. Чтобы облегчить это, вам нужно сделать намного меньше вызовов в основной поток для обновления пользовательского интерфейса. Хотя существует множество способов сделать это, один из способов состоит в том, чтобы просто иметь фоновый поток, сохраняя свои результаты за 0,5 секунды, затем вызывать основной поток и передавать эти результаты. Затем он повторяет это каждые 0,5 секунды, пока он не будет закончен. Хотя это не идеально, оно должно улучшить реакцию.

Кроме того, хотя следующие изменения могут не понадобиться, для меня они кажутся более четким дизайном. Если вы хотите связаться с объектом NSOperation, который выполняется в фоновом потоке, чтобы сделать что-то вроде обновления пользовательского интерфейса, который должен выполняться в основном потоке, сам объект операции беспокоится о том, что вы вызываете селектор обновления на главной нить. Таким образом, удалить 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

+0

NSGod благодарит много за столько усилий. Его работа прекрасна, но есть улов. Если результаты достигают 16000 (попробуйте 'a' как ключевое слово поиска), я все равно не могу нажать отменить. может быть из-за того, что основной поток слишком загружен при перезагрузке таблицы. Можно ли перезагрузить таблицу из фонового потока? Спасибо тонну снова NSGod и @ aman.sood, я не так много обошел от кого-либо –

+1

Обновленный пример, чтобы помочь решить эту проблему ... – NSGod

1

библиотека файл "SearchFile.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 

файл "SearchFile.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 

при необходимости. Вы можете продолжить и изменить в соответствии с вашим требованием для обработки любых условий ошибки, а также

+0

Пользовательский интерфейс все еще висит, и я не могу отменить проверку. Я создал POC, пожалуйста, посмотрите: http://1drv.ms/23J0WJQ –

+0

Извините за отложенный ответ, было не очень хорошо. Его работа прекрасна на моем конце :) –

+0

прежде всего; да благословит вас благое благословение и скоро поправится. И проблема заключалась в том, что основной поток был слишком загружен при обновлении NSTable непрерывно. Проблема разрешается с помощью таймера для обновления NSTable с частотой. –

Смежные вопросы