Coding with Titans

so breaking things happens constantly, but never on purpose

HowTo: Add CocoaPods private ZIP file as dependency

As an iOS developer you often hear about dependencies management. Looking into this subject deeper quickly leads towards CocoaPods and Carthage as two very popular solutions, yet build with totally different mindsets and assumptions. First one is a centralized solution, with full list of existing potential dependencies, while the latter one simply downloads the GitHub repo locally and builds it or only references the prepared binaries.

And even if I like and used Carthage in my previous projects, I had to put it on hold for some time, because of its lack of support of XCFrameworks and in general the Xcode 12 style of development. This problem was resolved starting from May 2021 as of 0.38 release, yet a bit too late for me. Unfortunately because of this delay I have lost the ability to quickly add a dependency into a project, where this dependency could be defined as local or remote file and be accessed privately using credentials defined in ~/.netrc (as described here).

CocoaPods did support XCFrameworks and Xcode 12 from the very beginning. That’s why it helped me to solve a big issue I stumbled - how could I share my precompiled library (in mentioned so many times here - XCFramework style) with the customer without exposing a full source code? I know there is such a way as hosting private CocoaPods Specs repository. But its very complex and requesting customers to set it up on they development machines, along with CI servers was far too cumbersome. I was looking for some easier to use solution. Browsing the features that were added or requested by community wasted few of my evenings. However it revealed that there might be something really straightforward, yet totally not documented.

As of 2019 and CocoaPods 1.3.0 the most important for me pull-request was merged and silently published (pr#89, issue#91). It enabled the way to inject custom headers into any HTTP connection while accessing dependency defined in a Podfile. A bit of misfortune, as I said it above, is that this feature is totally skipped by documentation and in my opinion it should get much more attention.

That’s why I am describing it here to let you know about this marvelous trick. Using it will let you host the precompiled XCFramework of your library zipped along with a dedicated PodSpec inside it. So all the customer will need to do, is to point to the released file and apply an Authorization header to get the access to the resource:

pod 'GreatSDK', :http => 'https://codetitans.pl/sdk/GreatSDK_1.0.1.zip',
      type: :zip, :headers => ['Authorization: Basic <access_info>', 'Accept: application/octet-stream']

Where here the <access_info> is simply base64 of the of the user name and password combined into a single UTF8 octet sequence with “:” as separator (» Wikipedia).

That’s the perfect solution, if you host the files yourself. Could is be also used to access GitHub releases? Well yes and no. I have found two downsides of this approach:

  1. GitHub requires access over GitHub Token with Full Repo rights. Do you want to share such a key with customers? It could be potentially used to clone the whole repo.

  2. The URL defined as dependency must be final. I don’t know how to make it follow any redirects. That means, that our beautiful and easy to remember release of the library, for example:

    https://github.com/phofman/great-sdk-ios/releases/download/1.0.1/GreatSDK.zip
    

    isn’t something we can directly embed inside Podfile. We would need to convert this URL into an URL pointing to an asset. This could become of a form:

    https://api.github.com/repos/phofman/great-sdk-ios/releases/assets/312344508
    

    Just in case - here is a script that can help with the conversion.

    If you know any way of overcoming this limitation, please let me know.

For GitHub release the Podfile reference would look like following:

pod 'GreatSDK', :http => 'https://api.github.com/repos/phofman/great-sdk-ios/releases/assets/312344508',
      type: :zip, :headers => ['Authorization: token <token_with_full_repo_access>', 'Accept: application/octet-stream']

Hope this helps you build a better iOS library too.

👍