views:

173

answers:

3

I'm including the netinet headers and doing some raw socket programming on the iPhone, and have found when making these calls, the UI won't refresh until I'm finished- any response I get and write to the UI, isn't written until the IBAction that made the call is complete. Using performSelector with a delay, I've been able to work around this, but I was wondering if there is a better way of handling this?

A: 

The easiest way I have found is to spin up a second thread. Read the Threading Programming Guide.

I then communicate back to the UI via performSelectorOnMainThread:withObject:waitUntilDone:. It's easy to send and NSObject that encapsulates the data received from the network.

For example, if my networking code could query Stack Overflow for a bunch of questions, then I might have:

@interface Question {
    NSString *question;
    NSString *answer;
}
@end

@interface QuestionQueryResults {
    NSMutableArray *questions;
}
@end

and I have a view controller with the following public method:

@interface QuestionsController : UIViewController {
}
- (void)setQuestions:(QuestionQueryResults*)questions;
@end

The setQuestions method will then populate all your textboxes, reload the table view, and whatever needs to be done.

The threading code would go something like:

void performQuery(QuestionsController *delegate) {

    QuestionQueryResults *results = [[QuestionQueryResults alloc] init];

    // Process the network data to fill in results

    [delegate performSelectorOnMainThread:@selector(setQuestions:)
              withObject:results
              waitUntilDone:YES]; // Wait so we can `release` the results

    [results release];        
}

You could consider QuestionQueryResults as the "ViewModel" in an MVC architecture. Works great and produces minimal UI glitches.

Frank Krueger
Thanks Frank. I'm going to try both your and Alex's suggestions.
Gene M.
I have read through the Threading Programming guide. There's a couple of different ways to spin a new thread- detaching as Alex mentions is one of them, a creating a background thread is another. I'm quite interested in what you've said, "communicate back to the UI via performSelectorOnMainThread:withObject:waitUntilDone:"- are you suggesting I update the UI with a call like [testField performSelectorOnMainThread:@selector(setText:) withObject:@"Some Text" waitUntilDone:YES] ??
Gene M.
I'm an old man so I still prefer `pthread_create` :-) I am updating the entry with a few more details regarding the communication back to the UI. The important thing is granularity; while setting text fields individually is workable, I don't recommend it.
Frank Krueger
@Frank: you don't need set `waitUntilDone:YES` to release the results object. The `performSelectorOnMainThread:` method will retain the object, the thread may release it and terminate before context returns to the main thread.
benzado
@benzado Good to know, thanks! I've switched over to MonoTouch for all my iPhone development so gladly don't have to think about this memory management silliness anymore.
Frank Krueger
+3  A: 

You can detach a new thread and run your network code in that method:

 [NSThread detachNewThreadSelector:@selector(doNetworkStuff:) toTarget:self withObject:nil];

 // ...

 - (void) doNetworkStuff:(id)_param {
     NSAutoreleasePool *_pool = [[NSAutoreleasePool alloc] init];
     // ... do netinet stuff here    
     [_pool release];
 }

By doing network stuff in its own thread, you leave the UI free to perform updates.

Alex Reynolds
+3  A: 

In keeping with the general philosophy that it's best to pick the highest level abstraction that is usable for you, I'll suggest that perhaps CFSockets might also be a possibility.

CFSocket is sufficiently low-level that you can likely do whatever form of network communications you're attempting, but it will help you by adding Run Loop integration. By using the run loop, you can avoid having to multi-thread your code. Using the run loop treats network events like any other source of events for your application.

See the CFNetwork Programming Guide for more information.

Jay O'Conor
Commenting to emphasize that adding the socket to the run loop and using callbacks is the key. It's just as effective as detaching a new thread, but you won't have to worry about locks/synchronization.
benzado
+1 Who worries about locks? :-) Even with threads, you still use a callback mechanism.
Frank Krueger
Actually, the first thing I did was create delegates and and callbacks, and that along does not solve the refresh issue.
Gene M.
I've had a look at the CFNetwork Programming guide, thank you. While this answer is very good, and especially helpful was the comment to add Run Loop integration, using CFSockets and related classes didn't solve my issue entirely. But I'm going marking it as the solution because it was the best answer given my vague question.
Gene M.