Skip to content

Instantly share code, notes, and snippets.

@mattt
Created November 25, 2014 19:38
Show Gist options
  • Save mattt/f00da093956f20945727 to your computer and use it in GitHub Desktop.
Save mattt/f00da093956f20945727 to your computer and use it in GitHub Desktop.
NSHipster New Year's 2015

Season's Greetings, NSHipsters!

As the year winds down, and we take a moment to reflect on our experiences over the past months, one thing is clear: 2014 has been an incredible year professionally for Apple developers. So much has happened in such a short timespan, and yet it's hard to remember our relationship to Objective-C before Swift, or what APIs could have captivated our imagination as much as iOS 8 or WatchKit.

It's an NSHipster tradition to ask you, dear readers, to send in your favorite tips and tricks from the past year for publication over the New Year's holiday. This year, with the deluge of new developments—both from Cupertino and the community at large—there should be no shortage of interesting tidbits to share.

Submit your favorite piece of Swift or 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!

If you're wondering about what to post, look to past year's write-ups for inspiration (2014, 2013).

I can't wait to see what you all send in!

@0xced
Copy link

0xced commented Nov 26, 2014

Quick way to check all the pods used by a (closed source) app:

class-dump -C Pods_ /Applications/Squire.app | grep -o "Pods_\w+"

@0xced
Copy link

0xced commented Nov 26, 2014

I discovered the CREATE_INFOPLIST_SECTION_IN_BINARY Xcode setting recently for command-line apps. It’s much easier to use than the -sectcreate __TEXT __info_plist linker flag and it embeds the processed Info.plist file into the binary.

It’s also a lesson on filing radars. This feature request was filed as rdar://4722772 in 2006 and was addressed about 7 years later.

@0xced
Copy link

0xced commented Nov 26, 2014

Make hackers’ life tougher with this trick from Sam Marshall:

Want to stop dylib hooking into your app? Add this one line to your “Other Linker Flags” :P
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null

@0xced
Copy link

0xced commented Nov 26, 2014

If you gave up on reading the official iOS 7.1 to iOS 8.0 and the OS X v10.9 to OS X v10.10 API differences because of the noise mostly generated by transforming getter/setters into properties and id into instancetype, I created curated versions for iOS and OS X.

Also interesting is the source code that generated these curated documents. Less than 200 lines of code thanks to the powerful NSXMLDocument/XPath APIs.

@0xced
Copy link

0xced commented Nov 26, 2014

If you are installing Xcode from the dmg, I highly recommend this technique by Joar Wingfors in order to avoid to accidentally modifying SDK headers.

Preserve ownership, permissions and hard links installing Xcode from a dmg:
sudo ditto /Volumes/Xcode/Xcode.app /<destination>/Xcode.app

I personally add the -V flag to ditto for verbose logging.

@0xced
Copy link

0xced commented Nov 26, 2014

For reverse engineering purpose, it’s always useful to look at instance variables of objects. It’s usually pretty easy to do so with valueForKey: since very few classes override the +accessInstanceVariablesDirectly
method to disable ivar acces through Key-Value Coding.

There’s one case where it doesn’t work though: when the ivar has a void * type. Here is an excerpt of the MediaPlayer framework class-dump on iOS 6.1:

@interface MPMoviePlayerController : NSObject <MPMediaPlayback>
{
    void *_internal;    // 4 = 0x4
    BOOL _readyForDisplay;  // 8 = 0x8
}

Since id internal = [moviePlayerController valueForKey:@"internal"] doesn’t work, here is the hardcore way to access the internal ivar:

id internal = *((const id*)(void*)((uintptr_t)moviePlayerController + sizeof(Class)));

Don’t ship this code, its’s very fragile because the ivar layout may change. Use this for reverse engineering only!

@0xced
Copy link

0xced commented Nov 26, 2014

Friendly reminder that if you are using -[NSDateFormatter setDateFormat:] without +[NSDateFormatter dateFormatFromTemplate:options:locale:], you’re probably doing it wrong.

@0xced
Copy link

0xced commented Nov 26, 2014

Recently, Matthias Tretter asked on Twitter:

Does anyone know the default animation duration and springs for modal viewController presentation on iOS 8?

Here is how to answer this question in 3 minutes:

  • Search for duration in a class-dump of UIKit

  • Find the +[UITransitionView defaultDurationForTransition:] method

  • Set a breakpoint for this method:

    (lldb) br set -n "+[UITransitionView defaultDurationForTransition:]"
    
  • Present a modal view controller, you will hit the breakpoint, type finish to execute the method

    (lldb) finish
    
  • At that point the defaultDurationForTransition: has executed and you can read the result, it’s in the xmm0 register.

    (lldb) register read xmm0 --format float64
        xmm0 = {0.4 0}
    

