Posts

  • Getting Started with Injection for Xcode

    Code Injection is the ability to add new code to a running app without relaunching it. In iOS development, large project compile time can be quite long, and injection can offer a huge productivity gain during development.

    There are two projects I’m aware of that support this: Injection for Xcode and DyCI. I’ve spent more time with Injection for Xcode, so that’s the one I’m focusing on here.

    In this post, I’m going to focus primary on how to get your project setup for successfully running your first injection.

    Overview

    A quick read of the project’s GitHub Readme offers the following overview for how this works:

    It performs this by parsing the build logs of the application to determine how a source file was last compiled. With this it wraps the result of re-compiling into a bundle which is injected into the application using the dynamic loader. At this point there are two versions of a class in the app, the original and a new modified version from the bundle. The modified version is then “swizzled” onto the original class so changes take effect.

    For a deep technical dive into the implementation, @orta has a great code spelunking article that walks through the codebase. He does a great job making a complex project more approachable.

    Setup

    Xcode 7

    Getting injection setup on Xcode 7 is straightforward, particularly if you already have Alcatraz installed. Once again, @orta has a quick introduction blog post and video tutorial that will help you get setup.

    Xcode 8

    For Xcode 8 it’s more complicated. This year Apple introduced an official extensions API, but it is currently limited to source editor extensions. Apple has also signed Xcode to prevent unsigned code from being injected and executed (remember XcodeGhost 👻?). These are good steps for security, but also prevent tools like injection.

    We can get code injection working in Xcode 8 with the following steps:

    1. Unsign Xcode 8.

      In my previous post Safely Running Extensions in Xcode 8, I describe how to do this and ensure that your workflow still provides the safety gains of using a signed copy of Xcode for deployment builds.

    2. Build Injection for Xcode from source. Start by downloading or cloning Injection for Xcode from GitHub.

      Open the project file in Xcode 8. Codesigning is now required, so udpate the plugin’s project file to include your codesigning, and update the template project that will be executed by your host project later to include codesigning as well. These two projects are InjectionPluginLite/InjectionPlugin.xcodeproj and InjectionPluginLite/iOSBundleTemplate/InjectionBundle.xcodeproj.

      If you have multiple signing identities (such as a personal identity and a company identity), then you may need to modify the Run Script build phase on the InjectionPlugin.xcodeproj to reference a specific identity:

      Build the scheme InjectionPlugin, which will install the bundle to ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/InjectionPlugin.xcplugin

    3. Approve the plugin to run. Now that the plugin is installed, quit and re-launch Xcode 8. When it relaunches, you’ll be presented with a dialog asking whether to load the plugin bundle. Choose “Load Bundle”.

      You should be able to verify that the plugin is successfully installed by going to the Product menu bar item, and you should have two new items at the bottom of that menu: Injection Plugin and Inject Source.

    4. Inject from your own project. Open your own project now, and let’s set it up to respond to injection. Add the following to one of your view controllers:

      func injected() { print("I've been injected: \(self)") }

      (This function is a special function that is called when injection is triggered. You can use it as a hook to do other actions in your app. For example, you may want to add code here to redraw a screen that you are currently developing.)

      Build and run the project. With that source file still visible, select Product > Inject Source, or press the hotkey ^=. You should see something like the following logged to the console:

      I've been injected: <InjectionDemo.ViewController: 0x7fc8ea505ec0>

      If so, success!

      However, you may see a codesigning error like this:

      If so, this is indicative of multiple iOS codesigning identities. By default, the injection bundle is trying to inject based on a generic developer profile name, but if you have multiple, you need to specify which to use. In your repo, find the file iOSInjectionProject/x86_64/identity.txt and modify iPhone Developer to be your fully qualified signing identity name. For me, that’s iPhone Developer: John McIntosh (XF7MPRXLNG).

      After saving that change, you should be good to go.

    Learn More

    The injection plugin’s GitHub page has a few good examples of use-cases for getting started with workflows to speed up your development.

  • Safely Running Extensions in Xcode 8

    Xcode 8 disables the ability to run 3rd party plugins (such as Alcatraz), in favor of providing an official extensions API. From what I’ve heard, it sounds like the Xcode engineers are open to community feedback and want to provide the extension points that the community needs. This is great for security and for preventing the next XcodeGhost 👻, however, only the source editor extension is available right now, which means that some of our favorite community plugins are disabled after upgrading to Xcode 8.

    These plugins are prevented from running because Xcode 8 is signed to prevent any unsigned code from being injected and executed (which is how community plugins work). We can enable plugins again by unsigning Xcode, but once Xcode is unsigned, we lose the security benefits that come with Apple’s changes. So, what can we do?

    The plugins that I use are things that I want to have while I am actively developing, and the security benefits I only really care about when I am building deployment builds for a beta or public release.

    In light of this, I’ve created xcunsign. This is a small tool that unsigns (and later restores) an installation of Xcode. With it, I can unsign while I’m actively coding and debugging, and then restore to the signed binary before I build a deployment. Even better, I can integrate xcrestore into my fastlane configuration, so that I know that all deployment builds will be built with the signed Xcode binary.

    Setup

    Download the repo from github and optionally include the scripts in your PATH.

    xcunsign

    To unsign, call the script, passing in the version of Xcode that you want to unsign. The script will find the copy of Xcode in the /Applications directory with that version, and unsign it. It will keep a copy of the original, signed binary as Xcode.signed next to the unsigned binary as Xcode.app/Contents/MacOS/Xcode.signed.

    xcunsign 8.0
    

    This will also update your Xcode installation’s icon, to indicate that Xcode is currently in an unsigned state.

    xcrestore

    To restore the signed binary, the original copy of the Xcode binary will replace the unsigned copy, restoring your Xcode installation to its original, “official” state.

    xcrestore 8.0
    

    This will also restore Xcode’s icon to the standard icon, to indicate that you are again running a signed copy of Xcode.

    Automating

    If you are doing your deployments from a CI machine, you should keep that installation always signed. If you ever need to do a deployemnt locally though, you’ll want to verify that you are building with a signed copy of Xcode. Fastlane has an action called verify_xcode which can be used to fail the build if Xcode is currently unsigned. Just add this action to your Fastfile to guarantee that you are shipping with a signed copy.

    You could also call xcrestore from the Fastfile’s before_all and call xcunsign from the after_all so that your development environment is restored after a build is completed. I’m planning to wrap this into a fastlane plugin, and will put up a new post when that is ready.

  • Reboot

    It’s time reboot my web site. Well, let’s be honest – it’s past time. The old site was primarily geared toward promoting my work as a freelance iOS developer, but I’ve been working with Mutual Mobile for over 3 years now, so I’m not currently looking for new contract work.

    I would like this to become an outlet for doing a bit more writing. It’s been awhile since I have done much writing, and I have slipped into a habit of feeling like I need to perfect something, otherwise it’s not worth posting. Between the burden of perfection and the friction of posting to the web, it rarely happens. In fact, the last post on the previous blog was from 2011.

    To combat this, I’m trying a new approach and striving to post more frequently with less concern for perfection. In addition, I’m trying out some new tools for web management. As a developer, I’m more comfortable writing in a basic text editor than a web interface, so I’m trying out Jekyll which allows me to write locally, and publish to the blog by pushing to a git repo. In theory, I think sounds like a frictionless workflow for me, so we’ll see how it works out in practice.

    My professional life is centered around iOS development and team building, so I suspect that most of the content here will be related to that, with brief intermissions for photography along the way (portfolio on 500px).