Create a gist now

Instantly share code, notes, and snippets.

Greetings, NSHipsters!

As we prepare to increment our NSDateComponents -year by 1, it's time once again for NSHipster end-of-the-year Reader Submissions! Last year, we got some mind-blowing tips and tricks. With the release of iOS 7 & Mavericks, and a year's worth of new developments in the Objective-C ecosystem, there should be a ton of new stuff to write up for this year.

Submit your favorite piece of Objective-C trivia, framework arcana, hidden Xcode feature, or anything else you think is cool, and you could have it featured in the year-end blowout article. Just comment on this gist below!

Here are a few examples of the kind of things I'd like to see:

Can't wait to see what y'all come up with!

@mindbrix

My favourite trick is to use PDF app assets in place of bitmaps. One asset, any size. A huge time saver, especially when it comes to resizing. Oh, and PDFs are previewed full-screen in Xcode. ;-)

The category below handles all the PDF rendering details for you - and caches the results for speed.

https://github.com/mindbrix/UIImage-PDF

@jpm
jpm commented Nov 11, 2013

I think the Thoughtbot article on handling large amounts of data on a MKMapView should be listed here. Insane levels of hackery to make that component be extremely fast and responsive:
http://robots.thoughtbot.com/how-to-handle-large-amounts-of-data-on-maps

@stevemoser

Tiny tip: Not only is firstObject public now but also it is better than objectAtIndex:0 because it is safe to use on empty arrays like lastObject.

@ericallam

NSAttributedString can do HTML now in IOS 7 with the new NSHTMLTextDocumentType document type attribute, which makes it really easy now to render markdown in a UITextView.

@mteece
mteece commented Nov 11, 2013

