Tag: iOS

How to Enable Custom Debugging in Release Builds

Reliably enable debugging features for yourself. Hide them from your end users. Stop using #if DEBUG.

TLDR: Use a custom Profile that includes a certificate. Install that on devices you want to enable debugging on. By detecting if that profile has been installed on the user’s device, you can enable/disable debug features that are impossible for your users to find.


At my work we use TestFlight to distribute beta builds for internal and external testing. This a great, because once you certify a build as “ready for production”, you can just release it – it’s the exact binary that you’re going to ship.

But there’s a problem – we also want to give ourselves some extra debugging controls in our TestFlight builds for things like switching to a staging environment, toggling feature flags, and viewing extra error info.

These are features that we can’t ship to end users, but that we want available when we use the same version of the App they will get.

Person Debugging an iPhone App

I first thought of a few not-so-good ways to do this:

Bad Way #1: Use a different build configuration

Use #if DEBUG or #if TESTFLIGHT compiler directives to compile out the debug features from your release build, then only test on a version that has them included.

The upside to this approach is that your debug features are literally compiled out of your release builds so that can’t possibly be enabled by end users. On the other hand, this can also be a problem.

Its easy to enable debug features for DEBUG builds, but this doesn’t meet the original requirement of being available in TestFlight since the TestFlight builds are releasable binaries, and are compiled using the RELEASE configuration.

You could even create a custom third configuration called TESTFLIGHT which is identical to RELEASE and then enable the features in that build, but then you would need to upload two builds to TestFlight, come up with a wacky versioning scheme to identify them, and keep clear which on is which when testing and releasing. Perhaps worst of all, we wouldn’t actually be testing the binary we were releasing and sometimes there really is a difference.

Bad Way #2: Hardcoded UserId’s

Hardcode a list of our team’s userIds, and only show these features to those users.

This is bad because you need to maintain this list of privileged users, and you can’t ever add users to an old commit. What if you hire new QA and ask them to test this version? They won’t be able to with their own credentials.

It’s also just inelegant. We can do better than this.

Bad Way #3: Detect the installation source programatically

Write your own method that figures out where the App was installed from.

There is no official platform API for differentiating whether the App was installed from Xcode on your dev machine, the App Store, or TestFlight.

This is perhaps because its not a straightforward question – what if you update from an App Store build to a dev build? That probably should count as a DEBUG build, since that’s where the App binary came from, but there will still be remnants of your App Store past on the device, such as the receipt that indicates your purchase history.

But this is not to say you can’t (even if you shouldn’t) make such an API yourself!

You could implement a isRunningInTestFlightEnvironment() function, along these lines and then use that to show/hide your debugging UI.

But do not overestimate how reliable this kind of method is. It is a hack, implemented by looking for those App Store Receipts, and there’s nothing to say that Apple won’t change the name, location, or meaning of that hardcoded string in a future release, leaving your solution broken.

It is totally great for analysis and you might want to include this in logging, or event reporting, but don’t trust it blindly.

Bad Way #4: Use a secret gesture

Create an undocumented gesture recognizer, and hope its obscure enough that end users don’t stumble on it.

This is sort of like the Konami Code (⬆️⬆️⬇️⬇️◀️▶️◀️▶️🅱🅰). Who could possibly figure out something like that? … right? 😂

The problem is exactly that – someone will figure it out, and then all your debugging tools are out there with no way for you to disable them.

Bad Way #5: Use a secret URL scheme

Add a URL Type to your App target, and implement application(_ open url: options:) in your AppDelegate. Open your special URL in Safari to enable debugging.

In my opinion, this is also a pretty good option, and depending on the choice of URL is probably obscure enough that users won’t find it.

But if they do… (see above).

It’s also kind of annoying to have to exit the app and remember the special URL every time you want to change a debug setting, or enter your special menu or whatever.

This need to exit the App could also be detrimental – if you need to be able to open the debug panel without putting your App in the background, this option won’t work for you.

One advantage here is that it isn’t coupled to any single part of the App. The url opens equally well whether you’re on the login screen, or deep in the heart of your App.

