Привязка КВО приводит к зависанию программы? - PullRequest
1 голос
/ 24 декабря 2011

У меня возник странный вопрос, касающийся процесса, который, похоже, застревает в состоянии ожидания с случайными интервалами, и я надеюсь, что смогу получить некоторую помощь по этому поводу.

Когда пользователь запускаетВ приложении отображается окно, которое собирает различные данные и выбирает записи из списка файлов пользователя.Затем пользователь нажимает кнопку «Выполнить», и метод делегата приложения, связанный с кнопкой «Выполнить», создает экземпляр класса (TestBindingClass), в котором есть метод, который в конечном итоге будет выполняться в фоновом режиме.Затем он запускает второй файл XIB, который отображает второе окно, которое содержит пустой вид прокрутки.Как часть инициации второго окна в методе windowDIdLoad, код второго окна регистрируется как наблюдатель свойства TestBindingClass.Наконец, метод run в главном окне выполняет метод второго потока внутри TestBindingClass, который добавляет записи в NSMutableArray, а также запускает отслеживаемое свойство каждый раз, когда добавляется новая запись.Каждая запись в NSMutableArray представляет собой строку текста (строку), которая должна отображаться во втором виде прокрутки xib.

TestBindingClass - это (относительно) пустая оболочка класса, в которой будут содержаться обширные вычисления, которые будут генерировать сообщения о состоянии при достижении определенных контрольных точек.Эти сообщения контрольных точек будут отображаться в режиме прокрутки.

В основном, это работает нормально.На самом деле, иногда это работает отлично.В других случаях процесс, кажется, зависает и отображает только несколько строк состояния, прежде чем зависнуть в цикле ожидания.Обычно это точка, в которой вид прокрутки собирается расширяться за пределы размера окна, и полоса прокрутки активируется.Странно то, что если я добавляю точки отладки в метод add наблюдателя во втором коде XIB, он ВСЕГДА работает правильно.

Так много для описания ... давайте покажем некоторый код ....

Вот код для метода кнопки 'run' в главном окне

-(IBAction)runButtonPressed:(id)sender
{
 // do a bunch of stuff

 TestBindingClass* tempTestBindingClass = [[TestBindingClass alloc] init];

 RunResultWindowController = [[RunResultWindow alloc] initWithWindowNibName:@"RunResultWindow"];
RunResultWindowController.localTestBindingClass=tempTestBindingClass;
[RunResultWindowController showWindow:self];

[self.StatusDisplayOutput setStringValue:@"Run Complete"];

[tempTestBindingClass submitStatusStringSequence];

}

Вот код для TestBindingClass, как указано выше.StatusStrings - это изменяемый массив, который будет содержать список строк состояния, которые должны в конечном итоге отображаться в представлении прокрутки, которое наблюдает за этим классом через свойство arraystatuscounter.

 //  TestBindingClass.h

 #import <Foundation/Foundation.h>

 @interface TestBindingClass : NSObject {

NSMutableArray *StatusStrings;
   int arraystatuscounter;


 }

 @property (nonatomic, retain) NSMutableArray *StatusStrings;
 @property  int arraystatuscounter;

 - (void) runStatusStringSequence:(id)param;
 - (void) submitStatusStringSequence;

 @end

Это показывает код TestBindingClass, который довольнопросто.Просто вставляйте по очереди глупые маленькие строки в NSMutableArray и каждый раз добавляйте свойство arraystatuscounter, добавляя строку в массив.

 //  TestBindingClass.m

 - (void) submitStatusStringSequence
 {

     [NSThread detachNewThreadSelector:@selector(runStatusStringSequence:) toTarget:self withObject:nil];

 }


 - (void) runStatusStringSequence:(id)param 
 {


NSMutableArray *StatusStringsAlloc = [[NSMutableArray alloc] initWithCapacity:1];
StatusStrings = StatusStringsAlloc;

[StatusStrings addObject:[NSString stringWithString:@"first string"]];
[self setArraystatuscounter:1];

[StatusStrings addObject:[NSString stringWithString:@"second string"]];
[self setArraystatuscounter:2];

[StatusStrings addObject:[NSString stringWithString:@"third string"]];
[self setArraystatuscounter:3];

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]];
[self setArraystatuscounter:4];

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]];
[self setArraystatuscounter:5];

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]];
[self setArraystatuscounter:6];

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]];
[self setArraystatuscounter:7];

[StatusStrings addObject:[NSString stringWithString:@"last string"]];
[self setArraystatuscounter:8];

[StatusStrings addObject:[NSString stringWithString:@"first string"]];
[self setArraystatuscounter:1];

[StatusStrings addObject:[NSString stringWithString:@"second string"]];
[self setArraystatuscounter:2];

