Search code examples
iphoneobjective-cmultithreadingsingletonalloc

Data going missing when passed between threads using a Singleton


Edit:

Thanks @BlackFrog. I think I'm nearer now, but the values are still not get getting through...

The values are set as shown by logs within [progressController updateProgressSummary:...] but are nil when I log them in progressUpdate initWithProgressUpdate:.... as shown below.

I'm slightly confused over which property is used the one set for progressUpdate or the ones set for each of the 3 components of progressUpdate. I have changed the 3 individual properties from assign to retain as suggested and have also tried doing the same with the overall progressUpdate property too (not shown here).

progressController.h

......

    @property (nonatomic, assign)   ProgressUpdate  *progressUpdate;

progressController.m

// Ask delegate to update and display Progress text
-(void) updateProgressSummary:(NSString *)summary detail:(NSString *)detail percentComplete:(NSNumber *)complete {
// These report the proper values
    DLog(@"Reporting Summary - %s", [summary UTF8String]);
    DLog(@"Reporting Detail -  %s", [detail UTF8String]);
    DLog(@"Reporting Complete -  %i", [complete intValue]); 

    if (summary != nil)
        self.progressUpdate.summaryText = summary;
    self.progressUpdate.detailText = detail;
    self.progressUpdate.percentComplete = complete;

    ProgressUpdate *progressUpdateForIssue = [[ProgressUpdate alloc] initWithProgressUpdate:progressUpdate];    
    [self.delegate performSelectorOnMainThread:@selector(displayProgress:) withObject:progressUpdateForIssue waitUntilDone:NO]; 

    [progressUpdateForIssue release];

}

But then a few milliseconds later...., inside the object....they're nil.

progressUpdate.h

.....

@property (nonatomic, retain) NSString  *summaryText;
@property (nonatomic, retain) NSString  *detailText;
@property (nonatomic, retain) NSNumber  *percentComplete;

progressUpdate.m

-(id) initWithProgressUpdate:(ProgressUpdate *)update {
    if ((self = [super init])) {
        summaryText = [update.summaryText copy];
        detailText = [update.detailText copy];
        percentComplete = [[NSNumber alloc] initWithFloat:[update.percentComplete floatValue]];
    }
    // These report nil values
    DLog(@"Reporting in progUpdate summaryText - %s", [summaryText UTF8String]);
    DLog(@"Reporting in progUpdate detailText -  %s", [detailText UTF8String]);
    DLog(@"Reporting in progUpdate percentComplete -  %i", [percentComplete intValue]);
return self;
}

end of update

I need some help with passing data in a custom class from one thread to another. Its there before the pass but then disappears upon arrival. I've tried everything I know, but to no avail.

My background thread calls ProgressController and passes it details of the current progress. That in turn does performSelectorOnMainThread on ProgressController's delegate (the view controller) to display the details.

It was all working fine when I was passing through a single NSString, but I need to pass two strings and a number and as performSelectorOnMainThread can only pass one object, I have encapsulated these in a custom object - ProgressUpdate.

The data gets through to ProgressController correctly but is null by the time that it appears in the View Controller. I know this as I've put NSLogs in various places.

I wonder if its to do with:

  • multithreading and custom objects

  • the fact that ProgressController is a singleton, which is why I have then alloc'd a new ProgressUpdate each time its called, but that has not helped.

Any ideas welcome. For clarity, the code is below.

ProgressUpdate.h

#import <Foundation/Foundation.h>


@interface ProgressUpdate : NSObject {
    NSString    *summaryText;    
    NSString    *detailText;
    NSNumber    *percentComplete;
}
@property (nonatomic, assign) NSString  *summaryText;
@property (nonatomic, assign) NSString  *detailText;
@property (nonatomic, assign) NSNumber  *percentComplete;

-(id) initWith:(ProgressUpdate *)update;

@end

ProgressUpdate.m

#import "ProgressUpdate.h"

@implementation ProgressUpdate

@synthesize summaryText, detailText, percentComplete;

-(id) initWith:(ProgressUpdate *)update {
    self = [super init];

    self.summaryText = update.summaryText;
    self.detailText = update.detailText;
    self.percentComplete = update.percentComplete;
    return self;
}

@end

ProgressController.m

static ProgressController *sharedInstance;

+ (ProgressController *)sharedInstance {
    @synchronized(self) {
        if (!sharedInstance)
            [[ProgressController alloc] init];              
    }
    return sharedInstance;
}

+(id)alloc {
    @synchronized(self) {
        NSAssert(sharedInstance == nil, NSLocalizedString(@"Attempted to allocate a second instance of a singleton ProgressController.", @"Attempted to allocate a second instance of a singleton ProgressController."));
        sharedInstance = [super alloc];
    }
    return sharedInstance;
}
-(id) init {
    if (self = [super init]) {
        [self open];
    }
    return self;
}

.........

// Ask delegate to update and display Progress text
-(void) updateProgressSummary:(NSString *)summary detail:(NSString *)detail percentComplete:(NSNumber *)complete {

    if (summary != nil)
        self.progressUpdate.summaryText = summary;
    self.progressUpdate.detailText = detail;
    self.progressUpdate.percentComplete = complete;
    ProgressUpdate *progressUpdateForIssue = [[ProgressUpdate alloc] initWith:progressUpdate];  
    [self.delegate performSelectorOnMainThread:@selector(displayProgress:) withObject:progressUpdateForIssue waitUntilDone:NO]; 
    [progressUpdateForIssue release];

}

RootViewController.m

// Delegate method to display specific text in Progress label
- (void) displayProgress:(ProgressUpdate *)update {
    [progressSummaryLabel setText:update.summaryText];
    [progressDetailLabel setText:update.detailText];
    [progressBar setProgress:[update.percentComplete intValue]];
    [progressView setNeedsDisplay];
}

Solution

  • In the init method, you are only assigning the ivars and not retaining them in the new object. Redo your init method as the following:

    -(id) initWithProgressUpdate:(ProgressUpdate *)update {
        if ((self = [super init])) {
            summaryText = [update.summaryText copy];
            detailText = [update.detailText copy];
            percentComplete = [[NSNumber alloc] initWithFloat:[update.percentComplete floatValue];
        }
        return self;
    }
    

    Couple of points:

    • You should not use accessor in the init method
    • Rename your init method to be a lot clear
    • In the @property, change the assign to retain