Swizzling in AFNetworking somehow breaks iOS’ NSDoubleLocalizedStrings

Tools to Help Test Localization

Apple provides some pretty slick tools to help with localization testing in your apps. I had completely forgotten about two launch parameters that make it possible to find those pesky layout problems early:

  • NSDoubleLocalizedStrings – Any calls made to NSLocalizedString will double the strings to simulate languages with longer words, like German.
  • NSShowNonLocalizedStrings – Replaces any text from NSLocalizedString that doesn’t have an entry in a strings file.
  • AppleTextDirection – Simulates a Left to Right language.

In Xcode 7 there is an easier way to provide these options. Edit the scheme for your app, click on the Run section, then the Options tab. You’ll see Application Language has two options – Double Length Pseudolanguage and Right to Left Pseudolanguage. There is also a check box for Show non-localized strings. These three options are equivalent to the launch arguments above.

There’s more about testing the localization of your app in Apple’s Internationalization and Localization Guide.

Why Isn’t it Working!?!!?!?

This past week I spent a good chunk of a day trying to figure out why Xcode’s options for localization testing weren’t working. I tried the three items above and also tried sending them as launch parameters like -NSDoubleLocalizedStrings YES with no avail. I checked the contents of NSUserDefaults upon launch and verified that the launch parameters made it into the defaults as expected. Why wasn’t it working??

I then tried to set the user default myself in the app delegate. At first it didn’t work. Then when I restarted the app suddenly the strings were being duplicated. I was confused to say the least.

I dug deeper and discovered that two test apps I had written for RayWenderlich.com worked just fine with the Xcode options and launch arguments approach. My project that didn’t work had CocoaPods – so I thought maybe that was the problem. It wasn’t because the second test app had CocoaPods. I then decided it had to be one of the pods causing the problem.

I went back to the original app and started removing pods from the Podfile. Turns out the pod causing the issue was AFNetworking! I then added AFNetworking to my demo project that was working and it stopped working after that. Then I dug through each subspec in AFNetworking and found that the NSURLSession spec was the culprit.

Turns out there is a single line of code in a method swizzle in AFNetworking’s AFURLSessionManager’s private class _AFURLSessionTaskSwizzling that’s causing the issue.

I opened an issue on GitHub to help track this. It does seem to be an issue even in AFNetworking 3.

How do I Fix it Right Now?

Simply comment out the line of code referenced above in your checked out copy of AFNetworking. If you’re using CocoaPods you’ll get a warning message saying you have to unlock editing of that file. Go ahead and do that but don’t forget to undo the change after.

UIKit Dynamics – Turning on Debug Mode

In iOS 9 Apple has introduced a number of new shiny things for UIKit Dynamics. One of them is UIFieldBehavior which describes magnetic, electrical and spring fields of influence. Fields are hard to debug (even in real life!) so Apple decided to provide a debug mode on UIDynamicAnimator. The trick is the debug mode isn’t published in the headers. Why? Who knows. They mentioned it quite plainly at WWDC 2015 and said you have to turn it on in the LLDB debugger.

I found an easier and more straightforward way. Just use key-value coding! UIDynamicAnimator still inherits from NSObject so the KVC helper methods are still there.

Easy!

Mac OS X Server Time Machine Volume Filling Too Fast

I’ve been noticing on my Mac OS X Mavericks 10.8 Server I have running in a data center has been filling up its Time Machine volume way too quickly.  The backups are continually huge and only about a week fits on the second hard drive inside of the Mac mini.  Every time the machine backed up it was taking up so much room that previous backups had to be deleted.

I searched everywhere and couldn’t find an issue.  I looked in console logs for Time Machine and couldn’t find anything definitive as well but it looked like it was something in /Library/Server.  I realized that it must be a file or set of files that are always being written to so they’re always backed up every hour or so.

I had tried some command-line tools like “du” but the usage was so many levels deep it was hard to pinpoint.  Finally I grew a brain and installed DaisyDisk on the server to see what I could find.  Within minutes I found the pinch point.  See all of the repeated pattern in the third layer from the center?

2014-08-21_07-53-41

Turns out the file /Library/Server/Xcode/Logs/xcs_proxy.log was huge and never rolled over by the logging utility.  I had been using Xcode on this server to run Bots but had since turned it off.  I still need to figure out WHY this file is being written to without the Bots service running.  Because the file is always being written to, the entire thing is backed up every single hour.

Solution:

