Code golfing with IPA

Recently I came across this Hacker news article, where the goal was to bring down the size of the apk, that can be installed on an android phone. People are usually familiar with Code golf. This is somewhat similar, and this gave me the idea to try this for an iOS application.

So, first I started out with a basic Single View application, that comes up whenever we choose a new project from Xcode, but I chose Swift as my programming language, since I have been used to it and developed few applications on it. To make things fast and easy, I decided not to export the archive built into an IPA, (mostly because I do not have a developer account and it’s too costly for me to afford it now). So to get the ipa file, I found out where the bundle is present from Xcode and tried zipping the bundle into a zip and rename it to a .ipa file. Theoritically, this is what Xcode also does, it creates your bundle file and compresses it to an ipa file. To verify if the ipa file will also be of the same size or not, I used iTunes to get the ipa file and verified it’s size. Both files were of same size. So building the ipa file now is easy, even though we don’t have the developer account. Of course I am not denying the features that we get having an Apple developer account, like App thinning would not be available to us, but all that can come later, once we have optimized things that we can control.

So getting back to the Swift project, just building that sample application, which did not have any source code written by me in it, apart from the auto generated AppDelegate and ViewController files, the size of the application was around 28Mb. On inspecting the app size, I got the respective sizes of it’s contents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ls -lah iOSGolf.app/*
-rw-r--r-- 1 akhil staff 1.1K Oct 15 23:25 iOSGolf.app/Info.plist
-rw-r--r-- 1 akhil staff 8B Oct 15 23:25 iOSGolf.app/PkgInfo
-rw-r--r-- 1 akhil staff 376B Oct 15 23:25 iOSGolf.app/archived-expanded-entitlements.xcent
-rw-r--r-- 1 akhil staff 7.5K Oct 15 23:25 iOSGolf.app/embedded.mobileprovision
-rw-r--r-- 1 akhil staff 307K Oct 15 23:25 iOSGolf.app/iOSGolf
iOSGolf.app/Base.lproj:
total 0
drwxr-xr-x 4 akhil staff 136B Oct 15 23:27 .
drwxr-xr-x 10 akhil staff 340B Oct 15 23:29 ..
drwxr-xr-x 5 akhil staff 170B Oct 15 23:27 LaunchScreen.storyboardc
drwxr-xr-x 5 akhil staff 170B Oct 15 23:27 Main.storyboardc
iOSGolf.app/Frameworks:
total 142408
drwxr-xr-x 11 akhil staff 374B Oct 15 23:27 .
drwxr-xr-x 10 akhil staff 340B Oct 15 23:29 ..
-rw-r--r-- 1 akhil staff 46M Oct 15 23:25 libswiftCore.dylib
-rw-r--r-- 1 akhil staff 1.1M Oct 15 23:25 libswiftCoreGraphics.dylib
-rw-r--r-- 1 akhil staff 252K Oct 15 23:25 libswiftCoreImage.dylib
-rw-r--r-- 1 akhil staff 633K Oct 15 23:25 libswiftDarwin.dylib
-rw-r--r-- 1 akhil staff 2.5M Oct 15 23:25 libswiftDispatch.dylib
-rw-r--r-- 1 akhil staff 18M Oct 15 23:25 libswiftFoundation.dylib
-rw-r--r-- 1 akhil staff 445K Oct 15 23:25 libswiftObjectiveC.dylib
-rw-r--r-- 1 akhil staff 304K Oct 15 23:25 libswiftQuartzCore.dylib
-rw-r--r-- 1 akhil staff 683K Oct 15 23:25 libswiftUIKit.dylib
iOSGolf.app/_CodeSignature:
total 16
drwxr-xr-x 3 akhil staff 102B Oct 15 23:27 .
drwxr-xr-x 10 akhil staff 340B Oct 15 23:29 ..
-rw-r--r-- 1 akhil staff 7.9K Oct 15 23:25 CodeResources

As you can see the Frameworks folder itself takes up almost all the space. This folder contains the Swift runtime libraries. Swift apps contain these libraries because Swift is not ABI stable yet. If a language is not ABI stable, it means that you cannot guarantee that the binaries compiled on a certain version of that language will work perfectly with binaries compiled on another version of that language. You can read about what is ABI and why does it’s stability matters for a programming language and how Swift is proposing to achieve it over here. I can try app thinning and other approaches recommended by Apple, but that would require Apple developer account. So I have decided to move away from Swift and try to work on Objective C. Doing the same initial step of choosing a single view application and building the app, the archive was at 26623 bytes. So for now, until the Swift’s ABI is stable, Objective C binaries might be of smaller size. This is what the binary contains

1
2
3
4
5
6
7
8
9
10
11
12
ls -lah iOSGolf.app/*
iOSGolf.app/Info.plist
iOSGolf.app/PkgInfo
iOSGolf.app/archived-expanded-entitlements.xcent
iOSGolf.app/embedded.mobileprovision
iOSGolf.app/iOSGolf
iOSGolf.app/Base.lproj:
Main.storyboardc
iOSGolf.app/_CodeSignature:
CodeResources

As you can see no more frameworks included. Now I started looking at the build settings of the app for settings that would help in reducing the binary size. First thing I found was Generate debug Symbols in Code generation column of Build settings. Debug symbols are just mappings from the instructions in the binary to the lines in the source code, these are necessary to make sense of crash reports or while attaching a debugger and running a step through execution of your binary. For iOS, these debug symbols are created in a file called dSYM and zipped with the app. I wanted to disable this for debug binaries, but a better way to do this was to build Release builds on running them. This is because, there is an optimization setting just below the Generate debug symbols section. This has different values set for both release and debug. For debug the value is set to None, and Release is set to Fastest, Smallest. None as specified does not do any other optimizations on the compile phase, other than reducing the cost of compilation (which by default almost all compilers do that). This makes the statements in the binary independent, so that new variables can be assigned or change the program counter after setting a breakpoint and I didn’t want to make all these changes and when debugging would be useful in the future it would be difficult to undo all these changes. So I decided to make release builds on running the app from xcode. The size was now down to 23432 bytes.

The next thing I wanted to try was to restricting the architecture. Older iphones like iPhone 3GS, iPod touch were on armv7 architecture. Phones like iPhone 5C were on armv7s architecture. The newer phones are on arm64. So I decided to restrict the architecture to arm64 because then my binary won’t include instructions that are necessary for those devices. I think this is where Apple’s ENABLE_BIT_CODE is helpful. When you build from BIT_CODE, your binary basically is compatible with any kind of architecture. When you submit to the app store, Apple decides which architecture to install on your phone and then sends the particular architecture’s binary to be installed on that phone. Of course, this would result in a heavy binary being built before you submit to the App store. Anyhow, setting supported architecture to arm64 did reduce the size but not by much. Size was not at 21006 bytes.

Now I decided to look at the contents of my archive again. I found these Main.storyboard and Launch.storyboard files present. These storyboard files represent your app’s view. Now my app does not need two storyboards, because it will be showing up an empty screen and the UI is not my problem for this app. So I decided to edit my Info.plist to use the same storyboard for both launch and main and then I deleted one of the storyboard files that is not being used. After this, the size reduced to 20013 bytes. I tried few things after this like disabling Code coverage support and removing some extra contents in my Info.plist. Finally the size reduced to 19642 bytes.

The highest contributors to size now were the app binary, storyboard files and the provisioning profile. I wanted to see if I could get rid of the storyboard file from my app. On looking at the ViewController class, I saw that it was an interface that implements the class UIViewController. So you really don’t need the ViewController class if you can replace all it’s call sites with UIViewController. All I had to do was change the class name for the scene in the storyboard to UIViewController. That didn’t help. Of course my source code was small, but my binary was still around the same size. Now I wanted to see if I could remove the AppDelegate file and see if that helped in reducing the size of the binary. On looking up where the AppDelegate class is referenced in the source code, it was inside the main.m file as

1
2
3
4
5
6
7
8
9
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc,
argv,
nil,
NSStringFromClass([AppDelegate class])
);
}
}

So if I did the same thing again here, I can get rid of my AppDelegate file. So I removed AppDelegate from the above piece of code and used UIResponder which is what the AppDelegate inherits from. Now the code looked something like this.
1
2
3
4
5
6
7
8
9
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc,
argv,
nil,
NSStringFromClass([UIResponder class])
);
}
}

I removed the all the AppDelegate files and tried running the app. The app was launching but this time there was a black screen, instead of the white screen when it launched and was throwing me a warning saying that
You need to provide a UIWindow for your application.

Basically I have no window that shows the UI now, which is still fine because I do not care about the UI since it is not the main goal of this project, and no UI meant no storyboard files. So I removed all the contents of the Assets.xcassets and Main.storyboard and built the app now. It worked although it was showing me the same warning as above. This did help a lot, my app’s size was now reduced to 16443 bytes. The app binary still taking up half of the space and ther other half by the provisioning profile. Now I just print out the whole binary to see what does it contain. Most of it looked like Apple’s root certificate and my developer certificate and then some references to classes like UIResponder. The only thing I can get rid of now is all these unused class definitions in the binary. Their presence would mean that there is some code which is referencing the header file that contains these definitions. And here is only one file which contains any kind of code (atleast that makes sense to me) and that is the main.m file. As suspected it was importing the UIKit.h file and removing this would throw an error because I was still referencing UIResponder from my main function.

That’s when it hit me, this is like any other C programme. It has a main function which is the starting point of entry for the execution process. I have been ignoring my basic concepts so far. I looked at the function definition of UIApplicationMain that is called in the function and it says that this is the main entry to the app and once your app enters this function it never returns it, even when it is in the background. It does have an int return type, but it never reaches it’s end. This function would be responsible for starting your event loop and doing all other initialisations that come in the app’s launch cycle. So if it is like any other C programme, then the following code should work.

1
2
3
int main(int argc, char * argv[]) {
return 0;
}

This code did not have the UIKit header file included. It compiled and when I ran it, the app launched and exited. So it worked. Now the app size was at 15807 bytes. There was no function definitions that were there before in the binary. It just contains the certificates now.

This was where I gave up, I had no clue onto how to edit these things, because they are essential for the app to be installed on the phone. I could neither get rid of the provisioning profile which took the other half of the size. The only thing now left was to enroll into the developer program and then build an ipa file that is ready for app store distribution and then try different things provided by the Apple developer account.

I have pushed the current app to iOSGolf repository. Feel free to fork it or submit PR to it if you think I have missed out on something that would generate in an app binary of lesser size.

Share Comments

Trust Evaluation in iOS devices

Apple’s Security Framework provides enough APIs to implement your own methods to secure the data your app manages. One important component of managing certificates is verifying if the certificate is valid or not. For verifying these certificates it provides an API called SecTrustEvaluate.

Now this function evaluates all the certificates in your chain of trust from the end SSL certificate to the root certificate. For each certificate, it evaluates whether it has been revoked or not. It chooses different methods for this, in an incremental approach. First it checks them in it’s cache, then in the set of certificates that the system has in the keychain and then it hits apple servers to verify them. Now, this is a network operation and remember that this operations is applied for all the certificates in your trust chain. It has a timeout of 7 seconds. The network operation uses something called the OCSP Protocol to evaluate the revocation status of each certificate. Unlike OSX, iOS does not have support to choose between CRL or OCSP. OCSP is enabled by default for iOS devices. In brief, the OSCP protocol is as follows. The device sends an HTTP request to a OSCP responder, like http://ocsp.apple.com and gets the response regarding the trustiness of the certificate. Since the request and response do not contain any sensitive information, HTTP can be used for these requests. These devices use a best effort level regarding the certificates. If they could not find any information regarding each of these certificates, then it considers it as trust worthy. These certificates are then added to its cache or the keychain.

When using this API, it should be noted that this can be a blocking request. So it is not a good idea to use this on the main thread. Or, you could use SecTrustEvaluateAsync for this job.

Share Comments

Mortal and Immortal Symbols in Ruby

Symbols are one of the most powerful datatypes that Ruby has to offer. Compare
and lookup operations are faster when symbols are used rather than strings. You
can check this post here know more about symbols and their performance when compared to strings.

One disadvantage of using symbols is that, they live in the program’s memory
for the entire life cycle of it’s execution. This is one of the reasons for faster symbol lookups. This also was the reason for many denial of service vulnerabilites in Ruby, for example this one.

From Ruby 2.2, there is a new concept of Mortal and Immortal Symbols. Internally
ruby has three types of symbols. Mortal dynamic symbols, which are symbols
created dynamically by user, Immortal dynamic symbols are the mortal dynamic symbols
that have been promoted to immortal dynamic symbols and then there are immortal
static symbols, which are function names, class names etc. The GC only collects the
Mortal dynamic symbols. An example of such symbols are symbols created by to_sym method.

Share Comments

Hello World

Hi there! I just setup this webpage. As of now, I haven’t started writing any blogs yet, but the idea is to start soon. Most of them will be about stuff related to Programming or Math.

So, until then stay awesome and thank you.

Share Comments