The Good Way: Detect a Profile Installed on the Device

Create a special configuration profile using Keychain Access and Apple Configurator 2, and install this on your devices. Detect the presence of this profile to enable your debugging features on that device.

This solution has a number of advantages:

  • Nobody can just guess your secret gesture or cheat code, they would need to be explicitly given the debug profile.
  • You only have to install the profile once. You don’t have to do your secret enabling technique on every App run or installation.
  • You could use one profile for many apps, or multiple profiles for different levels of privilege or feature sets.
  • It applies equally well to DEBUG and RELEASE builds, and allows you to access these features even in your published App Store builds.
  • It doesn’t require any special environment, or specific users

The disadvantages I see are:

  • It requires the work of creating and storing this Debug Profile.
  • Checking the trust result of the certificate is, admittedly, a bit of a hack.
  • You need to save your Debug Profile somewhere and not lose it

So How Does This Work?

This solution is entirely based on the the SecTrustEvaluate() function.

We use SecTrustEvaluate() to determine if a user has installed and trusted a certificate (by installing our debug-enabling configuration profile), or just the plain untrusted certificate that was bundled with the Application.

So we’ll need to learn how to: create both of these certificates, create and install the configuration profile, and check the certificate for trust.

Let’s do this!

1. Create a Certificate Authority

  • Open Keychain Access.
  • Click Keychain Access > Certificate Assistant > Create a Certificate Authority.
  • Give the certificate authority a Name.
  • Recommended: extend validity.
    • Select Let me override defaults.
    • Unselect Make this CA the default.
    • Change Validity Period to up to 7300 days (20 years).
  • Choose a profile.
  • You can just leave defaults for the rest of the options. Next, Next, Next…
  • Click Show CA Certificate to verify this succeeded, then close the Certificate Assistant.

2. Create the Leaf Certificate to bundle with the App

  • Click Keychain Access > Certificate Assistant > Create Certificate.
  • Change Identity Type to Leaf.
  • Change Certificate Type to Custom, and choose ~/Library/Application Support/Certificate Authority/<your CA name>/<your CA name>.certAuthorityConfig
  • Recommended: extend validity.
    • Select Let me override defaults. Continue.
    • Change Validity Period to up to 7300 (20 years).
  • Select an Issuer: <your CA name>
  • Accept the defaults for the rest of the options. Next Next Next…
  • Destroy the private keys for both the Certificate Authority and Leaf Certificate – you don’t need them anymore.

Ok now we’ve got the raw materials needed, and we just need to convert them to the formats needed for the App.

3. Export Leaf Certificate .cer and Add to App Bundle

  • In Keychain Access, find the Leaf Certificate “certificate” 
  • Right Click > Export
  • Select File Format .cer
  • Export to a location in your project repo (you want this certificate tracked in source control)
  • In Xcode, Add Files to <project>
  • Select your App target in “Add to Targets:”

The certificate will now be included in your app bundle, but since the user hasn’t taken any action to trust that certificate, its SecTrustResultType (in the code sample below) is kSecTrustResultFatalTrustFailure.

This will be our indication that the user does not have the debugging profile installed on the device.

4. Export CA Certificate .cer and Add to Debug Profile

  • In Keychain Access, find the Certificate Authority “certificate”
  • Right Click > Export
  • Select File Format .cer
  • Export to a Location in your project repo
    • Again, you want to track this file in source control so you never lose it, but you might want to keep it in a new directory from the leaf cert to avoid confusion.
  • Do NOT add this file to your App target.

5. Install Custom Profile on Desired Phones

  • Download Apple Configurator 2
  • Click File > New Profile
  • Provide a Name and fill in any relevant General details
  • In the Certificates tab, click Configure
  • Select the <CA Certificate>.cer file you exported in Step 4
  • Save the profile, ie “MyDebugProfile”

6. Add Objective-C Class to check status of profile

Add the following function somewhere in your project. You might add it to an existing utility, or create something like a DebuggingDetector.

