2012-09-25 30 views
0

首先,我是一個Objective-C新手。所以我不熟悉OS X或iOS開發。我的經驗主要是Java。從線程異步更新NSView

我正在創建一個基於代理的建模框架。我想展示模擬,並做我正在寫一個小應用程序。首先,介紹一下框架。該框架有一個World類,其中有一個start方法,該方法遍歷所有代理並讓它們執行其任務。在世界的一個「步驟」結束時(即,在所有代理完成它們的事情之後),start方法調用實現InterceptorProtocol的對象的方法intercept。這個對象以前通過構造函數傳入。使用攔截器,任何人都可以進入世界的狀態。這對日誌記錄很有用,或者在我試圖完成的場景中:以圖形方式顯示信息。撥打intercept是同步的。

現在就GUI應用程序而言,它非常簡單。我有一個初始化自定義視圖的控制器。這個自定義視圖還實現InterceptorProtocol,以便它可以監聽和查看世界上發生的事情。我創建了一個World對象並作爲攔截器傳入視圖中。該視圖通過私有財產保持對世界的引用,因此,一旦我初始化了世界,我將視圖的世界屬性設置爲我剛剛創建的世界(我意識到這會創建一個循環,但我需要引用世界在視圖的drawRect方法和唯一的方法,我可以擁有它是如果我保持從類的引用)。

由於世界上的start方法是同步的,所以我不會馬上啓動世界。在drawRect方法中,我檢查世界是否在運行。如果不是,我在後臺線程中啓動它。如果是這樣,我檢查世界並顯示我需要的所有圖形。

intercept方法(從start被調用後臺線程運行),我設置setNeedsToDisplayYES。由於世界的start方法在單獨的線程中運行,因此我還有一個鎖定對象用於同步,這樣我就不會在World對象正在進行變異的情況下工作(此部分有點笨拙,而且很可能不按我期望的方式工作 - 這裏有不止幾個粗糙的地方,我只是想努力工作;我打算晚些時候清理一下)。

我的問題是視圖呈現一些東西,然後它幾乎鎖定。我可以看到NSLog語句正在被調用,所以代碼正在運行,但沒有任何更新的視圖。

下面是一些相關的代碼:

MasterViewController

#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

- (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相關的東西在主線程上執行。最好的辦法是使用大中央調度,除非你需要支持的iOS 4 <或OS X 10.6 <(或者是它10.7?),就像這樣:

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

聽起來有希望!我會給這個:) –

+0

另一個問題 - 將'peformOnMainThread'也工作? –

+1

是的,'performOnMainThread'也可以。 – Pascal