Show Menu

Do you have a job opening?

Submit your 30 day Job Listing for FREE

iOS development tutorial

Because Objective-C categories don’t support @synthesize, you have probably written something like this:


@interface NSObject (AwesomeUtils)
 
@property (nonatomic, strong) NSString *someInfo;
 
@end
 
@implementation NSObject (AwesomeUtils)
 
static const void *SomeInfoKey = &SomeInfoKey;
 
- (NSString *)someInfo
{
    objc_getAssociatedObject(self, SomeInfoKey);
}
 
- (void)setSomeInfo:(NSString *)someInfo
{
    objc_setAssociatedObject(self, SomeInfoKey, someInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

5 lines of code just to replace @synthesize? Our code will be cluttered if we have more than 2 of these properties. There are a lot of open source solutions out there to save our fingers from hurt, both compile-time or runtime, but we can still do better:

  • We want support for value types (int, struct, etc.) too. Basically we want to be able to handle all types supported by ordinary properties. All in a single macro.
  • We want to be able to set the ownership (assign, strong, retain, copy, unsafe_unretained)
  • While we’re at it, let’s support weak too.
  • If possible, we want to have a compiler error on invalid ownership (e.g. setting strong on a CGRect)

Our macro signature will be JESynthesize(ownership, type, getter, setter) and can be used this way:


@implementation NSObject (AwesomeUtils)
 
JESynthesize(assign, NSInteger, index, setIndex);
JESynthesize(strong, NSString *, name, setName);
JESynthesize(copy, void(^)(void), completion, setCompletion);
JESynthesize(unsafe_unretained, id, unsafeObject, setUnsafeObject);
JESynthesize(weak, id, delegate, setDelegate);
JESynthesize(strong, NSString *, readonlyID, changeReadonlyID);
 
// …
@end

For simplicity’s sake, let’s require ARC. Because we want compile errors on invalid usage, we’ll focus on implementing compile-time getter and setter methods. Doing so also let’s us use JESynthesize(…) even if we never declared a property.

Note that my solution will drop the concept of atomic implementations. I find atomic useless even in thread-safe code. And I’m pretty sure many will agree. So we’ll Keep It Simple, Stupid™ and let our macro stick to nonatomic implicitly.

Value Types and assign Ownership

Recall our macro signature: JESynthesize(ownership, type, getter, setter). We’ll get our hands dirty and use the arguments right away. First, let’s figure out what our macro should output when we call:


JESynthesize(assign, CGRect, someRect, setSomeRect);

Since objc_setAssociatedObject(…) only accepts id objects, we have to wrap scalars and structs in NSValue.


static const void *_JESynthesizeKey_##getter = &_JESynthesizeKey_##getter;
 
- (type)getter
{
    type _je_value;
    [objc_getAssociatedObject(self, _JESynthesizeKey_##getter) getValue:&_je_value];
    return _je_value;
}
 
- (void)setter:(type)getter
{
    objc_setAssociatedObject(self,
        _JESynthesizeKey_##getter,
        [[NSValue alloc] initWithBytes:&getter objCType:@encode(type)],
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

Hmm, that was quick. But wait, what if objc_getAssociatedObject(…) returns nil? _je_value remains a garbage value as it’s returned from our accessor. The zero initializer syntax


type _je_value = {0};

will work, but only because our type is a struct. The compiler will not be happy if we use that on scalars like NSInteger, CGFloat, etc. We can use memset(…), but I prefer to use C++’s empty initializer syntax instead.


- (type)getter
{
    type _je_value[1] = {};
    [objc_getAssociatedObject(self, _JESynthesizeKey_##getter) getValue:_je_value];
    return _je_value[0];
}

Now we have a nice default-initialized value. Note that because our variable is now an array, we removed the ampersand on _je_value at line 6.

Let’s check if our implementation for assign is done. We already handled value types, but what about id object types? You probably want unsafe_unretained instead, but Objective-C does allow assign on id types.


JESynthesize(assign, NSString *, someString, setSomeString);

is OK as is, because NSValue will just treat the NSString pointer as a value.

What about blocks? (Again, you really shouldn’t be doing this. But we’ll support this only for consistency)


JESynthesize(assign, void(^)(void), someBlock, setSomeBlock);

Now we get errors. That’s because our accessor expands to


- (void(^)(void))someBlock
{
    void(^)(void) _je_value[1] = {};
    [objc_getAssociatedObject(self, _JESynthesizeKey_someBlock) getValue:_je_value];
    return _je_value[0];
}

where the highlighted line should be void(^_je_value)(void). We won’t have this problem if our block type was typedef‘ed, so we fix this by “inlining” the typedef with a typeof(…):


- (type)getter
{
    typeof(type) _je_value[1] = {};
    [objc_getAssociatedObject(self, _JESynthesizeKey_##getter) getValue:_je_value];
    return _je_value[0];
}

And our error goes away.

Object Types with weak Ownership

Because objc_setAssociatedObject(…) doesn’t support zeroing-weak objects, we’ll have to improvise. For assign, we used NSValue to hold our value for us. We’ll do the same for weak but we have to create our own wrapping object:

_JEAssociatedObjectsWeakWrapper.h


#import 
 
@interface _JEAssociatedObjectsWeakWrapper : NSObject
 
@property (nonatomic, weak, readonly) id weakObject;
 
- (id)initWithWeakObject:(id)weakObject;
 
@end

_JEAssociatedObjectsWeakWrapper.m


#import "_JEAssociatedObjectsWeakWrapper.h"
 
@implementation _JEAssociatedObjectsWeakWrapper
 
- (id)initWithWeakObject:(id)weakObject
{
    self = [super init];
    if (!self)
    {
        return nil;
    }
 
    _weakObject = weakObject;
    return self;
}
@end

A very simple class that just holds a single weak property. We’ll treat this class as immutable and create a new one every time we call the mutator method.


static const void *_JESynthesizeKey_##getter = &_JESynthesizeKey_##getter;
 
- (type)getter
{
    return [objc_getAssociatedObject(self, _JESynthesizeKey_##getter) weakObject];
}
 
- (void)setter:(type)getter
{
    objc_setAssociatedObject(self,
        _JESynthesizeKey_##getter,
        [[_JEAssociatedObjectsWeakWrapper alloc] initWithWeakObject:getter],
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

Simple enough.

Object Types with strong/retain, copy, and unsafe_unretained Ownerships

Objects are simpler because we can access and mutate them directly:


static const void *_JESynthesizeKey_##getter = &_JESynthesizeKey_##getter;
 
- (type)getter
{
    return objc_getAssociatedObject(self, _JESynthesizeKey_##getter);
}
 
- (void)setter:(type)getter
{
    objc_setAssociatedObject(self,
        _JESynthesizeKey_##getter,
        getter,
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

We only need to adjust the highlighted line:

strong/retain: OBJC_ASSOCIATION_RETAIN_NONATOMIC
copy: OBJC_ASSOCIATION_COPY_NONATOMIC
unsafe_unretained: OBJC_ASSOCIATION_ASSIGN

Putting It Together

We currently have 5 implementations for each of our accessor and mutator depending on ownership. Luckily, macros let us paste tokens:


#define JESynthesize(ownership, type, getter, setter) \
    static const void *_JESynthesizeKey_##getter = &_JESynthesizeKey_##getter; \
    - (type)getter \
    { \
        return _JESynthesize_get_##ownership(type, getter); \
    } \
    - (void)setter:(type)getter \
    { \
        _JESynthesize_set_##ownership(type, getter); \
    }

The _JESynthesize_get_##ownership and _JESynthesize_set_##ownership let us branch out to

_JESynthesize_get_assign and _JESynthesize_set_assign
_JESynthesize_get_strong and _JESynthesize_set_strong
_JESynthesize_get_retain and _JESynthesize_set_retain
_JESynthesize_get_copy and _JESynthesize_set_copy
_JESynthesize_get_unsafe_unretained and _JESynthesize_set_unsafe_unretained
_JESynthesize_get_weak and _JESynthesize_set_weak

depending on what we pass in to the ownership argument. We can then put our separate implementations to their corresponding #define‘s.

You can view our complete macro here:


#define JESynthesize(ownership, type, getter, setter) \
    static const void *_JESynthesizeKey_##getter = &_JESynthesizeKey_##getter; \
    - (type)getter \
    { \
        return _JESynthesize_get_##ownership(type, getter); \
    } \
    - (void)setter:(type)getter \
    { \
        _JESynthesize_set_##ownership(type, getter); \
    }
 
#define _JESynthesize_get_assign(type, getter) \
    ({ \
        typeof(type) _je_value[1] = {}; \
        [(NSValue *)objc_getAssociatedObject(self, _JESynthesizeKey_##getter) getValue:_je_value]; \
        _je_value[0]; \
    })
 
#define _JESynthesize_get_unsafe_unretained(type, getter) \
    objc_getAssociatedObject(self, _JESynthesizeKey_##getter);
 
#define _JESynthesize_get_strong    _JESynthesize_get_unsafe_unretained
 
#define _JESynthesize_get_retain    _JESynthesize_get_unsafe_unretained
 
#define _JESynthesize_get_copy      _JESynthesize_get_unsafe_unretained
 
#define _JESynthesize_get_weak(type, getter) \
    ((_JEAssociatedObjectsWeakWrapper *)objc_getAssociatedObject(self, _JESynthesizeKey_##getter)).weakObject
 
#define _JESynthesize_set_assign(type, getter) \
    objc_setAssociatedObject(self, \
        _JESynthesizeKey_##getter, \
        [[NSValue alloc] initWithBytes:&getter objCType:@encode(type)], \
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 
#define _JESynthesize_set_unsafe_unretained(type, getter) \
    objc_setAssociatedObject(self, \
        _JESynthesizeKey_##getter, \
        getter, \
        OBJC_ASSOCIATION_ASSIGN);
 
#define _JESynthesize_set_strong(type, getter) \
    objc_setAssociatedObject(self, \
        _JESynthesizeKey_##getter, \
        getter, \
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 
#define _JESynthesize_set_retain _JESynthesize_set_strong
 
#define _JESynthesize_set_copy(type, getter) \
    objc_setAssociatedObject(self, \
        _JESynthesizeKey_##getter, \
        getter, \
        OBJC_ASSOCIATION_COPY_NONATOMIC);
 
#define _JESynthesize_set_weak(type, getter) \
    objc_setAssociatedObject(self, \
        _JESynthesizeKey_##getter, \
        [[_JEAssociatedObjectsWeakWrapper alloc] initWithWeakObject:getter], \
        OBJC_ASSOCIATION_RETAIN_NONATOMIC);

having issues?

We have a Questions and Answer section where you can ask your iOS Development questions to thousands of iOS Developers.

Ask Question

FREE Download!

Get your FREE Swift 2 Cheat Sheet and quick reference guide PDF download when you sign up to SwiftMonthly


Sharing is caring

If you enjoyed this tutorial, please help us and others by sharing using one of the social media buttons below.


Written by:

I’m an iOS developer currently stationed in Tokyo, where we make cool and shiny apps. It is my principle as a software engineer that the systems I design and code I write is as elegant, robust, and functional as my sanity can handle. I try to make my posts helpful and relevant to the Cocoa dev community

Comments

comments