// Based on https://stackoverflow.com/a/31570347/782278
- (BOOL)IsMobileConfigInstalled {
    NSString* certPath = [[NSBundle mainBundle] pathForResource:@"YOUR_LEAF_CERTIFICATE_NAME_HERE" ofType:@"cer"];
    if (certPath == nil) {
    	return NO;
    }
    NSData* certData = [NSData dataWithContentsOfFile:certPath];
    if (certData == nil) {
    	return NO;
    }
    
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) certData);
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    SecTrustRef trust;
    
    OSStatus err = SecTrustCreateWithCertificates((__bridge CFArrayRef) [NSArray arrayWithObject:(__bridge id)cert], policy, &trust);
    print("Error Status?: \(err)")
    
    SecTrustResultType trustResult = -1;
    
    err = SecTrustEvaluate(trust, &trustResult);
    
    CFRelease(trust);
    CFRelease(policy);
    CFRelease(cert);
    
    print("Error Status?: \(err)")
    if(trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed) {
    	return YES;
    } else {
        return NO;
    }
}

7. Install the profile on the debugging devices

  • In Configurator, select the Device
  • Click Add, and choose the <MyDebugProfile>
  • The device will advise you the profile has been downloaded, but you’re not done yet.
    • At this point the trust result is still kSecTrustResultRecoverableTrustFailure
  • Open iPhone Settings then go to General > Profile
  • Choose <MyDebugProfile>
  • Tap Install, enter passcode, confirm Install, Install, Done.

The trust result will now be kSecTrustResultProceed. You have successfully enabled debugging on the device!

Even More Options…

There are surely many ways to skin a cat on this one. Here are some other ideas I’ve dreamt up but haven’t tried:

  • Leave the debugging features plainly available and protect them behind a password or PIN code.
  • Enable debugging by remotely sending messages to your App from another device such as Apple Watch or a computer.
  • Write a separate App with the debugging features and link your Apps to each other (think of how Google Drive and Google Docs are able to coordinate between Apps, for example)
  • Key debugging off of some other metadata you feel you can trust (only on your home wifi? Only on a given MAC address or SIM card? These are both probably bad ideas, but something along these lines…)
  • Write a separate App that simply enters a value into your device’s Keychain. Then check for that value in your primary App. Distribute your enabler App through TestFlight.

Do you have better ideas for how to do this? Did I miss something obvious? What else have you tried? Leave a comment below!


This article contains solutions described in this StackOverflow post and this Dev forums post.


If your job includes code review, consider also reading: How to do High-Bar Code Review Without Being a Jerk

Using a Swift PropertyWrapper to ensure a closure is only called once

Learn: how to write a property wrapper that avoids boilerplate for your asynchronous callback code.

The Solution Code

Want to skip to the answer? Variables marked with this property wrapper will be destroyed after they are read once. Use with caution!

@propertyWrapper
struct ReadableOnce<T> {
    var wrappedValue: T? {
        mutating get {
            defer { self._value = nil }
            return self._value
        }
        set {
            self._value = newValue
        }
    }
    private var _value: T? = nil
}

Now lets see how we got here.


Avoiding multiple invocations of a completion block

Let’s say we have a class that does some work, and then invokes its completion block.

class NumberChecker {
    private var completion: ((Error?) -> Void)
    
    init(completion: @escaping ((Error?) -> Void)) {
        self.completion = completion
    }
    
    public func check() {
        if someCondition {
            handleFailure(NSError(domain: "MyDomain", code: 1))
        }
        handleSuccess()
    }
    
    private func handleSuccess() {
        self.completion(nil)
    }
    private func handleFailure(_ error: Error) {
        self.completion(error)
    }
}

This code has a secret bug in its check method. Did you notice it?

    public func check() {
        if someCondition {
            handleFailure(NSError(domain: "MyDomain", code: 1))
        }
        handleSuccess()
    }

If someCondition gets evaluated as false, the completion block will be invoked twice – once by handleFailure(), and again by handleSuccess(). This is a bug – the completion block should only get called once.

Now obviously in this trivial example you could fix this by just using an else statement. But in The Real World, your code may be much more complicated, and it may be harder to make the calls of the completion block mutually exclusive.

