✂️ Objective-C

Small information nuggets and recipies about Objective-C


(most recent on top)

Don’t refer to your own class by name

self                              // From a class method
[self class]                      // From an instance method

Method -description is normally used as an internal text representation

-(NSString *)description
{
  return [NSString stringWithFormat:@"<GameObject: %@, Position: %f, %f>",
                 [self objectID], [self position].x, [self position].y];
}

Enable test feature from a command line argument

… this is useful for debugging development builds with the command line argument

-TestFeatureEnabled YES
[[NSUserDefaults standardUserDefaults] boolForKey:@"TestFeatureEnabled"]

Default values with GNU-style ternary operator

NSLog(@"%@", @"a" ?: @"b");       // => @"a"
NSLog(@"%@", nil ?: @"b");        // => @"b"

Manipulating collections using blocks

Declaring instance variables (ivars)

@interface ClassName : NSObject {
    int ivar1;  // @protected by default; could use @private (legacy) or @public (bad practice)
}
@end
@implementation ClassName () {
    int ivar2;  // @private by default; can change to @protected
}
@end
@implementation ClassName {
    int ivar3;  // @private by definition; other visibility keywords are ignored
}
@end

Assertions & Release builds

NSAssert() is disabled if the preprocessor macro
NS_BLOCK_ASSERTIONS is defined for Release builds (defaults change per Xcode version)

Naming private & addition methods to avoid naming conflicts

- (void)pfx_myMethodName;

Check if running under unit tests

BOOL runningTests = NSClassFromString(@"XCTest") != nil;

Annotate intentional fall-through between switch labels

[[clang::fallthrough]]

Using meta information in NSLog

NSLog(@"%d", ...)
__LINE__             // Current line number in the source code file
NSLog(@"%s", ...)
__func__             // Current function signature
__PRETTY_FUNCTION__  // Like __func__, but includes verbose type information in C++ code
__FILE__             // Full path to the source code file
NSLog(@"%@", ...)
NSStringFromSelector(_cmd)      // Name of the current selector
NSStringFromClass([self class]) // Name of the current object's class
[NSThread callStackSymbols]     // NSArray of the current stack trace as programmer-readable strings
[[NSString stringWithUTF8String:__FILE__] lastPathComponent] // Name of the source code file

Return values from code blocks

self.bounds = ({
	CGRect bounds = self.bounds;
	bounds.size.height = self.currentYPosition + SHEETINSETY;
	bounds;
});
const BOOL anyValueIsXyz = (^{
  for (QwertyValue *value in values) {
    if (value.isXyz) {
      return YES;
    }
  }
  return NO;
}());

Define enums and bitmasks

typedef NS_ENUM(NSInteger, PFXEnumName) {
    PFXEnumNameValueA,
    PFXEnumNameValueB
};

typedef NS_OPTIONS(NSUInteger, PFXEnumName) {
    PFXEnumNameValueA = 0,
    PFXEnumNameValueB = 1 << 0,
    PFXEnumNameValueC = 1 << 1,
    PFXEnumNameValueD = 1 << 2
};

… usage

PFXEnumNameValueA | PFXEnumNameValueC

Boxed enums: converting a standard enum to an integer type, and boxing the value accordingly.

@(SomeStandardEnumValue)          // boxing
[boxedNum nativeTypeValue]        // unboxing

Iterate through an enum…

… fragile pattern; assumes too much: incremental values only, no gaps numeric order, count must be the last element, etc. ⚠️

typedef NS_ENUM(NSInteger, PFXEnumName) {
    PFXEnumNameValueA,
    PFXEnumNameValueB,
    PFX_ENUM_NAME_COUNT
};

for (int i = 0; i < PFX_ENUM_NAME_COUNT; i++) {
    NSLog(@"%@", (i == PFXEnumNameValueA)? @"got the element" : @"nope, not yet");
}

Random number generator

// random int between [0..upper_bound[
arc4random_uniform(upper_bound);

Constants declaration

… within the same translation unit (i.e. current source being compiled + all the included headers)

// file.m
static NSString * const PFXExampleConstantA = @"value";
static const int PFXExampleConstantB = 1;

… globally available to all

// file.h
extern NSString * const PFXExampleConstantA;
// file.m
NSString * const PFXExampleConstantA = @"value";
// file.h
extern const struct PFXExampleConstantsStruct
{
    NSString *constantA;
    int constantB;
    struct
    {
        NSString *constantC;
        NSString *constantD;
    } scope;
} PFXExampleConstants;
// file.m
const struct PFXExampleConstantsStruct PFXExampleConstants = {
    .constantA = @"value",
    .constantB = 1,
    .scope = {
        .constantC = @"valueC",
        .constantD = @"valueD"
    }
};
// usage
(void)PFXExampleConstants.scope.constantC

Singletons with a thread-safe pattern

… using Grand Central Dispatch

+ (instancetype)shared<#ClassName#> {
   static id sharedInstance = nil;
   static dispatch_once_t token;
   dispatch_once(&token, ^{
      sharedInstance = [[self alloc] init];
   });
   return sharedInstance;
}

Custom warning and error directives

#warning pay attention to this
#error continue here…

Suppress specific warnings in code

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wwarning-type"
#pragma clang diagnostic ignored "-Wanother-warning"
// …
#pragma clang diagnostic pop

Temporarily silence unused variables warning

__unused int someVar;
(void)someVar;
#pragma unused (someVar, anotherVar)

Xcode custom build phases

… TODO & FIXME comments as warnings

KEYWORDS="TODO:|FIXME:|\?\?\?:|\!\!\!:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | perl -p -e "s/($KEYWORDS)/ warning: \$1/"

… show test coverage report

open -g -a Xcoverage.app "$OBJECT_FILE_DIR-normal/$CURRENT_ARCH" >/dev/null 2>&1 || true