[StatusStrings addObject:[NSString stringWithString:@"third string"]];
[self setArraystatuscounter:3];

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]];
[self setArraystatuscounter:4];

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]];
[self setArraystatuscounter:5];

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]];
[self setArraystatuscounter:6];

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]];
[self setArraystatuscounter:7];

[StatusStrings addObject:[NSString stringWithString:@"last string"]];
[self setArraystatuscounter:8];

 }

Вот код контроллера окна RunResultWindows.Адрес для TestBindingClass добавляется в ivars, чтобы он мог правильно настроить необходимые параметры наблюдателя KVO.

 //  RunResultWindow.h

 @interface RunResultWindow : NSWindowController {

  NSTextView *RunResultWindowTextView;
  TestBindingClass *localTestBindingClass;
 }

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

 @property (strong) IBOutlet NSTextView *RunResultWindowTextView;
 @property (nonatomic, retain) TestBindingClass *localTestBindingClass;

 @end

Вот интересующие нас методы RunResultWindow.Прошу прощения за массив, если оператор, который был добавлен туда для некоторых временных целей отладки и никогда не удалялся.

 //  RunResultWindow.m methods of interest

 - (void)windowDidLoad
 {
     [super windowDidLoad];

     NSWindow *wcWindow;
     wcWindow = [self window];
     [wcWindow makeKeyAndOrderFront:self];

     NSString *teststring;
     teststring = [NSString stringWithString: @"show first time window did load "];
     [RunResultWindowTextView setString:teststring];
     [RunResultWindowTextView display];

     [localTestBindingClass addObserver:self
       forKeyPath:@"arraystatuscounter"
          options:NSKeyValueObservingOptionNew
          context:NULL];

 }

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change 
    context:(void *)context
{
  NSInteger arrayCount;
  NSString* localDisplayString;
  NSString* localNewlinePlusDisplayString;
  NSTextStorage *tempTextStorage;

  tempTextStorage = [RunResultWindowTextView textStorage];

  arrayCount = [ localTestBindingClass.StatusStrings count ];
  if (arrayCount >= 1) {
    arrayCount--;
    localDisplayString = [localTestBindingClass.StatusStrings objectAtIndex:arrayCount];
    localNewlinePlusDisplayString = [@"\n" stringByAppendingString:localDisplayString];
    [tempTextStorage beginEditing];
    [tempTextStorage replaceCharactersInRange:NSMakeRange([tempTextStorage length] - 1, 0) 
                             withString:localNewlinePlusDisplayString];
    [tempTextStorage endEditing];
    [RunResultWindowTextView display];
  }

 }

Ответы [ 2 ]

2 голосов
/ 24 декабря 2011

Я не думаю, что КВО имеет к этому какое-то отношение. Бит, который вы описываете как «довольно простой», на самом деле очень, очень сложный. Это виновник: -

[NSThread detachNewThreadSelector:@selector(runStatusStringSequence:) toTarget:self withObject:nil]

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

Вам, по крайней мере, нужно изучить эти

http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/Multithreading.pdf

Если вы вообще удалили второй поток, я все равно не ожидал бы, что это будет работать постоянно (хотя иногда это может работать). Вы не должны вызывать -display в текстовом представлении напрямую. Рисование видов, настройка контекста рисования, частота обновления и т. Д. Обрабатывается фреймворком.

Простого вызова -display в случайное время, не зная состояния текущего контекста рисования, будет достаточно, чтобы вывести из строя ваше приложение (вы можете легко рисовать в любой случайный фрагмент памяти). Добавьте нити в смесь, и у вас больше проблем.

Ваш метод

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

вызывается в фоновом потоке, но графический интерфейс не является потокобезопасным и должен выполняться в основном потоке, включая любые взаимодействия с объектами графического интерфейса, такие как текстовые представления.

0 голосов
/ 26 декабря 2011

Вот код, который действительно работал. Прошу прощения за неправильную регистрацию и случайный бит нечетного кода, оставшегося от предыдущей итерации решения.

Вот код метода кнопки «Выполнить» в главном окне ...

- (IBAction)runButtonPressed:(id)sender
{

    // do a bunch of stuff

    TestBindingClass* tempTestBindingClass = [[TestBindingClass alloc] init];

    RunResultWindowController = [[RunResultWindow alloc] initWithWindowNibName:@"RunResultWindow"];
    RunResultWindowController.localTestBindingClass=tempTestBindingClass;
    [RunResultWindowController showWindow:self];

    [self.outputfilestring1 becomeFirstResponder];

    [tempTestBindingClass submitStatusStringSequence];

}

Вот код для TestBindingClass, как указано выше. StatusStrings - это изменяемый массив, который будет содержать список строк состояния, которые в конечном итоге должны отображаться в представлении прокрутки, которое отслеживает этот класс через свойство arraystatuscounter.

 //  TestBindingClass.h

 #import <Foundation/Foundation.h>

 @interface TestBindingClass : NSObject {

NSMutableArray *StatusStrings;
   int arraystatuscounter;


 }

 @property (nonatomic, retain) NSMutableArray *StatusStrings;
 @property  int arraystatuscounter;

 - (void) runStatusStringSequence:(id)param;
 - (void) submitStatusStringSequence;

 @end

Здесь показан код реализации TestBindingClass. Он просто сбрасывает глупые маленькие строки в NSMutableArray по одной и тянет свойство arraystatuscounter каждый раз, когда добавляет строку в массив, чтобы отключить метод наблюдения KVO. Первое существенное отличие состоит в том, что я использовал методы GCD, как предложено выше ...

//  TestBindingClass.m

 - (void) submitStatusStringSequence
 {
     NSString *parameterString;
    [self runStatusStringSequence: parameterString];
 }


 - (void) runStatusStringSequence:(id)param 
 {

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

dispatch_async(backgroundQueue, ^{ 

NSMutableArray *StatusStringsAlloc = [[NSMutableArray alloc] initWithCapacity:1];
StatusStrings = StatusStringsAlloc;

[StatusStrings addObject:[NSString stringWithString:@"first string"]];
[self setArraystatuscounter:1];

[StatusStrings addObject:[NSString stringWithString:@"second string"]];
[self setArraystatuscounter:2];

[StatusStrings addObject:[NSString stringWithString:@"third string"]];
[self setArraystatuscounter:3];

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]];
[self setArraystatuscounter:4];

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]];
[self setArraystatuscounter:5];

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]];
[self setArraystatuscounter:6];

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]];
[self setArraystatuscounter:7];

[StatusStrings addObject:[NSString stringWithString:@"last string"]];
[self setArraystatuscounter:8];

[StatusStrings addObject:[NSString stringWithString:@"first string"]];
[self setArraystatuscounter:1];

[StatusStrings addObject:[NSString stringWithString:@"second string"]];
[self setArraystatuscounter:2];

[StatusStrings addObject:[NSString stringWithString:@"third string"]];
[self setArraystatuscounter:3];

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]];
[self setArraystatuscounter:4];

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]];
[self setArraystatuscounter:5];

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]];
[self setArraystatuscounter:6];

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]];
[self setArraystatuscounter:7];

[StatusStrings addObject:[NSString stringWithString:@"last string"]];
[self setArraystatuscounter:8];

dispatch_release(backgroundQueue);

});


 }

Вот код контроллера окна RunResultWindows. Адрес для TestBindingClass добавляется в ivars, чтобы он мог правильно настроить необходимые параметры наблюдателя KVO. Кроме того, я принудительно запустил метод в основном потоке, потому что, я полагаю, он выполнялся в фоновом потоке, потому что он был вызван процессом, уже запущенным в фоновом потоке.

@interface RunResultWindow : NSWindowController {

    NSTextView *RunResultWindowTextView;
    TestBindingClass *localTestBindingClass;

}

- (IBAction)FinishButtonPush:(id)sender;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

@property (strong) IBOutlet NSTextView *RunResultWindowTextView;
@property (nonatomic, retain) TestBindingClass *localTestBindingClass;

@end

Вот интересующие нас методы RunResultWindow. Пожалуйста, извините оператор arraycount if, который был добавлен туда для некоторых целей временной отладки и никогда не удаляется Обратите внимание, что это было принудительно в основной поток.

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change 
        context:(void *)context
{    

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{ 

    NSInteger arrayCount;
    NSString* localDisplayString;
    NSString* localNewlinePlusDisplayString;
    NSTextStorage *tempTextStorage;

    tempTextStorage = [RunResultWindowTextView textStorage];

    arrayCount = [ localTestBindingClass.StatusStrings count ];
    if (arrayCount >= 1) {
        arrayCount--;
        localDisplayString = [localTestBindingClass.StatusStrings objectAtIndex:arrayCount];
        localNewlinePlusDisplayString = [@"\n" stringByAppendingString:localDisplayString];
        [tempTextStorage beginEditing];
        [tempTextStorage replaceCharactersInRange:NSMakeRange([tempTextStorage length] - 1, 0) 
                                 withString:localNewlinePlusDisplayString];
        [tempTextStorage endEditing];
        [RunResultWindowTextView setNeedsDisplay:YES];}

    });
}

Большое спасибо hooleyhoop за хорошие указатели в правильном направлении.

...