D. Patrick
D. Patrick

Reputation: 3022

Returning values from main thread in objective-c

I'm working on an application in which I need to build some images in a background thread. At some point in the process, I need to get the text from a UITextView. If I call UITextview.text, I get the warning that my secondary threads shouldn't pester UIKit

That's all well and good, but I need the text and I can't figure out a reasonable way to get said text from the main thread.

My question is: Has anyone come up with a good way to get properties of UI elements from background threads or, alternatively, good ways to avoid doing so in the first place?

I threw this thing together and it does the trick, but it doesn't feel quite right:

@interface SelectorMap : NSObject

@property (nonatomic, strong) NSArray *selectors;
@property (nonatomic, strong) NSArray *results;

@end


@interface NSObject (Extensions)
- (NSArray *)getValuesFromMainThreadWithSelectors:(SEL)selector, ...;
- (void)performSelectorMap:(SelectorMap *)map;
@end

And the implementation:

#import "NSObject+Extensions.h"

@implementation SelectorMap
@synthesize selectors;
@synthesize results;
@end

@implementation NSObject (Extensions)

- (void)performSelectorMap:(SelectorMap *)map
{
    NSMutableArray *results = [NSMutableArray arrayWithCapacity:map.selectors.count];

    for (NSString *selectorName in map.selectors)
    {
        SEL selector = NSSelectorFromString(selectorName);
        id result = [self performSelector:selector withObject:nil];
        [results addObject:result];
    }

    map.results = results.copy;
}

- (NSArray *)getValuesFromMainThreadWithSelectors:(SEL)selector, ...
{
    NSMutableArray *selectorParms = [NSMutableArray new];

    va_list selectors;
    va_start(selectors, selector);

    for (SEL selectorName = selector; selectorName; selectorName = va_arg(selectors, SEL))
        [selectorParms addObject:NSStringFromSelector(selectorName)];

    va_end(selectors);

    SelectorMap *map = [SelectorMap new];
    map.selectors = selectorParms.copy;

    [self performSelectorOnMainThread:@selector(performSelectorMap:) withObject:map waitUntilDone:YES];

    return map.results;
}

@end

I call it like this:

NSArray *textViewProperties = [textView getValuesFromMainThreadWithSelectors:@selector(text), @selector(font), nil];

Getting the font doesn't give the same warning that getting the text does, but I figured it'd be best to be consistent.

Upvotes: 5

Views: 3559

Answers (2)

bryanmac
bryanmac

Reputation: 39296

You can either use performSelector on main thread or you can use GCD to dispatch on main queue.

[self performSelectorOnMainThread:@selector(updateText:) withObject:nil waitUntilDone:YES];

GCD would look like:

dispatch_queue_t main = dispatch_get_main_queue();

dispatch_sync(main, ^{
  // read and assign here
});

Here's a related posts on the subject:

GCD, Threads, Program Flow and UI Updating

Upvotes: 2

bbum
bbum

Reputation: 162712

I avoid any kind of a meta-programming as much as possible. It completely undermines the compilers ability to double-check your code, for starts, and it tends to be unreadable.

 __block NSString* foo;
 dispatch_sync(dispatch_get_main_queue(), ^{
      foo = [textField ...];
  });

Note if not using ARC, you'll likely want to copy or retain the string in the block and then release or autorelease in your local thread.

Upvotes: 17

Related Questions