sudo rm /Library/Server/Xcode/Logs/*

If I find the solution to the xcs_proxy issue, I’ll follow up.

Asynchronous unit testing Core Data with Xcode 6

The WordPress for iOS project had a number of unit tests using Core Data and a custom asynchronous test helper.  The helper used a semaphore in a global scope and a bit of method swizzling to give a wait/notify mechanism.  The problem with this solution was the global semaphore and poorly written tests causing a conflict.  Tests would call the ending wait and previous tests running Core Data would fire off notifies causing a mismatch between the original test and the recipient of the message to pass by the current semaphore.

The solution was to eliminate the global scope and have a semaphore object/instance that could be passed around by the single test.  The more I started designing the solution I realized we should just start using the new asynchronous testing capabilities of Xcode 6.  Xcode 6 / iOS 8 / Yosemite brings us a wealth of new tools to play with including Swift, the newest language developed by Apple.

XCTestExpectation

The newest member to the XCTest framework is XCTestExpectation.  This class encapsulates a single semaphore (of sorts) allowing your test to wait for 1..n things before the test is considered over.  This helps with asynchronous things like AFNetworking, Core Data, and Grand Central Dispatch.  If you’re using a Core Data stack with multiple NSManagedObjectContext instances (background & main for example) then you’re going to almost certainly want to know how to test asychronously.

Creating a XCTestExpectation is quite simple:

- (void)testObjectPermanence {
    XCTestExpectation *saveExpectation = [self expectationWithDescription:@"Context save expectation"];
    // ..
}

The method expectationWithDescription: is part of XCTestCase and it has a companion method waitForExpectationsWithTimeout:handler: which you call when you’re reading to block the test execution until [saveExpectation fulfill] is called.  Fulfilling an expectation means the expectation has been met and the flag should be flipped for the test that is waiting.  If your test execution never reaches a fulfillment, then your test will fail when your timeout threshold is met.

Some important things to note about XCTest asynchronous testing:

  • A XCTestExpectation instance can only be fulfilled a single time.  Use multiple instances if you have more than one expectation.
  • Only a single waitForExpectationsWithTimeout:handler: can be active at any one time.
  • Unit tests, by default, run one at a time and not in parallel.  If you modify the test suite running to parallelize, I can see things breaking fairly quickly.
  • There are convenience methods to create expectations on a KVO property or NSNotification

Core Data Secret Sauce

There are a bunch of waits you could fire off a fulfillment on an XCTestExpectation when a Core Data context saves.  In the case of WordPress-iOS we use a central class, ContextManager, to help manage all of the Core Data levers and knobs.  There is a companion class called CoreDataTestHelper that does a bunch of useful things like overriding our persistent store to be in memory rather than on disk and also swizzle a method on ContextManager to fire fulfillments if an expectation is recorded.  Here’s an example of a test using this expectation helper:

- (void)testObjectPermanence {
    XCTestExpectation *saveExpectation = [self expectationWithDescription:@"Context save expectation"];
    [CoreDataTestHelper sharedHelper].testExpectation = saveExpectation;

    NSManagedObjectContext *derivedContext = [[ContextManager sharedInstance] newDerivedContext];
    Blog *blog = [self createTestBlogWithContext:derivedContext];
    [[ContextManager sharedInstance] saveDerivedContext:derivedContext];

    // Wait on the merge to be completed
    [self waitForExpectationsWithTimeout:2.0 handler:nil];

    XCTAssertFalse(blog.objectID.isTemporaryID, @"Object ID should be permanent");
}

You can see we store the single expectation for the next NSManagedObjectContext save on the CoreDataTestHelper sharedHelper instance. Later on when the context is saved, the following swizzled code executes and fulfills the expectation:

+ (void)load {
    Method originalSaveContext = class_getInstanceMethod([ContextManager class], @selector(saveContext:));
    Method testSaveContext = class_getInstanceMethod([ContextManager class], @selector(testSaveContext:));
    method_exchangeImplementations(originalSaveContext, testSaveContext);
}

- (void)testSaveContext:(NSManagedObjectContext *)context {
	[self saveContext:context withCompletionBlock:^() {
        if ([CoreDataTestHelper sharedHelper].testExpectation) {
            [[CoreDataTestHelper sharedHelper].testExpectation fulfill];
            [CoreDataTestHelper sharedHelper].testExpectation = nil;
        } else {
            NSLog(@"No test expectation present for context save");
        }
    }];
}

Next Steps

Our asynchronous tests are definitely not finished and the Core Data testing stack will most likely continue to change.  Our service layer doesn’t enforce a single save paradigm – we tell contributors that they should probably save often and at least one time before the service method completes.  The problem with the design we’re using above is that there is no good way to let a number of saves happen and to wait until the very last save.  In order do that, we’d have to offer up a special saveContext method on ContextManager that could get an instance of an XCTestExpectation.  That method would exist only as a class extension in our CoreDataTestHelper.  It’s messy but then again so is Core Data at times.  🙂

Pull Request

The changes I made to the WordPress for iOS project are up for review in this GitHub Pull Request.

If you run Unit Tests in Xcode

If you run unit tests inside of Xcode, you may wish to turn on the behavior to show the test results after they run.

TestBehaviors

  1. Go to Preferences in Xcode.
  2. Click on the Behaviors tab.
  3. Click on Succeeds.
  4. Check the box shown and select “Show” then “Test Navigator”.
  5. Repeat step 4 for Fails as well.

Now when your tests finish (failed or succeeded) you’ll see the pretty green or red marks.

Software I Use Every Day

In the spirit of yesterday’s post, I’m going to list out what I use every day in terms of software.  This isn’t exhaustive but it’s pretty darn close.

General Utilities

  • 1Password – Probably the best password manager out there combined with mobile apps
  • Cloudup – quick way to share images, videos, text (ask me for a referral code)
  • Coffitivity – coffeehouse sounds to help boost productivity
  • Cyberduck – SFTP client
  • DaisyDisk – finding where all my space has gone
  • Dropbox
  • Evernote – where I keep my larger notes, graphics, PDF files
  • Parallels Desktop – for the occasional booting of old Mac OS & Windows VMs
  • Radium – menu bar radio streaming
  • Rdio – monthly subscription-based song streaming
  • RescueTime – track my app usage to determine if I’m distracted
  • Simplenote – for my quick note taking needs
  • Skype – sadly yes
  • Slack – communication for our team – web socket-based system like HipChat but better

Graphics

Development – General

  • Base – for digging around SQLite files; especially handy debugging Core Data
  • Charles – proxying application for testing remote calls
  • HockeyApp – binary distribution for testing
  • Kaleidoscope – arguably the most beautiful diff tool – ignore whitespace is still not a feature 😦
  • PHPStorm – for when I have to get into WordPress and WordPress.com API coding
  • TextMate – Still my favorite text editor
  • Textual – Mac IRC client

Development – iOS

  • AppCode – alternative IDE for Objective-C – I switch between Xcode and here for specific reasons (future post?)
  • iExplorer – could not live without the ability to dig around device filesystems
  • PaintCode – easiest way to get Core Graphics code from images or hand-drawn UI elements
  • Reflector – transmit your iOS device screen to your computer for recording
  • Spark Inspector – interactively debug your UIView layers & NSNotificationCenter calls
  • Xcode
  • xScope – helpful UI tools for your Mac

Development – Android

  • Android Studio
  • Genymotion – Android VM manager – lurv

Installed Apps

Checking in CocoaPods files

I’ve gone back and forth on the debate with whether or not we should be checking in the dependencies for a project supplied by CocoaPods.  In the past I felt it was best to only check in the Podfile and maybe the lock file.  I believe I’ve finally made a decision with recent experiences and my development practices with Git.

I’m checking in the whole effing workspace.

Why?

  • Every branch, especially master, should be compilable and archivable up to a point.
    • Caveat 1: Xcode is a piece of shit sometimes and will break things because it can between versions.
    • Caveat 2: Provisioning Profiles.
  • CocoaPods is a well-maintained tool, however, Specs are a crap-shoot.
    • Specs can disappear.
    • Specs can be unofficially maintained.
    • Specs can be wrong.
  • Checking in your Pods directory ensures the best possible snapshot of the pre-binary code.
    • Forcing a pod install every time someone checks out code doesn’t ensure the same state of code is maintained for testing bugs in previous versions.
    • You may decide to drop Pods support and forget how to use it.
    • Wouldn’t it be nice to do diffs on your dependency classes?
  • Xcode Bots doesn’t work well enough yet with CocoaPods for me to want to install Pods every build
    • I’m also not sold on Xcode Bots itself – it’s quite unreliable and likes to smoke my server’s CPU.
    • CocoaPods needs to not be a hinderance – its pretty innocuous when the risky work (installing Pods) is done and checked in.

That sums up my thoughts.  I primarily work on WordPress for iOS which is a heavily forked and contributed to repository.  I don’t think the project could be a success with the amount of branching and pull requests performed if we didn’t check in the Pods directory.