Perhaps your code is asynchronous, and completes when the first of two operations returns a result, for example.

I’m not judging how you have gotten yourself into this problem – just providing a neat solution.

How to solve this problem

Our approach here is going to be to ensure the completion block can only be called once by destroying it once it has been used. One perfectly reasonable approach to this solution is as follows:

class NumberChecker {
    // Make the completion closure optional
    private var completion: ((Error?) -> Void)?
    
    init(completion: @escaping ((Error?) -> Void)) {
        self.completion = completion
    }
    
    public func check() {
        if someCondition {
            handleFailure(NSError(domain: "MyDomain", code: 1))
        }
        handleSuccess()
    }
    
    private func handleSuccess() {
        self.completion?(nil)
        self.completion = nil
    }
    private func handleFailure(_ error: Error) {
        self.completion?(error)
        self.completion = nil
    }
}

Here we are simply setting the completion closure to nil after we use it. This is fine, but requires us to add extra boilerplate around each usage of the callback. It is also error-prone, since we could easily forget to include the self.completion = nil and end up with a partial solution.

So instead, lets create a property wrapper that changes the behaviour of the getter for that variable.

What is a property wrapper?

Property wrappers are structs that use Generics to define extra behaviour around the usage of some arbitrary property. They are defined separately from the property they act on, and can be re-used easily for different properties, even of different types.

NSHipster has a great write-up on the background and purpose of this Swift feature if you haven’t seen it before so I won’t go into more of those details here.

The ReadableOnce property wrapper:

I included this at the top, but lets look in more detail at our property wrapper:

@propertyWrapper
struct ReadableOnce<T> {
    var wrappedValue: T? {
        mutating get {
            // This defer is the magic. It lets us 
            // erase the value AFTER it is returned.
            defer { self._value = nil }
            return self._value
        }
        set {
            self._value = newValue
        }
    }
    // Use a private backing variable that we can modify here.
    private var _value: T? = nil
}

The property wrapper definition is just a struct marked with @propertyWrapper. It has a special var called wrappedValue, which is the place we have to implement whatever our custom logic is.

In our case, we want to put our magic into the get method of the value, so that the value is destroyed after being returned the first time. The set method should behave as normal, so we just add a private backing variable, here _value.

The defer keyword

Deferred closures are executed at the end of the scope they are declared in. In our case, the defer keyword allows us to execute our cleanup code after the return value is returned.

Using @ReadableOnce in the NumberChecker

Here’s what our NumberChecker looks like if we use our new property wrapper:

class NumberChecker {
    @ReadableOnce private var completion: ((Error?) -> Void)?
    
    init(completion: @escaping ((Error?) -> Void)) {
        self.completion = completion
    }
    
    public func check() {
        if someCondition {
            handleFailure(NSError(domain: "MyDomain", code: 1))
        }
        handleSuccess()
    }
    
    private func handleSuccess() {
        self.completion?(nil)
    }
    private func handleFailure(_ error: Error) {
        self.completion?(error)
    }
}

It would be a good idea to fix that secret bug, but even with it this code will now work as expected.

Downsides of this approach

The biggest concern with this is precisely the magic that makes it useful. It is unintuitive that you can only read once, and that reading could mutate the value.

The following code demonstrates the most likely pitfall:

private func handleSuccessPoorly() {
    if self.completion != nil {
        // The completion was just read (and set to nil).
        // So the next line will silently do nothing!
        self.completion?(nil)
    }
}

Overall, I think this property wrapper is very useful, as long as it is clear to the developer that it is being used.

So basically:

With great power comes great responsibility.

Uncle ben. yes. from spiderman.

Possible Improvements

There are a number of things you might continue to experiment with to improve this solution:

  • Use a different property wrapper to accomplish the same task
  • Implement something like a callThenDestroy() method on the type you want to store
  • Try @dynamicCallable (see this Hacking With Swift post for info)
  • Thread safety has not been considered in this implementation. It would be possible for two threads to both get access to the variable before either of them set it to nil.