Answer: the default duration is 0.4s.

@0xced
Copy link

0xced commented Nov 26, 2014

Quick tip from @bensge:

Wow +[UIViewController _printHierarchy] is like -[UIView recursiveDescription] but for view controllers! #Development

@0xced
Copy link

0xced commented Nov 26, 2014

DIY weak associated objects, adapted from my Stack Overflow answer:

Unfortunately, the associated objects OBJC_ASSOCIATION_ASSIGN policy does not do zeroing weak reference. Fortunately, it’s quite easy to implement yourself. You just need a simple class to wrap an object with a weak reference:

@interface WeakObjectContainter : NSObject
@property (nonatomic, readonly, weak) id object;
@end

@implementation WeakObjectContainter
- (instancetype) initWithObject:(id)object
{
    if (!(self = [super init]))
        return nil;

    _object = object;

    return self;
}
@end

Then you must associate the WeakObjectContainter as OBJC_ASSOCIATION_RETAIN(_NONATOMIC):

objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainter alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);

and use the object property to access it in order to get a zeroing weak reference:

id object = [objc_getAssociatedObject(self, &MyKey) object];

@shpakovski
Copy link

Here is a convenient way to access child controllers inserted into Storyboard container views:

// 1. A property has the same name as a segue identifier in XIB
@property (nonatomic) ChildViewController1 *childController1;
@property (nonatomic) ChildViewController2 *childController2;

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    [super prepareForSegue:segue sender:sender];

    // 2. All known destination controllers assigned to properties
    if ([self respondsToSelector:NSSelectorFromString(segue.identifier)])
        [self setValue:segue.destinationViewController forKey:segue.identifier];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 3. Controllers already available bc viewDidLoad is called after prepareForSegue
    self.childController1.view.backgroundColor = [UIColor redColor];
    self.childController2.view.backgroundColor = [UIColor blueColor];
}

@shpakovski
Copy link

You can Edit Breakpoint in Xcode by Option-Command-Click on the blue indicator.

@orta
Copy link

orta commented Nov 28, 2014

AppCode.

@CodaFi
Copy link

CodaFi commented Nov 28, 2014

Member functions on Swift classes and structures always have the following type when used statically:

Object -> (Args) -> Thing

For example, you can call reverse() on an array in two ways

[1, 2, 3, 4].reverse()
Array.reverse([1, 2, 3, 4])()

@zobkiw
Copy link

zobkiw commented Nov 28, 2014

I'm a fan of the Objective-C Runtime functions objc_getAssociatedObject and objc_setAssociatedObject. These allow the ability to "associate" an object with another object. This association can survive the life of the source object. Imagine tacking "data" onto a button that the button's IBAction can then access when tapped. Lots of possibilities with these functions.

@kn-neeraj
Copy link

Cool instruments tricks to counter memory leaks - https://medium.com/hacking-ios/a-day-with-instruments-tool-414ad50234f1
Also CGParallaxCollectionView written very early when Swift was annouced - https://github.com/kNeerajPro/CGParallaxCollectionView

@bitops
Copy link

bitops commented Dec 1, 2014

Shameless plug for some Array extensions I wrote to mimic some of the popular methods from Ruby's Enumerable module. Attributions, where applicable, are in the source. https://github.com/bitops/swift-collections/blob/master/SwiftCollections.swift

@defagos
Copy link

defagos commented Dec 1, 2014

Instead of if-testing a block against nil before calling it:

if (block) {
    result = block(param1, param2);
}
else {
    result = defaultValue;
}

if love using the one-liner:

result = block ? block(param1, param2) : defaultValue;

to declutter my code. For blocks returning void, simply use nil as default value:

voidBlock ? voidBlock(param1, param2) : nil;

@defagos
Copy link

defagos commented Dec 1, 2014

Given the fact that literals are most of the time associated with numbers and collections, I often forget that they work for UTF8-encoded NULL-terminated C-strings as well, especially when I write code using the runtime:

NSString *propertyAttributesString = @(property_getAttributes(class_getProperty([NSObject class], "description")));         // T@"NSString",R,C

It is namely quite amusing that:

[@("someString") isEqualToString:@"someString"] == YES

is something I so easily forget, especially since this boxing syntax is totally natural.

@cmyr
Copy link

cmyr commented Dec 1, 2014

