从线程异步更新NSView

时间:2012-09-25 16:26:42

标签: objective-c macos nsview quartz-2d agent-based-modeling

首先,我是Objective-C新手。所以我对OS X或iOS开发不太熟悉。我的经验主要是Java。

我正在创建一个基于代理的建模框架。我想展示模拟并做到这一点我正在编写一个小应用程序。首先,关于框架的一点点。该框架有一个World类,其中有一个start方法,它迭代所有代理并让它们执行任务。在世界的一个“步骤”结束时(即,在所有代理完成了它们之后),start方法调用实现intercept的对象的InterceptorProtocol方法。此对象先前通过构造函数传递。使用拦截器,任何人都可以进入世界状态。这对于日志记录或我正在尝试完成的场景非常有用:以图形方式显示信息。对intercept的调用是同步的。

现在就GUI应用程序而言,它非常简单。我有一个初始化自定义视图的控制器。此自定义视图还实现了InterceptorProtocol,以便它可以监听,以及世界上发生的事情。我创建一个World对象并将视图作为拦截器传递。该视图通过私有属性维护对世界的引用,因此一旦我初始化了世界,我将视图的世界属性设置为我刚刚创建的世界(我意识到这创建了一个循环,但我需要一个引用视图的drawRect方法中的世界以及我可以拥有它的唯一方法是,如果我从类中保留对它的引用。)

由于世界start方法是同步的,我不会立即启动世界。在drawRect方法中,我检查世界是否正在运行。如果不是,我在后台线程中启动它。如果是,我会检查世界并显示我需要的所有图形。

intercept方法(从后台线程上运行的start调用)中,我将setNeedsToDisplay设置为YES。由于世界的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

的WorldView

#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 个答案:

答案 0 :(得分:1)

哇,安静的问题可能是一个简单的答案:;)

必须在主线程上执行UI更新

如果我正确阅读了您的代码,请在后台线程上调用start方法。 start方法包含moveBugFrom:...intercept:方法之类的内容。截取方法因此在后台线程上调用setNeedsDisplay:

让所有与UI相关的内容在主线程上执行。您最好的选择是使用Grand Central Dispatch,除非您需要支持iOS&lt; 4或OS X&lt; 10.6(或者是10.7?),像这样:

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