How to detect if a property is an IBOutlet programmatically at runtime?

460 views Asked by At

I am setting up unit tests on my project to make sure that all UIViewController IBOutlets are connected to their respective Xib objects (i.e., are not nil after viewDidLoad.) I was considering applying a protocol to these UIViewControllers with a required function "getAllOutletNames", like so:

-(NSArray*)getAllOutletNames
{
    return @[ @"outletproperty1", @"outletProperty2", ...];
}

...and then using [viewController valueForKey:outletName] to make sure none of these are nil. The problem with this is that it's a little unwieldy; "getAllOutletNames" has to be updated for each outlet added to the xib, which could easily be overlooked. I would prefer to do this in programmatic fashion, so that all IBOutlet properties can be automatically detected and iterated over.

I read in this NSHipster article (cmd+f for "attribute-backed attributes") that an attribute is applied to IBOutlets (or, an "attribute-backed attribute", which I don't quite understand.)

It looks like I can get a list of all properties in a class using part of this answer, and I can get their attributes using part of this answer. But, printing out the attributes from an IBOutlet versus a non-IBOutlet property using the following code, I don't find any difference:

const char * type = property_getAttributes(class_getProperty([self class], [outletName UTF8String]));
NSString * typeString = [NSString stringWithUTF8String:type];
NSArray * attributes = [typeString componentsSeparatedByString:@","];
NSLog(@"%@",attributes);

IBOutlet

(
    "T@\"UILabel\"",
    "&",
    N,
    "V_titleLabel"
)

Non-IBOutlet

(
    "T@\"UIView\"",
    "&",
    N,
    "V_programmaticallySetupView"
)

Is there any way to access this "attribute-backed attribute" that the NSHipster article had mentioned or otherwise determine if a property is an IBOutlet programmatically, or am I barking up the wrong tree here?

1

There are 1 answers

0
Richard J. Ross III On BEST ANSWER

Here's a trick I've used before to mark methods as special, it can be adopted to properties as well:

#import <objc/runtime.h>

#define synthesize_nooutlet(name) \
synthesize name=_ ## name;        \
- (oneway id)name {               \
  return _ ## name;               \
}

@interface ViewController ()

@property (nonatomic, strong) UIView *theView;

@end

@implementation ViewController

@synthesize_nooutlet(theView);

- (void) viewDidLoad {

  [super viewDidLoad];

  [self.theView setBackgroundColor:[UIColor clearColor]];

  char returnType;
  method_getReturnType(class_getInstanceMethod([self class], @selector(theView)), &returnType, 1);
  if (returnType == 'V') {
    // Do stuff
  }
}

@end

This works because of the distributed objects type encodings, documented here:

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-BABHAIFA

You can use any of these to denote methods however you'd like, and as distributed objects don't exist on iOS, the compiler & runtime just ignore them.