2012-09-25 28 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