This article is based on a code sample shared with me by Josh Caswell (https://github.com/woolsweater)


Have you tried any of the above? Do you have a better solution? Please leave a comment below!


If you liked this post you might also like my guide on how to do high-bar code review without being a jerk.

iPhone Battery Icon Drained

iOS Battery Issue – SOLVED

I upgraded my iPhone 5 to iOS 6.1.2 this week, and was disappointed to see a huge drop in battery life.

Previously, I could easily make it through the day without charging but after upgrading I was down to 3-4 hours on standby and my phone was constantly over-heating!

I FIXED IT!

UPDATE: Definitely worked for me. 99% after 1.5 hrsstandby. 100% after 1 hrs regular usage!

UPDATE: Added confirmed solutions for iOS 6.1.3 and iOS 6.1.4.

I had read that a bug in iOS 6.1.1 was causing problems with battery life and that it was related to having a Microsoft Exchange Email on your phone. So I thought, maybe this is a similar bug. Different things seem to be working for different people, but it looks like there’s some sort of (UPDATE: try disabling Push email. See below)

Solution 1 – Re-Install Microsoft Exchange Email

Add Account

Step 1 – Delete Exchange Email Account

Go to Settings > Mail, Contacts, Calendarsand click on your Exchange email account. Make sure you have your emails backed up on the server (login to the email account on your computer) and then click Delete Account.

Step 2 – Reset Phone

Hold down both the Lock and Home buttons until the phone shuts off. Then turn it back on.

Step 3 – Set up Exchange Account

Create New Exchange Email

Return to the Mail, Contacts, Calendars page and click Add Account. Enter all your account information and voila! Your battery life should be back up again!

Didn’t work? Try it with all of your Hotmail and Gmail accounts at once.

Still didn’t work? Try the next solution.

Solution 2 – Disable Push Email 

A very popular solution seems to be disabling Push email. Go to Settings > Main, Contacts, Calendars > Fetch New Data. Set Push to OFF. Then click Advanced, and make sure that each email is set to Fetch instead of Push.

Still didn’t work? Try these other solutions.

Solution 3 – Router firmware

Multiple users on the Apple forums have reported seeing the WiFi and 3G/LTE icons switching back and forth every 15 seconds or so while connected to a WiFi network. This seems to be caused by out-of-date firmware for their NetGear routers, which caused the iPhone to constantly reconnect to the network. Their solution was to update or revert your router’s firmware and hope for the best. This has worked, but it only applies to the alternating icons problem.

Other Solutions

Lot’s of other options have cropped up, all related to network activity. Other people online have found these things to be solutions:

  • Quit Apps with Hanging Downloads. If you have iTunes U, iTunes Match, or the App Store running with downloads waiting in the background, they are fighting for data. To quit these apps, Double-Tap the Home Button to show the multitasking tray,Press and Hold an App Icon, and tap the red QUIT icons.
  • Update to the latest version of iOS. Apple does in fact try to fix these problems so the latest update is always worth a shot.
  • Disable Documents in the Cloud.
  • Remove the SIM card, disable all network connections, restart iPhone, insert SIM card (only seems relevant to people outside the US)
  • Remove ALL email accounts, restart iPhone, re-install ALL email accounts
  • Sign out of Facebook, restart iPhone.
  • Do a completely fresh install of iOS (Don’t forget to back up to iTunes or iCloud)
  • In iTunes, go to edit>preferences>devices and check “prevent ipods, iphones and ipads from syncing automatically”
  • Check that the SIM card is seated properly in it’s tray. If the contacts aren’t cleanly connecting to the phone, you could be wasting energy. This is especially important if you cut your own SIM card out of the old one.
  • DONT USE AN ALUMINUM CASE. This aluminum blocks the 3G signal from reaching your phone. When the phone is struggling to get service, it works much harder and runs the radio signal at a higher power level. Your case is forcing your phone to work too hard!
  • NOTE: You may need to let your battery die and then recharge before the changes take effect!!

What worked for you?

Other solutions?

Leave a comment below!

Powered by WordPress & Theme by Anders Norén