Optionals should be avoided. Implicitly unwrapped optionals should be strongly avoided. Want to declare a var but don't necessarily have an initial value at init time? Use the lazy keyword, and just don't call the getter before you have your real value.

lazy var someModelStructure = ExpensiveClass()

if you call set on this var without having ever called the getter, the lazy expression is never evaluated. Great for references to views that you don't necessarily want to init until viewDidLoad, for instance.

@incanus
Copy link

incanus commented Dec 4, 2014

Now that 10.10 is allowing easy screencasts of iOS devices (which itself is a neat tip), I find myself again using a framework I made some time back for showing touches on screen: Fingertips. Great for presentations, too.

@kazmasaurus
Copy link

You can optional chain calling a closure just like you can accessing a subscript

func foo(completion: ((String) -> Void)?) {
    completion?("Heyo!!") ?? println("Oyeh!!")
}

foo { println($0) }     // prints "Heyo"
foo(nil)                // prints "Oyeh"

@0xced
Copy link

0xced commented Dec 9, 2014

Sometimes, you need to know which language your app is running in. Often, people will use +[NSLocale preferredLanguages]. Unfortunately this tells nothing about the language the app is actually displaying. It will just give you the ordered list as found in Settings → General → Language & Region → Preferred Language Order on iOS or System Preferences → Language & Region → Preferred Languages on OS X.

Imagine that the preferred language order is {English, French} but your app is German only. Calling [[NSLocale preferredLanguages] firstObject] will give you English when you want German.

The proper way to get the actual language used by the app is to use [[NSBundle mainBundle] preferredLocalizations].

From the documentation:

An array of NSString objects containing language IDs for localizations in the bundle. The strings are ordered according to the user's language preferences and available localizations.

From a comment in NSBundle.h:

a subset of this bundle's localizations, re-ordered into the preferred order for this process's current execution environment; the main bundle's preferred localizations indicate the language (of text) the user is most likely seeing in the UI

You may also need to use +[NSLocale canonicalLanguageIdentifierFromString:] in order to ensure a canonical language identifier.

@samirGuerdah
Copy link

Functional Programming vs OOP vs Imperative Programming

@dodikk
Copy link

dodikk commented Dec 9, 2014

@samirGuerdah vs "Playground Driven Development" )))

@fpillet
Copy link

fpillet commented Dec 9, 2014

When you have objects in a NSArray, you can extract another NSArray with the value returned from a selector applied to each object using -valueForKey. This call will turn the string into a @selector if it doesn't find a property with the same name, and call the selector:

NSArray *array = <some array of NSObject> ;
NSArray *descriptions = [array valueForKey:@"description"];

The cool thing is that this works with any selector you define on your objects.

This is part of the key-value coding conventions. Relevant documentation for NSArray is here

@hborders
Copy link

hborders commented Dec 9, 2014

If you're repeatedly debugging the same problem over and over, you can run your app without rebuilding by using <ctrl>+<cmd>+r.

@jonfriskics
Copy link

Swift Playgrounds all share the same Shared Playground Data folder that's symlinked to /Users/HOME/Documents/Shared Playground Data.

If you like using lots of Playgrounds, you'll want to organize the data that each Playground is using into subfolders of that shared folder, but then you've got to let the Playground know where to look. Here's a helper function that I use that makes that easy:

func pathToFileInSharedSubfolder(file: String) -> String {
    return XCPSharedDataDirectoryPath + "/" + NSProcessInfo.processInfo().processName + "/" + file
}

That processName property in NSProcessInfo contains the name of the Playground file, so as long as you have already created a sub-folder in the Shared Playground Data folder with the same name you can access those files pretty easily, like reading local JSON:

var jsonReadError:NSError?
let jsonData = NSFileManager.defaultManager().contentsAtPath(pathToFileInSharedSubfolder("data.json"))!
let jsonArray = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &jsonReadError) as [AnyObject]

or pulling out a local image:

let imageView = UIImageView()
imageView.image = UIImage(contentsOfFile: pathToFileInSharedSubfolder("image.png"))

@Fl0p
Copy link

Fl0p commented Dec 10, 2014

Cocoapods
@orta 😉

@palewar
Copy link

palewar commented Dec 16, 2014

Playground as Documentation.

And also Firefox coming to iOS and looks to be mostly done in Swift. It's still early days and a great opportunity for community to get involved with the development. Contributing guidelines are non existent and so are code comments, making it difficult to get involved. If somebody can do some digging and come up with some simple guidelines and code comments for existing code, it will be great for everybody.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment