Colby Jordan made a remark on BlueSky that got me thinking. Why was the simple app for his LG dryer so big? I decided to investigate.
Getting the app
So which application Colby is looking at? Since he's probably on an iPhone (though I didn't check...!) I suspected that it was the LG ThinQ iOS app.
But hang on, how do I get the application files on my computer. Enter ipatool - it makes it easy to find and download the .ipa files that live behind your mobile applications and it's all open source.
So we can install ipatool on our mac with:
==> Fetching downloads for: ipatool
==> Downloading https://ghcr.io/v2/homebrew/core/ipatool/manifests/2.2.0
Already downloaded: /Users/nclarey/Library/Caches/Homebrew/downloads/ca8a713d23ab508e0025bff0daadc7482ea0e22693259419a55ceff4ecadf9ce--ipatool-2.2.0.bottle_manifest.json
==> Fetching ipatool
==> Downloading https://ghcr.io/v2/homebrew/core/ipatool/blobs/sha256:d108282c96e19011ef57d18b0515746bc9d8207cbc6dc6e4bed2abfed460be72
Already downloaded: /Users/nclarey/Library/Caches/Homebrew/downloads/c91caaa6c0edd87ee265e78b6ff5bbc9ec7882bf88736120f1480c27caf14d32--ipatool--2.2.0.arm64_sequoia.bottle.tar.gz
==> Pouring ipatool--2.2.0.arm64_sequoia.bottle.tar.gz
🍺 /opt/homebrew/Cellar/ipatool/2.2.0: 9 files, 9MB
Then we need to login to our appstore account:
[nclarey@destructor:2321 ~/Development/lg-dryer]$ ipatool auth login --email=nick@clarey.org
2:15PM INF enter password:
2:16PM INF email=nick@clarey.org name="Nick Clarey" success=true
Find the correct package:
[nclarey@destructor:2324 ~/Development/lg-dryer]$ ipatool search "lg dryer"
2:17PM INF apps=[{"bundleID":"com.lgeha.nuts","id":993504342,"name":"LG ThinQ","price":0,"version":"5.1.17320"}] count=1
And download it:
[nclarey@destructor:2335 ~/Development/lg-dryer]$ ipatool download --purchase -b com.lgeha.nuts
2:23PM INF output=/Users/nclarey/Development/lg-dryer/com.lgeha.nuts_993504342_5.1.17320.ipa purchased=false success=true
And sure enough, there it is:
[nclarey@destructor:2329 ~/Development/lg-dryer]$ ls -al *.ipa
-rw-r--r-- 1 nclarey staff 280M 9 Sep 14:18 com.lgeha.nuts_993504342_5.1.17320.ipa
280Mb! For an application that gets a push message from your dryer? That seems like an awfully big application for something that doesn't do much. We're going to need to dig a bit deeper to figure out why it's so large.
Opening the package
An .ipa file is just a zip archive, so we can decompress it just like any other zip archive:
[nclarey@destructor:2336 ~/Development/lg-dryer]$ unzip com.lgeha.nuts_993504342_5.1.17320.ipa
Archive: com.lgeha.nuts_993504342_5.1.17320.ipa
creating: META-INF
inflating: META-INF/com.apple.ZipMetadata.plist
extracting: META-INF/com.apple.FixedZipMetadata.bin
creating: Payload
creating: Payload/LG ThinQ.app
...and lots more output...
There's a lot going on in this package! It contains all of the compiled code, resources and metadata that your device needs to execute the application.
Most of the good stuff is in the Payload/LG ThinQ.app
directory, which contains a lot of files:
[nclarey@destructor:2339 ~/Development/lg-dryer]$ find Payload/LG\ ThinQ.app/ -type f | wc -l
9295
Over 9 thousand! That's a lot. And it's a lot more than I can figure out by hand, so I'm going to need to put together a script to help me understand it.
Writing the script
Our script walks the directory tree and produces a data structure that looks like this:
{
"<mime-type1>": [{"filepath1", <size_in_bytes>}, {"filepath2", <size_in_bytes>}...{"filepathn", <size_in_bytes>}]
"<mime-type2>": [{"filepath1", <size_in_bytes>}, {"filepath2", <size_in_bytes>}...{"filepathn", <size_in_bytes>}]
}
This allows us to easily group files that are the same type of file. It then prints out a summary of each mime type and the corresponding total size. Here's what it looks like:
(venv) [nclarey@destructor:2343 ~/Development/lg-dryer]$ python summarise.py Payload
1.6K audio/x-aiff
1.6K text/x-diff
1.8K application/x-pem-file
4.4K text/x-objective-c
5.9K application/x-adobe-aco
11.5K text/x-c++
85.8K audio/mpeg
196.9K application/gzip
209.1K image/jpeg
279.1K text/html
640.6K image/gif
852.6K font/sfnt
1.8M image/bmp
2.9M image/svg+xml
3.5M image/webp
3.7M text/xml
12.0M text/plain
20.5M application/javascript
24.4M image/png
34.5M application/octet-stream
36.3M application/json
66.6M application/x-bplist
236.4M application/x-mach-binary
Total: 444.9M 466474404
Number: 9295
I've also made it so that we can look at individual categories and list the files of that category. For example, we can see how much is "images":
(venv) [nclarey@destructor:2351 ~/Development/lg-dryer]$ python summarise.py --mimetype image Payload
33.4M image
Total: 33.4M 35048417
Number: 5789
Over half the files by count are image files. In addition, there are over 170 Lottie format vector animations in JSON format, which are a little tricky to distinguish from structured data. But in any case, about 50% of the overall size of the file are what we might describe as "media".
There are also a significant number of localised resources. There are 59 ".lproj" directories in the LG ThinQ application, each of which represents changes for individual languages. These are mainly text, so not large. They can also include images, in case there are brand differences or text included in image files.
Looking at the binaries
The main area of interest, however, is what is inside the binary files.
(venv) [nclarey@destructor:2352 ~/Development/lg-dryer]$ python summarise.py --mimetype application/x-mach-binary Payload
236.4M application/x-mach-binary
Total: 236.4M 247841712
Number: 76
Extending the script to list individual files, we see a wide range of different system libraries. The top 35:
(venv) [nclarey@destructor:2544 ~/Development/lg-dryer]$ cat binaries.txt | sort -h | tail -35
715.8K Payload/LG ThinQ.app/Frameworks/CryptoSwift.framework/CryptoSwift
823.9K Payload/LG ThinQ.app/Frameworks/SDWebImage.framework/SDWebImage
915.2K Payload/LG ThinQ.app/Frameworks/FBSDKLoginKit.framework/FBSDKLoginKit
1.1M Payload/LG ThinQ.app/PlugIns/LGCastMirroringBroadcastUploadExtension.appex/LGCastMirroringBroadcastUploadExtension
1.2M Payload/LG ThinQ.app/Frameworks/ThingMbedtls.framework/ThingMbedtls
1.3M Payload/LG ThinQ.app/Frameworks/RxCocoa.framework/RxCocoa
1.5M Payload/LG ThinQ.app/PlugIns/ThinQWidgetExtension.appex/ThinQWidgetExtension
1.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_DoorLock.framework/SmartThinQ_DoorLock
1.8M Payload/LG ThinQ.app/Frameworks/Kingfisher.framework/Kingfisher
2.0M Payload/LG ThinQ.app/Frameworks/RxSwift.framework/RxSwift
2.1M Payload/LG ThinQ.app/Frameworks/IotManagerLib.framework/IotManagerLib
2.1M Payload/LG ThinQ.app/Frameworks/MarketingCloudSDK.framework/MarketingCloudSDK
2.5M Payload/LG ThinQ.app/Frameworks/ThingOpenSSLSDK.framework/ThingOpenSSLSDK
2.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_Entity.framework/SmartThinQ_Entity
2.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_UI.framework/SmartThinQ_UI
2.7M Payload/LG ThinQ.app/Frameworks/RealmSwift.framework/RealmSwift
2.8M Payload/LG ThinQ.app/Frameworks/Alamofire.framework/Alamofire
2.8M Payload/LG ThinQ.app/Frameworks/ThingAudioEngineSDK.framework/ThingAudioEngineSDK
3.1M Payload/LG ThinQ.app/Frameworks/ThingCameraSDK.framework/ThingCameraSDK
3.2M Payload/LG ThinQ.app/Frameworks/FBSDKCoreKit.framework/FBSDKCoreKit
3.2M Payload/LG ThinQ.app/Frameworks/LGShare.framework/LGShare
3.2M Payload/LG ThinQ.app/Frameworks/TcsLib.framework/TcsLib
3.3M Payload/LG ThinQ.app/Frameworks/Lottie.framework/Lottie
3.4M Payload/LG ThinQ.app/Frameworks/ThingFFmpegWrapper.framework/ThingFFmpegWrapper
3.7M Payload/LG ThinQ.app/Frameworks/LockCodecLib.framework/LockCodecLib
3.8M Payload/LG ThinQ.app/Frameworks/OpenSSL.framework/OpenSSL
4.4M Payload/LG ThinQ.app/Frameworks/SmartThinQ_Security.framework/SmartThinQ_Security
4.9M Payload/LG ThinQ.app/Frameworks/BLECodecLib.framework/BLECodecLib
6.3M Payload/LG ThinQ.app/Frameworks/GStreamerForLGCast.framework/GStreamerForLGCast
9.5M Payload/LG ThinQ.app/Frameworks/SharedCode.framework/SharedCode
9.9M Payload/LG ThinQ.app/Frameworks/Realm.framework/Realm
10.9M Payload/LG ThinQ.app/Frameworks/Matter.framework/Matter
26.3M Payload/LG ThinQ.app/Frameworks/SmartThinQ_3DHome.framework/SmartThinQ_3DHome
29.6M Payload/LG ThinQ.app/Frameworks/UnityFramework.framework/UnityFramework
62.3M Payload/LG ThinQ.app/LG ThinQ
Putting these into rough categories based on naming and a bit of intuition, we have:
Security and encryption
715.8K Payload/LG ThinQ.app/Frameworks/CryptoSwift.framework/CryptoSwift
1.2M Payload/LG ThinQ.app/Frameworks/ThingMbedtls.framework/ThingMbedtls
2.5M Payload/LG ThinQ.app/Frameworks/ThingOpenSSLSDK.framework/ThingOpenSSLSDK
3.8M Payload/LG ThinQ.app/Frameworks/OpenSSL.framework/OpenSSL
4.4M Payload/LG ThinQ.app/Frameworks/SmartThinQ_Security.framework/SmartThinQ_Security
Marketing and social media
915.2K Payload/LG ThinQ.app/Frameworks/FBSDKLoginKit.framework/FBSDKLoginKit
3.2M Payload/LG ThinQ.app/Frameworks/FBSDKCoreKit.framework/FBSDKCoreKit
2.1M Payload/LG ThinQ.app/Frameworks/MarketingCloudSDK.framework/MarketingCloudSDK
System utilities (reactive frameworks, networking)
1.3M Payload/LG ThinQ.app/Frameworks/RxCocoa.framework/RxCocoa
2.0M Payload/LG ThinQ.app/Frameworks/RxSwift.framework/RxSwift
2.8M Payload/LG ThinQ.app/Frameworks/Alamofire.framework/Alamofire
Database
2.7M Payload/LG ThinQ.app/Frameworks/RealmSwift.framework/RealmSwift
9.9M Payload/LG ThinQ.app/Frameworks/Realm.framework/Realm
Media handling (audio, video, animation, games)
823.9K Payload/LG ThinQ.app/Frameworks/SDWebImage.framework/SDWebImage
1.8M Payload/LG ThinQ.app/Frameworks/Kingfisher.framework/Kingfisher
2.8M Payload/LG ThinQ.app/Frameworks/ThingAudioEngineSDK.framework/ThingAudioEngineSDK
3.1M Payload/LG ThinQ.app/Frameworks/ThingCameraSDK.framework/ThingCameraSDK
3.3M Payload/LG ThinQ.app/Frameworks/Lottie.framework/Lottie
3.4M Payload/LG ThinQ.app/Frameworks/ThingFFmpegWrapper.framework/ThingFFmpegWrapper
6.3M Payload/LG ThinQ.app/Frameworks/GStreamerForLGCast.framework/GStreamerForLGCast
29.6M Payload/LG ThinQ.app/Frameworks/UnityFramework.framework/UnityFramework
Matter
10.9M Payload/LG ThinQ.app/Frameworks/Matter.framework/Matter
Kotlin iOS runtime and supporting code
2.1M Payload/LG ThinQ.app/Frameworks/IotManagerLib.framework/IotManagerLib
3.2M Payload/LG ThinQ.app/Frameworks/TcsLib.framework/TcsLib
Application implementation
1.1M Payload/LG ThinQ.app/PlugIns/LGCastMirroringBroadcastUploadExtension.appex/LGCastMirroringBroadcastUploadExtension
1.5M Payload/LG ThinQ.app/PlugIns/ThinQWidgetExtension.appex/ThinQWidgetExtension
1.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_DoorLock.framework/SmartThinQ_DoorLock
2.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_Entity.framework/SmartThinQ_Entity
2.6M Payload/LG ThinQ.app/Frameworks/SmartThinQ_UI.framework/SmartThinQ_UI
3.2M Payload/LG ThinQ.app/Frameworks/LGShare.framework/LGShare
3.7M Payload/LG ThinQ.app/Frameworks/LockCodecLib.framework/LockCodecLib
4.9M Payload/LG ThinQ.app/Frameworks/BLECodecLib.framework/BLECodecLib
9.5M Payload/LG ThinQ.app/Frameworks/SharedCode.framework/SharedCode
26.3M Payload/LG ThinQ.app/Frameworks/SmartThinQ_3DHome.framework/SmartThinQ_3DHome
62.3M Payload/LG ThinQ.app/LG ThinQ
A few notes:
- It also looks like a significant amount of the core application (LG ThinQ and SharedCode) is written in Kotlin (possibly Kotlin Multiplatform?) and not Swift
- There are modules written in C++, Objective-C, Swift and Kotlin
- It contains the Unity framework, which has support for .NET via Mono
- It has support for a huge variety of audio devices, televisions, cameras and other appliances
- It supports the Matter home networking standard, but also includes code for supporting BLE
- It uses the Realm database
Conclusions
Why is the app so big? The answer is, simply, because it's doing a lot. You may only make use of one narrow part of the functionality of this application, but it appears to be capable of doing everything required to manage all of LG's digitally-connected appliances. And it does it in 59 different languages.
Different engineering decisions
There are clearly engineering decisions that you might make differently if you were trying to optimise for size. You would probably:
- Ensure more reuse and avoid redundant libraries (you don't need two different cryptography libraries as well as the one provided at the system level, you don't need two web image libraries)
- Try to standardise on a single platform language
- Why do you need all of Unity? (it's very big!)
Shared 3rd party libraries
It's also worth bearing in mind that one of the problems with having applications that are isolated in their own worlds is that they can't easily share their libraries with each other. If application 1 uses Unity, and application 2 does as well, then you have two copies of a 30Mb Unity library on the device. This makes sense on one level, because they may be depending on slightly different versions. But device storage would be used much more frugally if the operating system provided a mechanism to request and manage dependencies between applications beyond just giving access to system libraries.
But storage is cheap, engineering is hard, and there just isn't the commercial imperative. So until then, you're stuck with each application playing in its own sandbox.
What we've not considered
Colby originally reported that the application was taking up more than 800Mb on his device. We've accounted for something in the vicinity of half of that. Some of the cost might be related to block sizes (if you have lots of small files, it may take up more space than you're expecting because a small file needs a larger bucket to live in than is ideal); some of the cost might also be related to cached information or downloaded material that is stored on the device during runtime. Logging and local analytics data might also account for quite a lot, and if there are downloaded videos or similar they could easily eat up a lot of space.
Tools I used in the making of this post
and various other things that come on your Mac.