Skip to content

Instantly share code, notes, and snippets.

@mattt
Created November 11, 2013 15:12
Show Gist options
  • Save mattt/7414618 to your computer and use it in GitHub Desktop.
Save mattt/7414618 to your computer and use it in GitHub Desktop.

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!

@0xced
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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

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

...output:
someBoolean: YES

@joshavant
Copy link

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!");
}

@erikkerber
Copy link

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
Copy link

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
Copy link

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
Copy link

carlj commented Dec 13, 2013

you should write a Post about the advantage from xcconfig files

@florianbachmann
Copy link

yeah xcconfig files @carlj

@shpakovski
Copy link

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

@nevyn
Copy link

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
Copy link

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