2012-09-25 3 views
0

우선, 저는 Objective-C 초보자입니다. 따라서 OS X 또는 iOS 개발에 익숙하지 않습니다. 내 경험은 대부분 자바이다.스레드에서 NSView를 비동기 적으로 업데이트하십시오.

저는 에이전트 기반 모델링 프레임 워크를 만들고 있습니다. 나는 시뮬레이션을 표시하고 싶습니다. 그리고 저는 약간의 어플리케이션을 작성하고 있습니다. 첫째, 프레임 워크에 대해 조금. 프레임 워크에는 World 클래스가 있으며이 클래스에는 모든 에이전트를 반복하고 작업을 수행하는 start 메서드가 있습니다. 전 세계의 한 "단계"가 끝날 때 (즉 모든 상담원이 자신의 작업을 완료 한 후) start 메소드는 InterceptorProtocol을 구현하는 객체의 intercept 메소드를 호출합니다. 이 객체는 이전에 생성자를 통해 전달되었습니다. 인터셉터를 사용하여 누구나 세계의 상태를 파악할 수 있습니다. 이것은 로깅 또는 내가 성취하려고하는 시나리오에서 유용합니다. 정보를 그래픽 방식으로 표시하는 것입니다. intercept에 대한 호출은 동기식입니다.

지금까지 GUI 응용 프로그램에 관한 한 매우 간단합니다. 사용자 정의보기를 초기화하는 컨트롤러가 있습니다. 이 사용자 정의보기는 또한 세계에서 일어나는 일을 청취 할 수 있도록 InterceptorProtocol을 구현합니다. 나는 World 오브젝트를 생성하고 뷰를 인터셉터로 전달합니다. 이 뷰는 사유 재산을 통해 세계에 대한 참조를 유지하므로 세계를 초기화하고 나면 방금 만든 세계에 뷰의 세계 속성을 설정합니다 (이것이 사이클을 생성한다는 것을 알고 있지만 세상의 drawRect보기의 방법과 나는 그것을 가질 수있는 유일한 방법은 내가 클래스에서 그것에 대한 참조를 유지하는 경우)입니다.

세계의 start 방법이 동기식이므로 나는 즉시 세계를 시작하지 않습니다. drawRect 메서드에서 나는 세계가 실행 중인지 확인합니다. 그렇지 않다면 백그라운드 스레드에서 시작합니다. 그렇다면 세계를 조사하고 필요한 모든 그래픽을 표시합니다. intercept 방법에서

은 (백그라운드 스레드에서 실행 start에서 호출되는 함), 나는 YESsetNeedsToDisplay을 설정합니다. 세계의 start 메서드는 별도의 스레드에서 실행되기 때문에 동기화 할 때 사용하는 잠금 개체가 있으므로 World 개체에서 작업하지 않습니다.이 개체는 돌연변이입니다 (이 부분은 janky이며 아마 내가 예상하는대로 작동하지 않습니다. - 거친 부분이 몇 개 있고, 조금만 노력하려고합니다. 나중에 정리할 계획입니다.)

내 문제는 뷰가 일부 항목을 렌더링하고 꽤 많이 잠기는 것입니다. NSLog 문이 호출되고 있으므로 코드가 실행되고 있지만 뷰에서 아무 것도 업데이트되지 않습니다.

#import "MasterViewController.h" 
#import "World.h" 
#import "InfectableBug.h" 

@interface MasterViewController() 

@end 

@implementation MasterViewController 

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 
{ 
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 
    if (self) { 
     _worldView = [[WorldView alloc] init]; 

     World* world = [[World alloc] initWithName: @"Bhumi" 
               rows: 100 
              columns: 100 
             iterations: 2000 
            snapshotInterval: 1 
             interceptor: _worldView]; 
     for(int i = 0; i < 999; i++) { 
      NSMutableString* name = [NSMutableString stringWithString: @"HealthyBug"]; 
      [name appendString: [[NSNumber numberWithInt: i] stringValue]]; 
      [world addBug: [[InfectableBug alloc] initWithWorld: world 
                  name: name 
                  layer: @"FirstLayer" 
                 infected: NO 
               infectionRadius: 1 
               incubationPeriod: 10 
             infectionStartIteration: 0]]; 
     } 

     NSLog(@"Added all bugs. Going to add infected"); 

     [world addBug: [[InfectableBug alloc] initWithWorld: world 
                 name: @"InfectedBug" 
                 layer: @"FirstLayer" 
                infected: YES 
              infectionRadius: 1 
              incubationPeriod: 10 
            infectionStartIteration: 0]]; 

     [_worldView setWorld: world]; 

     //[world start]; 
    } 

    return self; 
} 

- (NSView*) view { 
    return self.worldView; 
} 

@end 

세계관

#import "WorldView.h" 
#import "World.h" 
#import "InfectableBug.h" 

@implementation WorldView 

@synthesize world; 

- (id) initWithFrame:(NSRect) frame { 
    self = [super initWithFrame:frame]; 
    if (self) { 
     // Initialization code here. 
    } 

    return self; 
} 

- (void) drawRect:(NSRect) dirtyRect { 

    CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort]; 
    CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768)); 

    NSUInteger rows = [world rows]; 
    NSUInteger columns = [world columns]; 

    NSUInteger cellWidth = 1024/columns; 
    NSUInteger cellHeight = 768/rows; 

    if([world running]) { 
     @synchronized (_lock) { 
      //Ideally we would need layers, but for now let's just get this to display 
      NSArray* bugs = [world bugs]; 
      NSEnumerator* enumerator = [bugs objectEnumerator]; 
      InfectableBug* bug; 
      while ((bug = [enumerator nextObject])) { 
       if([bug infected] == YES) { 
        CGContextSetRGBFillColor(myContext, 128, 0, 0, 1); 
       } else { 
        CGContextSetRGBFillColor(myContext, 0, 0, 128, 1); 
       } 

       NSLog(@"Drawing bug %@ at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight); 

       CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight)); 
      } 
     } 
    } else { 
     [world performSelectorInBackground: @selector(start) withObject: nil]; 
    } 
} 

- (BOOL) isFlipped { 
    return YES; 
} 

- (void) intercept: (World *) aWorld { 

    struct timespec time; 
    time.tv_sec = 0; 
    time.tv_nsec = 500000000L; 

    //nanosleep(&time, NULL); 

    @synchronized (_lock) { 
     [self setNeedsDisplay: YES]; 
    } 
} 

@end 

시작 방법 에서 World.m

MasterViewController :

다음은 관련 코드의 일부입니다 :

- (void) start { 

    running = YES; 

    while(currentIteration < iterations) { 

     @autoreleasepool { 

      [bugs shuffle]; 

      NSEnumerator* bugEnumerator = [bugs objectEnumerator]; 
      Bug* bug; 

      while((bug = [bugEnumerator nextObject])) { 

       NSString* originalLayer = [bug layer]; 
       NSUInteger originalX = [bug x]; 
       NSUInteger originalY = [bug y]; 

       //NSLog(@"Bug %@ is going to act and location %i:%i is %@", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? @"occupied" : @"not occupied"); 
       [bug act]; 
       //NSLog(@"Bug has acted"); 

       if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) { 
        //NSLog(@"Bug has moved"); 
        [self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]]; 
        //NSLog(@"Updated bug position"); 
       } 
      } 

      if(currentIteration % snapshotInterval == 0) { 
       [interceptor intercept: self]; 
      } 

      currentIteration++; 
     } 
    } 

    //NSLog(@"Done."); 
} 

다른 코드를보고 싶다면 알려주십시오.나는 그 코드가 예쁘지 않다는 것을 알고있다. 나는 물건을 작동 시키려고하고 있었고 나중에 청소할 계획입니다. 또한 Objective-C 모범 사례를 위반하는 경우 알려 주시기 바랍니다.

조금 씩 나가기; 죄송합니다. 즉시 응답하지 않으면!

답변

1

휴, 조용한 아마도 간단한 대답에 대한 질문 :)

UI 업데이트가 주 스레드에서 수행해야

난 당신의 코드가 제대로, 당신은 start 메소드를 호출 읽는다면 배경 스레드. 시작 메소드는 moveBugFrom:...과 같은 메소드와 intercept: 메소드를 포함합니다. 인터셉트 메서드는 따라서 백그라운드 스레드에서 setNeedsDisplay:을 호출합니다.

메인 스레드에서 모든 UI 관련 작업 수행. 가장 좋은 방법은 아이폰 OS < 4 또는 OS X < 10.6을 지원해야하지 않는 한, 그랜드 센트럴 디스패치를 ​​사용하는 것입니다 (또는 10.7이었다?)이 같은 :

dispatch_async(dispatch_get_main_queue(), ^{ 
    // perform UI updates 
}); 
+0

가 유망한 소리! 나는 이것을 줄 것이다 :) –

+0

또 다른 질문 - 'peformOnMainThread'도 작동 할까? –

+1

예, 'performOnMainThread'도 작동합니다. – Pascal

관련 문제