سؤال

First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.

I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.

Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).

Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.

In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).

My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.

Here's some of the pertinent code:

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

start method in 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.");
}

Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!

Stepping out for a bit; sorry if I don't respond immediately!

هل كانت مفيدة؟

المحلول

Whew, quiet a question for probably a simple answer: ;)

UI updates have to be performed on the main thread

If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.

Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:

dispatch_async(dispatch_get_main_queue(), ^{
    // perform UI updates
});
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top