Objective-C Class Properties

With all the excitement about the new Swift 3 language features it is easy to overlook some of the small improvements still happening to Objective-C. It may be the case that any love Apple is showing Objective-C is to improve Swift interoperability but it is still welcome for developers needing to get work done in Objective-C .

In this post we look at the addition of class properties to Objective-C.

Objective-C Class Properties

From the Xcode 8 release notes:

Objective-C now supports class properties, which interoperate with Swift type properties. They are declared as: @property (class) NSString *someStringProperty;. They are never synthesized. (23891898)

To experiment let’s create a simple Objective-C class with some class properties. This is what the interface for our User class looks like:

@interface User : NSObject
@property (class, nonatomic, assign, readonly) NSInteger userCount;
@property (class, nonatomic, copy) NSUUID *identifier;
+ (void)resetIdentifier;
@end

For illustration purposes I have two properties, the first is a read-only integer and the second is a read-write NSUUID with copy semantics. Note the class in the property declarations.

The implementation is simple, we first need some storage for the identifier and userCount class properties. Since these are class level and not instance variables we declare them as static:

@implementation User
static NSUUID *_identifier = nil;
static NSInteger _userCount = 0;

Now we need to create the getter and setter methods for the two properties. As mentioned in the release notes these are never synthesised for you so Xcode will warn you if they are missing. First the read-only userCount needs a getter that just returns the value of the count. Note the + to make our getter a class method:

+ (NSInteger)userCount {
  return _userCount;
}

The identifier property needs both a getter and a setter. We create an identifier in the getter if we do not already have one:

+ (NSUUID *)identifier {
  if (_identifier == nil) {
    _identifier = [[NSUUID alloc] init];
  }
  return _identifier;
}

+ (void)setIdentifier:(NSUUID *)newIdentifier {
  if (newIdentifier != _identifier) {
    _identifier = [newIdentifier copy];
  }
}

We will also create a basic initializer that updates the count property:

- (instancetype)init
{
  self = [super init];
  if (self) {
    _userCount += 1;
  }
  return self;
}

The resetIdentifier class method is a convenience method that creates a new identifier:

+ (void)resetIdentifier {
  _identifier = [[NSUUID alloc] init];
}

@end

You access the class properties using normal dot syntax on the class name:

User.userCount;
User.identifier;

An example of this class in use:

for (int i = 0; i < 3; i++) {
    self.user = [[User alloc] init];
    NSLog(@"User count: %ld",(long)User.userCount);
    NSLog(@"Identifier = %@",User.identifier);
}

[User resetIdentifier];    
NSLog(@"Identifier = %@",User.identifier);

This produces the output:

// User count: 1
// Identifier = 4B98B7FD-F8DC-484A-92B2-B2BB20BCB709
// User count: 2
// Identifier = 4B98B7FD-F8DC-484A-92B2-B2BB20BCB709
// User count: 3
// Identifier = 4B98B7FD-F8DC-484A-92B2-B2BB20BCB709
// Identifier = A0519681-1E08-4DF2-B2D1-D077CF2BDEFF

Note that since this is a feature of the LLVM compiler in Xcode 8 it will work with deployment targets earlier than iOS 10.

Generated Swift interface

It seems that the only enhancements that Objective-C gets these days are to improve interoperability with Swift. The addition of class properties to Objective-C maps to the use of class variables in Swift. Here is the generated Swift interface for our User class:

public class User : NSObject { 
  public class var userCount: Int { get }
  public class var identifier: UUID!   
  public class func resetIdentifier()
}

Note that our identifier property is an implicitly unwrapped optional meaning that we do not ever expect it to be nil. To allow it be nil we would need to add a nullable annotation to the Objective-C property declaration. Our Swift variable would then be an optional. See Using nullable to annotate Objective-C for more details.

Further Reading