Houston. Part of Nomad CLI (http://nomad-cli.com/). Made testing push notifications so much easier. If you're doing APNS it is a must have.

@shpakovski

While developing an app, you may need a functionality that is rarely used and is needed only for development purposes. To avoid commenting “temporary” code and changing project sources you can use the Arguments panel in Xcode.

First, wrap that functionality into the following code:

#ifdef DEBUG
NSDictionary *environment = [NSProcessInfo processInfo].environment;
NSString *testFeatureEnabled = environment[@"TEST_FEATURE_ENABLED"];
if ([testFeatureEnabled isEqualToString:@"YES"]) {
    //
    // Do something useful
    //
}
#endif

Next, add the variable TEST_FEATURE_ENABLED into the panel Xcode ▹ Product ▹ Scheme ▹ Edit Scheme… ▹ Arguments and set its value to YES when you need the optional functionality.

You can use command line arguments as well. For instance, add the argument -TestFeatureEnabled YES and then check it in code as [[NSUserDefaults standardUserDefaults] boolForKey:@"TestFeatureEnabled"].

@Thomvis
Thomvis commented Nov 11, 2013

This is my least favourite piece of Objective-C trivia:

In iOS7, the animation of the keyboard show/hide changed. Its duration and, most notably, its curve is different from previous iOS versions. As before, if you want to animate something with the keyboard, you can listen to UIKeyboardWillShowNotification/UIKeyboardWillHideNotification and use the values from the userInfo dictionary to coordinate your animations with the keyboard. The userInfo dictionary contains the keyboard's begin frame, end frame, animation curve and animation duration.

In iOS 7, the returned animation curve however is an undefined value, meaning that it is not one of the 4 defined values of UIViewAnimationCurve. Instead, its value is 7. This is a problem if you want to use the same curve in your own animation, because such a curve is not defined. The work-around, as discussed on the Apple forums (https://devforums.apple.com/message/878410#878410) is to manually translate the UIViewAnimationCurve to a UIViewAnimationOptions value. From the definition of UIViewAnimationOptions, we learn that this translation is done by shifting the curve value 16 times to the left: option = curve << 16. This works great, but of course shouldn't be necessary. I hope Apple will add this mysterious 5th curve to the definitions in a future iOS update.

@shpakovski

Xcode 5 finally brought native support for unit testing.

However, for some applications, running with a full-featured user interface is not needed, because you test data models in the first place. Here is a trick to skip view controller creation when you “test” the app:

#ifdef DEBUG
/// Tests if .xctest bundle is loaded, so returns YES if the app is running with XCTest framework.
inline BOOL IsUnitTesting() __attribute__((const));
inline BOOL IsUnitTesting()
{
    NSDictionary *environment = [NSProcessInfo processInfo].environment;
    NSString *injectBundlePath = environment[@"XCInjectBundle"];
    return [injectBundlePath.pathExtension isEqualToString:@"xctest"];
}
#endif
- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(id)options
{
#ifdef DEBUG
    if (IsUnitTesting()) return YES;
#endif

    //
    // Create UIWindow and assign rootViewController…
    //
    return YES;
}
@MaxGabriel

First introduced to me by issue 105 of iOS Dev Weekly/monkeydom, I'm finding statement expressions to be really nice for cleaning up initialization logic. Here I contain an intermediate launchString variable:

// Pulls a URL Scheme out of a dictionary, making sure to format it correctly. 
NSURL *const launchURL = ({
    NSString *launchString = [dict objectForKey:@"launch_uri" ofClass:[NSString class]];
    if ([launchString rangeOfString:@"://"].location == NSNotFound) {
        launchString = [launchString stringByAppendingString:@"://"];
    }
    [NSURL URLWithString:launchString];
});

This also makes the surrounding code easier to read, since the launchURL formatting is tucked away in the statement expression, rather than being at the same indentation as the core logic of the loop.

// Build up a list of installed games, given their launch URLs
NSMutableArray *installedGames = [@[] mutableCopy];
for (NSDictionary *game in response) {
    NSNumber *const gameID = [game objectForKey:@"game_id" ofClass:[NSNumber class]];

    NSURL *const launchURL = ({
        NSString *launchString = [game objectForKey:@"launch_uri" ofClass:[NSString class]];
        if ([launchString rangeOfString:@"://"].location == NSNotFound) {
            launchString = [launchString stringByAppendingString:@"://"];
        }
        [NSURL URLWithString:launchString];
    });

    if (gameID == nil || launchURL == nil) {
        continue;
    }

    if ([[UIApplication sharedApplication] canOpenURL:launchURL]) {
        [installedGames addObject:gameID];
    }
}
@shpakovski

If your project is built with LLVM 5.0, feel free to clear the list Xcode ▹ Project ▹ TARGETS ▹ General ▹ Linked Frameworks and Libraries. The following lines in Prefix.pch replace the standard setup:

@import UIKit;
@import Foundation;
@import CoreGraphics;

The same @import one-liners can be used for any system framework if you do not like to touch Xcode project settings.

@shpakovski

It is recommended to care about i18n from the very first lines in your project. I find it extremely useful to isolate all localized strings into separate source files. This way, you can manage all external texts in a single location and avoid duplicates in the .strings file:

// LocalizedStrings.h

//
extern inline NSString *StandardCancelButtonTitle();
//
// LocalizedStrings.m

//
inline NSString *StandardCancelButtonTitle()
{
    return NSLocalizedString(@"Cancel",
                             @"The title for Cancel button on alert views");
}
//

Now if you can insert StandardCancelButtonTitle() into any source file after adding the directive #import "LocalizedStrings.h" to the Prefix.pch.

@MaxGabriel

Don't even know if this is documented behavior, but this syntax is really great for if a then a else b:

NSString *a = @"a";
NSString *b = @"b";

NSString *result = a ?: b; // a
a = nil;
NSString *result2 = a ?: b; // b
@dbgrandi

I recently subclassed UIApplication openURL to add analytics (Google in my case) for opening external URLs from within an app.

-(BOOL)openURL:(NSURL *)url{
    if ([[url scheme] hasPrefix:@"http"]) {
        [[GAI sharedInstance].defaultTracker sendView:[url absoluteString]];
    }
    return [super openURL:url];
}

UIApplication is common to all apps. I'd love to see tips on why/when people extend UIApplication.

@ellneal
ellneal commented Nov 11, 2013

I recently came up with a trick to provide more configuration options to beta testers: having multiple settings bundles for different build configurations.

To setup, create 2 settings bundles and create an extra build configuration for App Store Release.

Settings-advanced.bundle
Settings-basic.bundle

Then add a user defined build setting SETTINGS_BUNDLE as follows:

Debug                ${PROJECT_DIR}/${PROJECT_NAME}/Settings-advanced.bundle
Release              ${PROJECT_DIR}/${PROJECT_NAME}/Settings-advanced.bundle
App Store Release    ${PROJECT_DIR}/${PROJECT_NAME}/Settings-basic.bundle

You will need to ensure that neither of the settings bundles are included in the Copy Bundle Resources build phase, and then add 2 extra Run Script build phases:

# Force Codesign
touch "${PROJECT_DIR}/${PROJECT_NAME}/main.m"

# Copy Settings Bundle
cp -r "${SETTINGS_BUNDLE}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.bundle"

The Force Codesign script is necessary because modifying one of the settings bundles (and nothing else) and then recompiling will fail with a codesign error; this was the best workaround I could find at the time.

@incanus
incanus commented Nov 11, 2013

Relatively little-known feature of iOS 7: you can turn off Apple's own maps now with MKTileOverlay, MKTileOverlayRenderer, and canReplaceMapContent, unlike you could before with Apple or (pre-iOS 6) Google. And check out MBXMapKit if you'd like to do it in one line of code.

@PycKamil

If for some reason you wish to use iOS7 SDK but still like to have iOS 6 look and feel there is non documented flag that make it possible:

[[NSUserDefaults standardUserDefaults] setObject:@YES forKey:@"UIUseLegacyUI"]
@ethanjdiamond

A useful tip I've found to speed up development is to use a breakpoint and LLDB's "expr" command to change primitives without having to recompile your application. Say for example, you're playing with the speed of very simple animation:

CGFloat animationDuration = 0.3f;
[UIView animationWithDuration:animationDuration animations:^{
    self.fooView.transform = CGAffineTransformMakeTranslation(10.0f, 0.0f);
}];

Instead of changing animationDuration and recompiling, you can use breakpoints to change them during runtime like so:

  1. Add a breakpoint after CGFloat animationDuration = 0.3f;
  2. Right click the breakpoint and select "Edit"
  3. Check Automatically continue after evaluating.
  4. Click Add Action and under Debugger Command write expr animationDuration = 0.5f

Alternatively you can just retype expr animationDuration = 0.5f into the console every time the code breaks.

Just tweak that value and rerun the animation and viola, no more need to compile.

@0xced
0xced commented Nov 11, 2013

An xml plist with all emoji grouped by category. Might be useful. (Extracted with private APIs on the iPhone simulator.)

@0xced
0xced commented Nov 11, 2013

You can set the OBJC_PRINT_REPLACED_METHODS environment variable to YES to automatically log all methods that are smashed by categories. You know, it helps diagnosing what’s going wrong when libraries forget to prefix or suffix methods in categories. 😉

@NSProgrammer

I am a big fan of buffering my table views. It's such a big experience improvement and so easy to accomplish too. It's mentioned in this blog post: http://www.nsprogrammer.com/2013/10/easy-uitableview-buffering.html under part two - but ultimately you just make your table view larger and take advantage of contentInset and scrollViewInsets.

There's a demo of the improvement too at http://www.github.com/NSProgrammer/NSProgrammer under
https://github.com/NSProgrammer/NSProgrammer/tree/master/code/Examples/TableViewOptimizations

@NSProgrammer

One trick I like to use for classes that I want to behave as abstract is to use a macro to enforce that particular methods need to be overridden.

/**
    @def NS_UNRECOGNIZED_SELECTOR
    An easy to use macro to replace boylerplate code for implementing a method that wants to be overridden so it throws an \c NSInvalidArgumentException.
    @par Example:
    @code
    - (void) methodToOverride NS_UNRECOGNIZED_SELECTOR; // ';' is optional but can help with auto code alignement
    @endcode
 */

#define NS_UNRECOGNIZED_SELECTOR { ThrowUnrecognizedSelector(self, _cmd); }

NS_INLINE void ThrowUnrecognizedSelector(NSObject* obj, SEL cmd) __attribute__((noreturn));
NS_INLINE void ThrowUnrecognizedSelector(NSObject* obj, SEL cmd)
{
    [obj doesNotRecognizeSelector:cmd];
    abort(); // will never be reached, but prevents compiler warning
}

Simple use:

- (void) methodNeedingSubclassOverride NS_UNRECOGNIZED_SELECTOR;

Part of http://www.github.com/NSProgrammer/NSProgrammer

@NSProgrammer

Not so much an Objective-C trick, I use the following C macros for floating point comparison since comparing floating point values can be very hairy:

/**
    @section Floating Point Comparison Macros
 */
#define FLOAT_EQ(floatA, floatB, epsilon)  (fabsf(floatA - floatB) < epsilon)
#define FLOAT_LT(floatA, floatB, epsilon)  (!FLOAT_EQ(floatA, floatB, epsilon) && floatA < floatB)
#define FLOAT_GT(floatA, floatB, epsilon)  (!FLOAT_EQ(floatA, floatB, epsilon) && floatA > floatB)
#define FLOAT_LTE(floatA, floatB, epsilon) (FLOAT_EQ(floatA, floatB, epsilon) || floatA < floatB)
#define FLOAT_GTE(floatA, floatB, epsilon) (FLOAT_EQ(floatA, floatB, epsilon) || floatA > floatB)

#define DOUBLE_EQ(floatA, floatB, epsilon)  (fabs(floatA - floatB) < epsilon)
#define DOUBLE_LT(floatA, floatB, epsilon)  (!FLOAT_EQ(floatA, floatB, epsilon) && floatA < floatB)
#define DOUBLE_GT(floatA, floatB, epsilon)  (!FLOAT_EQ(floatA, floatB, epsilon) && floatA > floatB)
#define DOUBLE_LTE(floatA, floatB, epsilon) (FLOAT_EQ(floatA, floatB, epsilon) || floatA < floatB)
#define DOUBLE_GTE(floatA, floatB, epsilon) (FLOAT_EQ(floatA, floatB, epsilon) || floatA > floatB)

Used like this:

if (FLOAT_EQ(fDollarValue, 0.0, 0.005)
{
    NSLog(@"The total is $0.00");
}

Part of http://www.github.com/NSProgrammer/NSProgrammer

@NSProgrammer

Detecting the image type of a data blob:

NS_INLINE NSPUIImageType _DetectDataImageType(NSData* imageData)
{
    if (imageData.length > 4)
    {
        const unsigned char* bytes = imageData.bytes;

        if (bytes[0]==0xff && 
            bytes[1]==0xd8 && 
            bytes[2]==0xff /* && bytes[3]==0xe0 -- the last byte of JPEG type can have numerous values, just accept anything for detection.*/)
            return NSPUIImageType_JPEG;

        if (bytes[0]==0x89 &&
            bytes[1]==0x50 &&
            bytes[2]==0x4e &&
            bytes[3]==0x47)
            return NSPUIImageType_PNG;
    }

    return NSPUIImageType_Unknown;
}

Part of http://www.github.com/NSProgrammer/NSProgrammer

@0xced
0xced commented Nov 11, 2013

Add #define OBJC_OLD_DISPATCH_PROTOTYPES 0 in your .pch file. This will force you to cast objc_msgSend calls.

@0xced
0xced commented Nov 11, 2013

You can easily hook NSLog with the private function _NSSetLogCStringFunction. But don’t do it of course.

@0xced
0xced commented Nov 11, 2013

Print which context is passed to observeValueForKeyPath:ofObject:change:context: in lldb.
Say you have declared a context like this:

static const void *MyFooContext = &MyFooContext;

And you want to to know what context it is when you are inside

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

Just do this:

(lldb) image lookup -a `context`
      Address: MyApp[0x00026258] (MyApp.__DATA.__data + 4)
      Summary: MyFooContext
@0xced
0xced commented Nov 11, 2013

Import class-dump info into GDB

My best Stack Overflow answer in 2013. Allows you to restore ObjC symbols in order to debug stripped apps easily.

@jurre
jurre commented Nov 12, 2013

With regards to localising strings I usually have a function like this somewhere in a .h file:

static inline NSString *JSLocalizedString(NSString *key, NSString *defaultValue) {
    return NSLocalizedStringWithDefaultValue(key, nil, [NSBundle mainBundle], defaultValue, nil);
}

static inline NSString *JSLocalizedStringWithComment(NSString *key, NSString *defaultValue, NSString *comment) {
    return NSLocalizedStringWithDefaultValue(key, nil, [NSBundle mainBundle], defaultValue, comment);
}

Used like:

NSString *title = JSLocalizedString(@"JS:Login:Title", "Login");
NSString *loginButtonText = JSLocalizedStringWithComment(@"JS:Login:LoginButton", @"Sign in", @"The login button for when users already have an account");
@jkubicek

If you are laying out your views manually, set your view frames in a block. The indentation helps delineate where you are setting frames and the code blocks let you reuse variable names. You can now just name your frames frame instead of closeButtonFrame, nextButtonFrame, loadingLabelFrame, which can get tiresome if you have a lot of views to position.

self.closeButton.frame = ({
    CGRect frame;
    // Calculate the frame here
    frame
});

self.nextButton.frame = ({
    CGRect frame;
    // Calculate the frame here
    frame
});

self.loadingLabel.frame = ({
    CGRect frame;
    // Calculate the frame here
    frame
});
@joshavant

A quick-and-dirty way to debug any instance - at any point during execution, without waiting to hit a breakpoint - is to NSLog the memory address in the object's initializer method:
NSLog(@"<%@>: %p", NSStringFromClass([self class]), self); (example: <XXFooView>: 0xDEADBEEF)

Once that prints, make note of the memory address (in the example above, it's 0xDEADBEEF).

Now, whenever you feel like it, you can hit the Pause execution button and type this in the debugger:
po 0xDEADBEEF
and the description of that object will print in the debugger.

@joshavant

It's a simple one, but an incredibly handy one:

BOOL someBoolean = YES;
NSLog(@"someBoolean: %@", someBoolean ? @"YES" : @"NO");

...output:
someBoolean: YES

@joshavant

Copy and paste this over the closing brace of application:didFinishLaunchingWithOptions: to quickly add a debug button to the top left corner of your app's window, and an easily-triggerable method to put test code into:

UIButton *debugButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
debugButton.frame = CGRectMake(0.f, 20.f, 44.f, 44.f);
[debugButton addTarget:self action:@selector(didPressDebugButton) forControlEvents:UIControlEventTouchUpInside];
[self.window addSubview:debugButton];

return YES;  
}

- (void)didPressDebugButton
{
  NSLog(@"boink!");
}
@eskerber

Protect your class from default initializers by marking init and new as unavailable. You can also provide a custom string that the compiler will display if somebody tries to use one.

-(instancetype) init attribute((unavailable("Please use initWithString")));
+(instancetype) new attribute((unavailable("Please use initWithString")));

@Anviking

In my attempts to avoid learning how to method-swizzle, I came up with a way of "replacing" classes with subclasses.

@implementation NSLayoutManager (JLLayoutManager)

// [NSLayoutManager alloc] will return an instance of  JLLayoutManager.
+ (id)alloc
{
    if ([self class] == [NSLayoutManager class]) {
        return [JLLayoutManager alloc];
    }
    return [super alloc];
}

@end
@pitiphong-p

I wrote the helper function for creating key path from selectors

inline NSString * PTPKeyPathsForKeys(SEL key, ...) {
  if (!key) {
    return nil;
  }
  NSMutableArray *keys = [[NSMutableArray alloc] init];
  va_list args;
  va_start(args, key);
  SEL arg = key;
  do {
    [keys addObject:NSStringFromSelector(arg)];
  } while ((arg = va_arg(args, SEL)));
  va_end(args);

  return [keys componentsJoinedByString:@"."];
}

Usage

NSString *keyPath = PTPKeyPathsForKeys(@selector(data), @selector(name), NULL); // @"data.name"
@carlj
carlj commented Dec 13, 2013

you should write a Post about the advantage from xcconfig files

@florianbachmann

yeah xcconfig files @carlj

@shpakovski

Recently I discovered a new Foundation class named NSByteCountFormatter, no more custom file size formatters! Documentation.

@nevyn
nevyn commented Dec 23, 2013

My favorite self-written hack this year is SPAwait. It emulates the await keyword from C# 4 (or Futures.wait from node.js) by building coroutines using macros, blocks and non-structured case statements (inspired by an old mikeash post). Unfortunately, it doesn't work great with ARC :( while not usable in real life, such structure allows you to write asynchronous code as if it was synchronous, which is absolutely magical and wonderful. Way better than either callbacks or futures.

https://github.com/nevyn/SPAsync/blob/master/include/SPAsync/SPAwait.h

@acoomans

iOS7 has introduced a lot of changes in the API, both deprecating and introducing new methods, in different frameworks.

One way to deal with this is to temporary disable the deprecated warnings in clang with a pragma (where you handle the API differences).

Another way is to use the excellent Deploymate for managing different iOS versions. It analyzes your code and gives you warnings for deprecated/new methods. Like with clang, you can use a pragma to let Deploymate ignore it.

Here is a gist with a few macros to help with checking the iOS version and ignore both clang and Deploymate's warnings.

To use the macros, simply do:

AVAILABLE_API_IF_GREATER_THAN(@“7.0”)
    do_something_only_in_ios7();
AVAILABLE_API_ELSE
    do_something_for_others();
AVAILABLE_API_END
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment