Text is displayed to view after it should be

Discussion in 'iOS Programming' started by l0uismustdie, Dec 29, 2010.

  1. l0uismustdie macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #1
    Hello. I am trying to display some text and then do some more stuff afterwords but for some reason the stuff that happens after I set the text happens first. Some relevant code:
    Code:
    [timeFormatter setDateFormat:@"dd-MM HH:mm:ss:SS"];
    timeString=[timeFormatter stringFromDate:date];
    results=timeString;
    results=[results stringByAppendingString:@" Parsing executable...\n"];
    textView.text=[textView.text stringByAppendingString:results];
    	
    loadElfReturn=[vm loadelf:filespace];
    
    So, I would think that this should set the text in the view before calling loadelf: but that isn't the case. Is there some way I can display this text to the screen immediately?
     
  2. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    Drawing occurs when your code returns control to the main event loop. Any task that your code executes that needs to update the screen in real time or that will take more than a fraction of a second to complete should be done in a background thread. Use NSOperation and NSOperationQueue. Parsing of anything almost always falls into this category.
     
  3. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #3
    Yes, this is what I am looking for. I have looked at a couple of sources and I think the way I want to approach this is using the NSInvocationOperation. I am following a source online that uses it as follows:
    Code:
    NSInvocationOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen) object:@" Parsing executable...\n"];
    [operationQueue addOperation:op];
    [op release];
    
    However, this is giving me a
    Code:
    Program received signal:  “EXC_BAD_ACCESS”
    
    error. I've declared operationQueue in my header and printToScreen is also defined. Is there something I am missing here?
     
  4. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    Did you alloc/init the operation queue?

    What line does the crash occur on?
     
  5. l0uismustdie, Dec 30, 2010
    Last edited by a moderator: Dec 30, 2010

    l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #5
    I have the operationQueue declared in my header so that should be okay. It looks like its crashing on the
    Code:
    NSInvocationOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen) object:@" Parsing executable...\n"];
    
    line. In fact, if I take out the operationQueue line the program still crashes.

    I should also note that I have run the "Leaks" and "Allocations" (with NSZombie detection) tools to see if this was a memory allocation problem. I have had those in the past and one of these tools has always caught them.

    For clarity sake I'll post some more info.
    This is in my .h file:
    Code:
    NSOperationQueue	*operationQueue;
    ...
    -(void)printToScreen:(NSString*)status;
    
    And of course the operation queue has a @synthesize in the .m file.

    If I comment out the Invocation line
    Code:
    NSInvocationOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen) object:@" Parsing executable...\n"];
    
    everything runs smoothly.
     
  6. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #6
    You didn't answer my questions.

    The selector needs to be

    Code:
    selector:@selector(printToScreen:)
    to match your method, but I don't know if that's the only problem.
     
  7. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #7
    Well that looks like it was the problem. However, I have found an alternative solution to my problem:
    Code:
    [self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Getting data file...\n"  waitUntilDone:YES];
    
    This seems to do the job!

    Thanks for your help.
     
  8. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #8
    So, maybe this isn't working the way that I though it should. It seemed to have done the job initially and now weird things are happening. I've put in a few test lines:
    Code:
    [self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Parsing Executable...\n" waitUntilDone:YES];
    NSLog(@"DING!");
    
    Now, it's my understanding I shouldn't see DING! before Parsing Executable is put on the screen...this is not the case however. For reference here is my printScreen: function
    Code:
    -(void)printToScreen:(NSString *)status
    {
    	NSDateFormatter		*timeFormatter=[[NSDateFormatter alloc]init];
    	NSDate				*date=[NSDate date];
    	NSString			*timeString;	
    
    	//set up timestamp and view
    	[timeFormatter setDateFormat:@"dd-MM HH:mm:ss:SS"];
    	timeString=[timeFormatter stringFromDate:date];
    	results=timeString;
    	results=[results stringByAppendingString:status];
    	textView.text=[textView.text stringByAppendingString:results];
    	NSLog(@"texView: %@",textView.text);
    	
    }
    
    I've added the print at the end and the text view does indeed hold the values I think it should have but it isn't being displayed on the screen. Anyone have any ideas what might be going on here?
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    Perhaps there is a disconnect between your textView variable and the UITextView that is on the screen.
     
  10. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #10
    I don't think there is. I am doing this a couple times. The first three things that I want to print to the screen get printed in what appears to be the right time. However, when I get to the fourth one or so they start getting delayed. The messages are still printed to the screen but it is after some other things happen.
     
  11. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #11
    Hmm, well that's a little different then "it isn't being displayed on the screen".

    After what kind of things? You need to realize that even if you ask the main thread to update the UI, this is not going to happen until you return control to the main event loop. Are these "other things" being done in the background or all they still part of the main event loop?
     
  12. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #12
    Maybe I will just show an example?

    Code:
    [self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Parsing Executable...\n" waitUntilDone:YES];
    	
    loadElfReturn=[vm loadelf:filespace];
    
    So, the print to screen here will happen after loadelf:
    This is all in the same function so shouldn't that function be waiting until the printToScreen: is finished before it carries on with the rest of it's execution?
     
  13. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #13
    Can we see the entire function? Is it running in a background thread or on the main thread?

    P.S. How are results and textView declared? Are they properties? If so, why are you not using the accessors you synthesized for them?
     
  14. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #14
    Code:
    -(void)encodeELF:(NSData *)data
    {
    	//initialize variable and allocate memory
    	NSDictionary		*memory,*symbol_table,*loadElfReturn;;
    	NSString*			filespace=[[NSString alloc]initWithData:data encoding:NSASCIIStringEncoding];
    	int					entry;
    	short				arch;
    	
    	virtual_machine_32 *vm = [[virtual_machine_32 alloc]init];
    	
    	[self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Parsing Executable...\n" waitUntilDone:YES];
    	NSLog(@"DING!");
    	
    	loadElfReturn=[vm loadelf:filespace];
    	memory=[loadElfReturn objectForKey:@"Memory"];
    	symbol_table=[loadElfReturn objectForKey:@"SymbolTable"];
    	entry=[[loadElfReturn objectForKey:@"Entry"]intValue];
    	arch=[[loadElfReturn objectForKey:@"Arch"]shortValue];
    
    	[self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Executing...\n" waitUntilDone:YES];
    	
    	[vm vm32:memory withEntrypoint:entry withSymbolTable:symbol_table];
    	
    //	NSLog(@"datafile:%@",dataFile);
    	
    	//free memory
    	[vm release];
    	[filespace release];
    	
    	[self uploadDataRequest];
    }
    
    I am not terribly sure how to answer your background or main thread question. I would believe this is running in the main thread...but I'm not really sure.

    As for *textView* and *results* they are properties...I'm not sure why I wasn't using the synthesized methods. New function:
    Code:
    -(void)printToScreen:(NSString *)status
    {
    	NSDateFormatter		*timeFormatter=[[NSDateFormatter alloc]init];
    	NSDate				*date=[NSDate date];
    	NSString			*timeString;	
    
    	//set up timestamp and view
    	[timeFormatter setDateFormat:@"dd-MM HH:mm:ss:SS"];
    	timeString=[timeFormatter stringFromDate:date];
    
    	[self setResults:timeString];
    	[self setResults:[results stringByAppendingString:status]];
    	
    	textView.text=[textView.text stringByAppendingString:results];
    	[self setTextView:textView];
    	
    	NSLog(@"texView: %@",textView.text);
    	[timeFormatter release];
    }
    
    Really though what is the difference?
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    I'm also guessing you're running in the main thread as well, since you don't seem to have any autorelease pools of your own around. So, based on that, here is, in effect what is happening: your call to loadelf: is blocking the main thread and, therefore, the UI update doesn't get taken care of until the main run loop is under it's own control, which doesn't happen until loadelf: is done.

    I think you might want to re-consider PhoneyDeveloper's advice and pursue the NSOperation approach.

    The difference is that you have defined certain directives (retain, most probably) for your properties and if you don't use the synthesized accessors you are bypassing the logic created for those directives and messing with memory-management, among other things.
     
  16. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #16
    I've reread your post a couple times and I'm wondering about one point. If I am running a line like:
    Code:
    [self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Parsing Executable...\n" waitUntilDone:YES];
    
    how could anything after that take control of the main thread. Shouldn't the main thread be executing printToScreen: and waiting until that is finished to do anything else?

    So, I've moved back to the NSOperationQueue idea. What I have produced looks to me like it should do what I want:
    Code:
    NSInvocationOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen:) object:@" Parsing Executable...\n"];
    [operationQueue addOperation:op];
    [op waitUntilFinished];
    [op release];
    
    The operation queue has been allocated memory and initialized. However, this will return an error of:
    Code:
    void SendDelegateMessage(NSInvocation*): delegate (<CFNotificationCenter 0x5c00760 [0x2414380]>) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode
    
    I'm not sure why this would take more than 10 seconds...
     
  17. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #17
    It does wait until it's finished. I think your confusion lies in your expecting the UI to be updated as part of that selector call. It's not. The UI is just "flagged" to be needing updating and that update is not actually processed until the main run loop is under its own control, which, in your case, is after your loadelf: has finished, because you are calling it from within the main thread.
     
  18. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #18
    That makes sense. I am wondering though about waitUntilFinished: for the InvocationOperation. It seems like whenever I use this method it hangs my app. I read the way to use this is to add an operation to the queue then send it the waitUntilFinished: which is what I'm trying:
    Code:
    operationQueue=[[NSOperationQueue alloc]init];
    operationQueue=[NSOperationQueue mainQueue];
    [operationQueue setMaxConcurrentOperationCount:1];
    
    NSInvocationOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen:) object:@" Connecting to server...\n"];
    [operationQueue addOperation:op];
    [op waitUntilFinished];
    [op release];
    
    But this will crash my program with the waitUntilFinished: line.
     
  19. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #19
    I'll let someone more familiar with NSOperations field this concern from here on out. Good luck though!
     
  20. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #20
    There's no reason to use waitUntilFinished. I believe it's creating a deadlock the way you're using it.

    The operation should be started, allowed to run to completion, then it notifies the delegate on the main thread that it's done.
     
  21. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #21
    Which makes total sense since I have set the maxConcurrentOperations to 1. But something is still wrong here. I am going to post the function I am looking at:
    Code:
    -(void)encodeELF:(NSData *)data
    {
    	//initialize variable and allocate memory
    	NSDictionary		loadElfReturn;;
    	NSString*			filespace=[[NSString alloc]initWithData:data encoding:NSASCIIStringEncoding];
    
    	
    	virtual_machine_32 *vm = [[virtual_machine_32 alloc]init];
    
    	NSInvocationOperation *parseOp=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen:) object:@" Parsing Executable...\n"];
    	[operationQueue addOperation:parseOp];	
    	[parseOp release];
    
    	NSInvocationOperation *loadelfOp=[[NSInvocationOperation alloc]initWithTarget:vm selector:@selector(loadelf:) object:filespace];
    	[operationQueue addOperation:loadelfOp];
    	loadElfReturn=[loadelfOp result];
    	[loadelfOp release];
    
    	Parameters *send=[[Parameters alloc]init];
    	send.memory=[loadElfReturn objectForKey:@"Memory"];
    	send.symbol_table=[loadElfReturn objectForKey:@"SymbolTable"];
    	send.entryPoint=[loadElfReturn objectForKey:@"Entry"];
    	arch=[[loadElfReturn objectForKey:@"Arch"]shortValue];
    
    	NSInvocationOperation *execPrintOp=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(printToScreen:) object:@" Executing...\n"];
    	[operationQueue addOperation:execPrintOp];
    	[execPrintOp release];
    	
    	NSInvocationOperation *executeOp=[[NSInvocationOperation alloc]initWithTarget:vm selector:@selector(vm32:) object:send];
    	[operationQueue addOperation:executeOp];
    	[executeOp release];
    	
    	//free memory
    	[vm release];
    	[filespace release];
    
    	NSInvocationOperation *uploadRequestOp=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(uploadDataRequest) object:nil];
    	[operationQueue addOperation:uploadRequestOp];
    	[uploadRequestOp release];
    }
    
    Looking at this it's my understanding that all of these things should be done sequentially. Meaning I should get "Parsing Executable..." on the screen, THEN I should run loadelf: and get the response from that THEN print "Executing..." to the screen, then once that is finished run vm32:, and finall run the requestUpload: function. But that isn't what is happening. It appears to be requesting the upload prior to finishing the vm32: function because what is getting uploaded isn't the result of vm32:
     
  22. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #22
    The red-hilited code is almost certainly wrong. First, you're not taking the possibility of nil into account. Second, you're not actually checking to see if the operation has completed (see the NSOperation class docs). The operation was just queued. Trust me, it's not going to complete that quickly. If you wanted synchronous (i.e. strict sequential) execution, then you need to design the code it does that. What you've coded does not do that.

    The whole point of queueing an operation is to perform it asynchronously. Your description of your understanding is almost the exact opposite of what the code appears to represent. In fact, I can't see why you'd be doing all those queued operations when what it seems you want is strict sequentiality.

    Your description is for a sequence of actions, to be performed exactly in sequence. I see NO possibility for one action to be performed without its predecessor being completed. If you believe there is, then either you're mistaken or you haven't adequately explained what problem you're trying to solve by all this. It looks to me like every action requires it's predecessor's completion (load file, parse symbols, extract symbols, blah, blah, etc.).

    I can see why some actions should completed before the display is updated, and I can see intermediate notifications that are displayed, but I see no reason why the actions shouldn't be done in sequence in a single async operation. There is no reason to block the main thread waiting for results to display, when those results can be delivered asynchronously. Blocking the main thread is wrong, and attempts to do so are severely misguided.

    If the actions are time-consuming, then simple logic suggests that the time-consuming part be done in the background, and the display only updated after the time-consuming part has completed. If intermediate notifications are needed, they can be delivered when intermediate waypoints are reached.

    Sadly, that's not at all what you've coded here. Instead, you've got a bunch of asynch operations (which shouldn't be done asynch'ly relative to one another), followed by some text update that should be done by callbacks, or notifications, which requires the completed results from the async operations.

    I think you need to completely reread the Threading Programming Guide that's the Companion Guide for NSInvocationOperation.
    http://developer.apple.com/library/...ationOperation_Class/Reference/Reference.html

    Then you need to completely redesign the code so that the things that need to occur in strict sequence actually occur in strict sequence, and you're not waiting for those to complete on the main thread. Then after completion, you can update the text display using a notification or callback. The current mish-mash of async and sync is never going to work.


    You should describe exactly what you're trying to accomplish, instead of describing how you're trying to accomplish it. I can tell you the "how" right now is almost completely wrong. What I'm unclear on is the "what", in order to suggest a more suitable "how".

    Finally, it's impossible to fully understand what's happening in the code without seeing the code for the methods that your operations invoke.
     
  23. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #23
    The kind of code you show is probably meant to run on a background thread. Then you can run a bunch of tasks and wait for each one to complete before proceeding. If you set up the opQ to run only a single operation at a time and then add a bunch of operations they should run one after the other. However the code you show that gets the result from an operation isn't right. The operation should store the result somewhere that the next operation can get it.

    I don't write operations like this. Instead when I have a series of tasks that need to execute in a specific order I set up a state machine. The first task runs. Then when it's finished it messages the main thread, which starts the next task. In fact most tasks don't take up much time and can run on the main thread. So there's no benefit to having them on a background thread. Also, NSURLConnection, which is frequently used for downloading or uploading files, is threaded internally. There's no benefit to running NSURLConnection from a thread, since it's already threaded.

    Also, the way your code is written I don't see any way to cancel the process.
     
  24. l0uismustdie thread starter macrumors regular

    l0uismustdie

    Joined:
    Nov 30, 2009
    Location:
    Edinburgh, UK
    #24
    This actually makes a lot more sense after that reply. I think I am moving forward. I'll restate some info for clarity. What I have been trying to do is display some text to the screen prior to a function being complete. After reading the previous responses the way I think is best to tackle this is put loadelf: and vm32: in background threads and use the main thread just to update the view. I have attempted that in the following revision:
    Code:
    -(void)encodeELF:(NSData *)data
    {
    	//initialize variable and allocate memory
    	NSDictionary		*memory;
    	NSString*			filespace=[[NSString alloc]initWithData:data encoding:NSASCIIStringEncoding];
    	
    	virtual_machine_32 *vm = [[virtual_machine_32 alloc]init];
    
    	[self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Parsing Executable...\n" waitUntilDone:YES];
    
    	NSInvocationOperation *loadelfOp=[[NSInvocationOperation alloc]initWithTarget:vm selector:@selector(loadelf:) object:filespace];
    	[loadelfOp setCompletionBlock:^
    	{
    		
    		NSDictionary	*loadElfReturn;
    		Parameters		*send=[[Parameters alloc]init];
    		loadElfReturn=[loadelfOp result];
    		
    		send.memory=[loadElfReturn objectForKey:@"Memory"];
    		send.symbol_table=[loadElfReturn objectForKey:@"SymbolTable"];
    		send.entryPoint=[loadElfReturn objectForKey:@"Entry"];
    
    		[self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Executing...\n" waitUntilDone:YES];
    		
    		NSInvocationOperation *executeOp=[[NSInvocationOperation alloc]initWithTarget:vm selector:@selector(vm32:) object:send];
    		[executeOp setCompletionBlock:^{[self uploadDataRequest];}];
    		[operationQueue addOperation:executeOp];
    
    		[executeOp release];
    		[loadelfOp release];
    	}];
    	[operationQueue addOperation:loadelfOp];
    
    	//free memory
    	[vm release];
    	[filespace release];
    }
    
    Really the only other relevant function here is uploadDataRequest:
    Code:
    -(void)uploadDataRequest
    {
    	//initialize variable and allocate memory
    	NSString			*wuname,*f,*s;
    	NSRange				l;
    
    	[dataFile retain];
    	wuname=[self getField:response andField:@"<wu_name>"];
    	
    	[self performSelectorOnMainThread:@selector(printToScreen:) withObject:@" Requesting upload...\n" waitUntilDone:YES];
    	
    	//create scheduler request
    	NSString *uploadPath= [[NSBundle mainBundle] pathForResource:@"data_upload_request" ofType:@"xml"];
    	data_upload_request=[NSString stringWithContentsOfFile:uploadPath encoding:NSASCIIStringEncoding error:NULL];
    
    	f=[NSString stringWithString:data_upload_request];
    	l=[f rangeOfString:@"</get_file_size>"];
    	s=[[[NSString alloc]initWithString:[f substringToIndex:l.location]]autorelease];
    	s=[s stringByAppendingString:wuname];
    	s=[s stringByAppendingString:[f substringFromIndex:l.location]];
    	uploadRequest=[NSString stringWithString:s];
    	
    	URLRequest *rq = [[[URLRequest alloc]init]autorelease];
    	[rq data:self requestSelector:@selector(uploadData:) withURL:file_handler andMethod:@"uploadRequest"];
    }
    
    So, this almost works as I would like. The text is displayed to the screen while the functions are executing. What seems to happen though is that when uploadDataRequest: returns from the URLRequest: call it returns to the
    Code:
    [executeOp setCompletionBlock:^{[self uploadDataRequest];}];
    
    line and hangs there (this was noticed when stepping through the program).

    Is this an appropriate strategy for executing these two functions sequentially while in a background thread? And why would uploadDataRequest: not make it to uploadData: which is the callback from the URLRequest: ??
     
  25. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #25
    I'm not really up on Blocks so I don't know the answer to why it's not working.

    I don't know what happens when you nest a block inside a block like that.
     

Share This Page