Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 375
D. Patrick Avatar asked Jan 13 '13 23:01

D. Patrick


2 Answers

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.

like image 56
bbum Avatar answered Sep 20 '22 06:09

bbum


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

like image 41
bryanmac Avatar answered Sep 21 '22 06:09

bryanmac