<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Noah Martin on Medium]]></title>
        <description><![CDATA[Stories by Noah Martin on Medium]]></description>
        <link>https://medium.com/@noahm444?source=rss-26887d1ff012------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*hR0mueCqQj0UoYNKP4EZCA.jpeg</url>
            <title>Stories by Noah Martin on Medium</title>
            <link>https://medium.com/@noahm444?source=rss-26887d1ff012------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 12:16:30 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@noahm444/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[ETTrace: Reliable iOS Profiling With Flame Charts]]></title>
            <link>https://medium.com/@noahm444/ettrace-reliable-ios-profiling-with-flame-charts-2127370058fd?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/2127370058fd</guid>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[performance-testing]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[xcode]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Thu, 27 Apr 2023 13:15:52 GMT</pubDate>
            <atom:updated>2023-04-27T16:40:29.392Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lC2xf8A6M13Z6ecA-RwSvg.jpeg" /></figure><p>Measuring the performance of iOS apps is typically done by profiling an app to calculate how much time is spent in each function. Usually you’d do this with the Time Profiler in Xcode Instruments, but it is known to be slow and unreliable.</p><p>Emerge offers a profiling tool as part of <a href="https://www.emergetools.com/product/performanceanalysis">Performance Testing in CI</a>. This profiling is visualized as a flame graph, and has <a href="https://doordash.engineering/2023/01/31/how-we-reduced-our-ios-app-launch-time-by-60">proven to be</a> an easy way to gain insights about bottlenecks in app performance, and identify solutions. Today, we’re introducing a new way to use the same great profiling visualizations that is <em>entirely local and open-source</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dIZdESI_58-qt3bRXzg1WA.gif" /><figcaption>Emerge Tools profiling UI used for performance testing</figcaption></figure><p><a href="https://github.com/emergeTools/ettrace">ETTrace</a> is an open-source framework written in Objective-C and a CLI in Swift that profiles and visualizes entirely locally. It’s built to be easy and fast — just link the framework to your app, run ettrace to start profiling and stop to instantly see your flame chart. No restarting the app or clicking through long menus to see results.</p><h3>Why we need a new profiler</h3><p>Emerge’s Performance Analysis is designed to prevent regressions from being merged to your codebase. It gives you consistent results for specific scenarios configured in CI. However, pushing to CI is not ideal when you’re debugging. You need something fast and local.</p><p>ETTrace let’s you easily debug a performance issue. If you’re using Emerge, you can then validate your fix by pushing to CI when you’re ready. Additionally, you can explore all code paths in your app without writing specific tests. If you identify performance critical paths with ETTrace, you can then set them up to be monitored in CI.</p><p>You might wonder why not just use Instruments for this? While the Time Profiler is the state of the art for iOS profiling, it can be difficult to use without being a performance expert (and even for performance experts). This is partly due to the non-intuitive visualizations, but also due to the unwieldy nature of the tool.</p><p>At Emerge I’ve talked to many engineers working on large apps and the feedback is all the same: Time Profiler can be flaky and slow. Even getting the screenshots for this article I encountered multiple freezes and needed to force-quit. Symbolication is frequently a problem, with traces being generated and only showing addresses but not function names.</p><p>ETTrace supports symbolication in two ways. First, if you have dSYMs you can directly supply them to the tool through the --dsyms argument. Second, for simulator builds, ETTrace will automatically use the symbol table in the app binary to symbolicate. Since the tool is open source, if you have any issues with symbolication it’s easy to debug - unlike Instruments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sj8j_ZLnQwDJ2r3LQthdCQ.png" /><figcaption>Instruments vs. ETTrace</figcaption></figure><h3>How sampling works</h3><p>ETTrace is a sampling based profiler, meaning it records the stack at fixed intervals to build the visualization. The sampling is for the main thread only, which is where you typical find user facing performance issues such as hangs. Sampling is done on a background thread roughly like this:</p><pre>sStackRecordingThread = [[NSThread alloc] initWithBlock:^{<br>    NSThread *thread = [NSThread currentThread];<br>    while (!thread.cancelled) {<br>        [self recordStack];<br>        usleep(4500);<br>    }<br>}];</pre><p>Each recording includes the list of addresses observed in the stack, and the current timestamp. Addresses are symbolicated after the trace is done recording, and the symbolicated versions are aggregated. The maximum amount of time between any two stacks should be 5ms (accounting for usleep taking up to 0.5ms longer than the time we specify). To ensure traces are accurate, any extra time longer than this is accumulated and reported as &lt;unattributed&gt;.</p><p>The visualization is technically a flame chart, which means nodes are ordered by time on the x-axis. It follows this data structure:</p><pre>struct FlameChartNode {<br>  let name: String<br>  let duration: Double<br>  let children: [FlameChartNode]<br>}</pre><p>The visualization generated by Emerge performance testing is a flame graph, where stacks are aggregated at each level by name. Each node does not have a specific start/end time because they are not ordered, they only have a duration on the x-axis. The data structure looks like this:</p><pre>struct FlameGraphNode {<br>  let duration: Double<br>  // Children is keyed by node name<br>  let children: [String: FlameGraphNode]<br>}</pre><p>Since ETTrace only visualizes a single trace of the app (rather than an average of many traces) the data is a flame chart which can be easier for debugging. ETTrace also has a diffing feature, where you can upload two traces and compare them to see how a function has improved or regressed. When this is used, the visualization will be a flame graph.</p><h3>Understanding protocol conformances</h3><p>As a case study of using ETTrace, let’s look at how to analyze the impact of protocol conformances on app launch. We’ll use the open source Mastodon app as our example, but modified to include many more protocol conformances. Normally you would use ETTrace after the app has launched, but to profile directly from launch we add the key ETTraceRunAtStartup set to YES to the Info.plist.</p><p>Now we can launch the app with ETTrace.framework linked and start profiling! Make sure the app is deleted from your phone before installing from Xcode. Then, install but don’t launch the app. Finally, run ettrace at the command line and follow the prompts including launching the app manually from the home screen. The resulting flame graph shows a large amount of time spent in protocol conformances: over 60ms!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*612pRy1rfwTS252a-0d7EQ.png" /><figcaption>Slow protocol conformances in ETTrace</figcaption></figure><p>Next, try launching the same app a second time and running ettrace to get the trace. This time conformance lookups are so fast, ETTrace doesn&#39;t even sample them! By selecting both traces you can use the differential flame graph to confirm the reason for the slowdown.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WkfAnnS2_8AFvrFdQTOcNg.png" /></figure><p>This demonstrates that protocol conformances still use the slow path on the first launch of an app in iOS 16 (including after installing an update) and are very fast for subsequent launches. However, other kinds of protocol conformance checks, such as when the result of an as? operation is nil can still be <a href="https://www.emergetools.com/blog/posts/how-order-files-speed-up-protocols">very slow</a>. Running your app locally with ETTrace can help identify if you have any of these in your app.</p><h3>Automate in CI</h3><p>Local performance debugging with ETTrace is just one part of a performance optimization workflow. You want a quick iteration cycle that allows you to evaluate new ideas, but continuous testing and alerts provide an additional safety net to prevent issues from being introduced in production and confirm what you measure locally. Emerge offers a <a href="https://www.emergetools.com/product/performanceanalysis">performance testing feature</a> to do just this. Together local performance debugging with ETTrace and Emerge’s Performance Analysis bring a unified performance workflow to developers and results in app performance continuously improving. If you’re interested in learning more about these tools feel free to <a href="mailto:support@emergetools.com">get in touch</a>, and if you have any feedback on ETTrace please <a href="https://github.com/EmergeTools/ETTrace/issues">open a Github issue</a>!</p><p>Thanks to <a href="https://github.com/Itaybre">Itay Brenner</a> for his work on this project, and to <a href="https://twitter.com/__unused">Filip Busic</a>, <a href="https://twitter.com/miguel_jimemigu">Miguel Jimenez</a>, and <a href="https://github.com/keith">Keith Smiley</a> for their early feedback testing the tool!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2127370058fd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How To Speed Up Swift By Ordering Conformances]]></title>
            <link>https://medium.com/@noahm444/how-to-speed-up-swift-by-ordering-conformances-a20461c1366d?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/a20461c1366d</guid>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[apple]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Wed, 25 Jan 2023 15:01:07 GMT</pubDate>
            <atom:updated>2023-01-25T15:01:07.500Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*77np5Fx_ugoq1JAxUCWmUg.png" /></figure><p>The Swift runtime executes a protocol conformance check when you cast a type to a protocol, such as with as? or as!. This operation is surprisingly slow, as detailed in my <a href="https://www.emergetools.com/blog/posts/SwiftProtocolConformance">previous post</a>. In this article we’ll look at an easy way to speed this up by ~20%, without making any changes to your source code. First, a brief review of protocol conformance checks.</p><h4>Review + iOS 16 improvements</h4><p>Records of every conformance you write in source code get stored in the __TEXT/__const section of the binary in a form similar to this:</p><pre>struct ProtocolConformanceDescriptor {<br>  // Offset to the protocol definition<br>  let protocolDescriptor: Int32<br>  // Offset to the type that conforms to the protocol<br>  var nominalTypeDescriptor: Int32<br>  let protocolWitnessTable: Int32<br>  let conformanceFlags: UInt32<br>}</pre><p>A typical app can have tens of thousands of these. Many are conformances to common protocols such as Equatable Hashable Decodable or Encodable. When the Swift runtime encounters something like myVar as? MyProtocol (which may not be written directly in your code, many common functions like String(describing:) internally do an as?) it loops over every ProtocolConformanceDescriptor in the binary plus any dynamically linked binaries. This operation is O(n). In the worst case if you need to lookup a protocol conformance record for every type that would be O(n^2).</p><p>iOS 16 greatly improves on this. As I explained in a <a href="https://www.emergetools.com/blog/posts/iOS16LaunchTime">previous post</a>, iOS 16 precomputes protocol conformances in the dyld closure, and the Swift runtime consults dyld before running the O(n) lookup. At the time of the previous blog post Apple had not released the iOS 16 dyld source code, but now that they have, we can see the actual implementation in the function <a href="https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f9fc1713db34674e79b00e30723e79d/dyld/DyldAPIs.cpp#L2424">_dyld_find_protocol_conformance_on_disk</a>. This function is conceptually the same as the <a href="https://github.com/EmergeTools/zconform/tree/main">zconform library</a> which speeds up these checks using a hash table that maps types to a list of protocols that they conform to.</p><p>However, there are still 3 cases where you might encounter the slow lookup, making it worth optimizing for:</p><ol><li>On the first launch after an app install/update. The dyld closure isn’t built yet, and all conformance lookups are still slow.</li><li>When the conformance lookup results in nil. This could be a <a href="https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f9fc1713db34674e79b00e30723e79d/include/mach-o/dyld_priv.h#LL651C3-L651C60">_dyld_protocol_conformance_result_kind_definitive_failure</a> but a quick scan of the source code reveals this is not yet implemented.</li><li>If you aren’t using iOS 16, such as a user on an older OS or using Swift on a non-apple platform including server side Swift.</li></ol><p>It’s also difficult to measure this iOS 16 improvement in practice, because this dyld behavior is disabled when running the app from Xcode or Instruments. Emerge has a <a href="https://docs.emergetools.com/docs/performance-debugging">local performance debugging tool</a> that works around this and can be used to profile apps that do have access to the dyld closure.</p><h4>Order files</h4><p>Order files are inputs to the linker which make apps faster by grouping code used together into the same region of the binary. With order files, your app accesses only the memory used by the app launch code rather than reading an entire 100+ MB binary into memory. <strong>This principle relies on the concept of a memory page size. To access one byte of the binary, the entire 16kb page is loaded. It’s beneficial to have the data you need on as few pages as possible.</strong> I previously wrote a <a href="https://www.emergetools.com/blog/posts/FasterAppStartupOrderFiles">deep dive on order files</a>.</p><p>Keeping used memory close together is also important to improve the cache hit rate. iPhones have multiple levels of memory caches, for example the iPhone 7/A10 has the following structure [1]</p><pre>Level  |  Size<br>--------------<br>L1     | 126KB<br>L2     | 3MB<br>L3     | 4MB<br>RAM    | 2GB<br>NAND   | 256GB</pre><p>The specifics of speeds are not published by Apple and vary year to year, but some benchmarks show that moving up a level can increase latency by 5x [2].</p><h4>Ordering conformances</h4><p>By default, protocol conformances end up spread throughout the __TEXT/__const section of the binary. This is because each module in an app generates their own static binary. When they are linked into the final app, the binaries are placed side by side. Data from different modules is not interleaved in the executable. Let’s visualize this with the Uber app, the version we’re using has 102,800 conformance records (based on the size of the __TEXT/__swift5_proto section) and a 12.7mb __TEXT/__const section.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I4HLnFfaIxvKW_418rDZcg.png" /><figcaption>Conformances in each binary page of Uber’s __TEXT/__const section. The y-axis shown the number of conformances on each page. The top figure is a heatmap of conformance counts.</figcaption></figure><p>The above figure shows the number of conformances on each page of Uber’s app. A protocol conformance record can vary in size (depends on details like associated types), but the minimum size is 16 bytes. You can have a maximum of 1024 conformance records on a single page of memory. Interestingly, Uber has a few spikes where a page contains nothing but the minimum sized conformances. This might be due to codegen, such as dependency injection or network models, which creates many simple protocols in one module. There are also a couple regions with no conformances, likely due to non-Swift code in the app. <strong>The key takeaway is that conformances are spread throughout the binary, so almost all pages will be loaded from memory when conformances are enumerated.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bL17Aa4tf6aecu2iJjxdEQ.png" /><figcaption>Conformances in each binary page of Lyft’s __TEXT/__const section</figcaption></figure><p>Similarly, the above figure shows conformances Lyft’s app. While there are no large spikes, there are about 250 conformances on every page with the exception of one region that is likely non-Swift code.</p><p>We can apply the idea of using order files to group data onto as few pages as possible to conformances, and generate an order file that moves all conformances onto their own pages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8mxolJasPbbtDekpZQaa6w.png" /><figcaption>Conformances ordered to the beginning of the __TEXT/__const section</figcaption></figure><p>The above figure shows the result of using an order file to group conformances. Each of the first ~250 pages now only contain protocol conformance descriptors, with about 500 per page. Conformance records vary in size, so the number of conformances on a page is not always the same. With this ordering, less than half of the section needs to be loaded when performing a protocol conformance lookup. In fact, the total memory used by 250 pages is &lt; 4MB so in this example they can all fit in the L3 cache of an iPhone 7. In our tests, co-locating the conformances like this resulted in an <strong>over 20% decrease in protocol conformance lookup time on an iPhone 7 running iOS 15!</strong></p><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/how-order-files-speed-up-protocols">Emerge Tools Blog</a></h4><p>[1] <a href="https://en.wikipedia.org/wiki/Apple_A10">https://en.wikipedia.org/wiki/Apple_A10</a></p><p>[2] <a href="https://www.anandtech.com/show/14892/the-apple-iphone-11-pro-and-max-review/3">https://www.anandtech.com/show/14892/the-apple-iphone-11-pro-and-max-review/3</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a20461c1366d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How iOS 16 makes your app launch faster]]></title>
            <link>https://medium.com/geekculture/how-ios-16-makes-your-app-launch-faster-1c27fd442738?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/1c27fd442738</guid>
            <category><![CDATA[wwdc]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Thu, 07 Jul 2022 09:01:37 GMT</pubDate>
            <atom:updated>2022-07-07T09:01:37.435Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jqcj6LAYIMSOFDFn852I2w.png" /></figure><p><a href="https://developer.apple.com/videos/play/wwdc2022/102/">WWDC22’s state of the union</a> promised to bring some big launch time improvements:</p><blockquote>with apps like Lyft or Airbnb launching almost twice as fast thanks to improvement in the dynamic linker.</blockquote><p>This improvement comes from speeding up protocol checks, which I demonstrated to be slow in a <a href="https://www.emergetools.com/blog/posts/SwiftProtocolConformance">previous blog post</a>. Additionally, iOS 16 improves the time it takes to load a binary by reducing the amount of data loaded from disk. This was also the subject of a <a href="https://www.emergetools.com/blog/posts/FasterAppStartupOrderFiles">previous article</a>.</p><p>These improvements come down to changes in dyld — the program responsible for bootstrapping your app and starting execution of your own code. They also take two opposite (but common) approaches to improving performance — <strong>eager loading</strong>, and <strong>lazy evaluation</strong>.</p><p>In this post, we’ll look at what changed in iOS 16, how much faster it really is, and how you can best take advantage of these new features in your app.</p><h3>Protocol checks</h3><p>Protocol conformance checks happen in the Swift runtime to determine the result of code like myVar as? MyProtocol. Every time a type conforms to a protocol the binary will include a &quot;conformance record&quot;. When checking a conformance, the runtime loops over every conformance record to see if any match the current operation. This loop is O(n) where n is the number of conformance records in your app. For big apps there can be over one hundred thousand, making each conformance check very slow.</p><p>Protocol conformance checks are widespread in Swift apps even if you don’t write them, because they are also invoked from common operations like String(describing:) or AnyHashable. For some large Swift apps, such as Lyft and Airbnb, close to half of the launch time is spent in protocol conformance checks.</p><p>The big change comes in the “dyld closure”, which is a per-app cache used to accelerate various dyld operations during app launch. The <strong>closure now contains pre-computed conformances</strong>, allowing each lookup to be much faster. Note that the dyld closure is not always used, e.g. because it’s out-of-date or because it’s being launched from Xcode, which complicates things. This change was implemented in the Swift open source project as part of <a href="https://github.com/apple/swift/pull/41185">Mike Ash’s PR</a>, which adds the dyld API call: _dyld_find_protocol_conformance_on_disk. If the conformance is found with this cache, the O(n) operation is completely skipped, so we should see big improvements to apps with a large number of conformances.</p><h4>Testing it out</h4><p>At Emerge, we run a lot of performance tests to identify how PRs impact app launch. So naturally, we wanted to measure exactly how these precomputed conformance checks will affect launch time.</p><p>Since we are dealing with two different OS versions, we can’t test on the same device. Instead, I opted to micro-benchmark the time for a conformance check across an app with 10k, 20k, 30k, 40k, and 50k conformances in the binary. Each version is launched on an iOS 16 and iOS 15 device, with the 10k conformance considered a baseline and only times relative to the baseline are compared.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CT5zyTmtqFkK5_qY3IlzSw.png" /></figure><p>Clearly, iOS 15 gets slower as more conformances are added while iOS 16 does not, problem solved! In practice, the closure is not always going to be available, so we’ll have to wait until iOS 16 hits user devices in a few months to know exactly how much this will change numbers in production. This improvement applies to all apps, even if their minimum deployment target is below iOS 16.</p><h3>Page faults</h3><p>The second big improvement comes from reducing the amount of data that has to be loaded from disk at startup. The first time a piece of code is executed, the kernel loads the surrounding chunk of memory, known as a page, in a process called a <strong>page fault</strong>. On app launch some parts of the binary need to be fixed up before the code can run (an in depth explanation of this is in a <a href="https://www.emergetools.com/blog/posts/SwiftReferenceTypes">previous blog post</a>). On iOS 15 all fixups were done at app launch, meaning any location in the binary requiring a fixup had to be paged in. Now, a new feature called <a href="https://developer.apple.com/wwdc22/110362">page-in linking</a> resolves fixups lazily, only the first time a page is accessed.</p><p>Last year’s WWDC introduced a major new format to the metadata used to perform these fixups, which I <a href="https://www.emergetools.com/blog/posts/iOS15LaunchTime">covered in depth</a> at the time. This format is required for the lazy evaluation of fixups, so iOS 16 users will only get it if you target iOS 13.4 or later.</p><h3>Testing it out</h3><p>On iOS 15, all of the __DATA and __DATA_CONST segments of a binary contain fixups, so the total number of page faults before your code even runs is just the size of these segments. With Emerge’s tooling, we can also measure how many pages your code needs to run, the difference gives us how many fewer page faults you have in iOS 16.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dab2ce6e3aba9734c94086a5c8014110/href">https://medium.com/media/dab2ce6e3aba9734c94086a5c8014110/href</a></iframe><h3>Getting the full benefit</h3><p>This is a great improvement that immediately reduces page faults, but there are ways to get an even larger reduction. Emerge offers an order file service — Launch Booster — which automatically orders a binary to minimize page faults. We <strong>improved launch time by an average of 18% </strong>when deploying an order file to the app store on iOS 15. Now that iOS 16 doesn’t automatically load every page, ordering the binary can make your app startup time even faster. If you’d like to learn more about how you can automatically reduce startup time and take advantage of iOS 16 improvements, <a href="mailto:team@emergetools.com">get in touch</a> with the Emerge team.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c27fd442738" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/how-ios-16-makes-your-app-launch-faster-1c27fd442738">How iOS 16 makes your app launch faster</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Code Injection with Dyld Interposing]]></title>
            <link>https://medium.com/geekculture/code-injection-with-dyld-interposing-3008441c62dd?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/3008441c62dd</guid>
            <category><![CDATA[objective-c]]></category>
            <category><![CDATA[reverse-engineering]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Thu, 26 May 2022 11:32:11 GMT</pubDate>
            <atom:updated>2022-05-26T11:32:11.059Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZmBNCY522hwNepvv5fELaw.jpeg" /></figure><p>The dynamic nature of the Objective-C runtime can be exploited for many purposes, including method swizzling. There are <a href="https://nshipster.com/method-swizzling/">many tutorials</a> explaining how to use swizzling, and for many purposes it gets the job done. However, it can’t always be used.</p><p>Swizzling handles Objective-C methods, but cannot be used for C/C++ functions. When reverse engineering iOS apps some non-Obj-C lower level calls might be most useful, and sometimes you need to intercept them in an app that you don’t have the source code for.</p><p>In this post we’ll be looking at a lesser known technique for injecting code at a function call, one that works with C/C++ functions and with unmodified app binaries. These basic building blocks underpin many developer tools, including my work with <a href="https://www.emergetools.com">Emerge Tools</a>.</p><h4>Example</h4><p>Imagine you are trying to reverse engineer an app to understand how it uses the keychain. You know at some point the app calls <a href="https://developer.apple.com/documentation/security/1398306-secitemcopymatching">SecItemCopyMatching</a>, but are unsure what data is stored and what keys it is stored under. This function can’t be swizzled because it is not Objective-C. You also can’t modify the original source code, all you have is the compiled app.</p><p>In this post we’ll implement a solution that prints all the data requested from the keychain to stdout as the app is running. The solution uses a framework that interposes SecItemCopyMatching and is loaded on launch with DYLD_INSERT_LIBRARIES.</p><h4>DYLD_INSERT_LIBRARIES</h4><p>While not strictly necessary for interposing, inserted libraries are commonly combined with interposing and are a fantastic resource for anyone exploring iOS internals, so it’s worth a quick overview.</p><p>DYLD_INSERT_LIBRARIES is an environment variable that allows you to add code to an app’s process. The format is just a colon separated list of frameworks that will be linked on app launch. For example: DYLD_INSERT_LIBRARIES=@executable_path/Frameworks/InterposingSample.framework/InterposingSample. If you’ve ever used LD_PRELOAD on Linux/Android, this is the iOS equivalent.</p><p>This one environment variable[1] has a lot of potential, you can write a +load method to add any extra logic you want to app launch, including swizzling, responding to NSNotifications, or presenting an entirely new UI. This is even used by SwiftUI previews, inspecting a crash report from a preview reveals the line: DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot//System/Library/PrivateFrameworks/PreviewsInjection.framework/PreviewsInjection</p><h4>DYLD_INTERPOSE</h4><p>When you use dyld interposing you don’t even need to use an initializer such as +load, because it is a much more declarative API than swizzling. As <a href="https://www.emergetools.com/blog/posts/SwiftReferenceTypes">I’ve discussed before</a>, the first code that runs at app launch is dyld, not the code you write. One of dyld’s responsibilities is to bind calls from one binary to another, such as from your app to Apple’s frameworks. Interposing is a way to tell dyld to substitute one bound function for another.</p><p><strong>Dyld Binding</strong></p><p>Embedded in your binary is a table of symbols that are referenced externally, I wrote about the new iOS 15 format of this data in a <a href="https://www.emergetools.com/blog/posts/iOS15LaunchTime">previous blog post</a>. Some of the symbols are bound lazily, only the first time they are used, through a function included in every app called dyld_stub_helper. Other symbols are bound right at app launch. Either of these methods allow dyld to have the final say in the address of any function defined in another binary, and luckily it gives you the opportunity to modify this address to point at your own function.</p><p><strong>Interposing</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/577/1*SBGFORRXE1rSkFwu9wo4qA.png" /><figcaption>Interposing SecItemCopyMatching with an inserted framework</figcaption></figure><p>Interposing works by adding a new Mach-O section (__DATA, __interpose) that contains a list of tuples holding the address to a replacement function and a replacee. If any library (including inserted ones) include the Mach-O section (__DATA, __interpose) then dyld will use this list to replace any call to the replacee with the replacement, as long as the call is not coming from the binary containing the replacement function. <strong>This means any call to the function you are trying to interpose in your inserted framework will still go to the original function.</strong></p><p>Looking at the source code for dyld, we can see exactly where these interposed addresses are loaded:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ncnEBjsIVXhhTy4r8MebtA.png" /></figure><p>To make this new section in your binary Apple provides a <a href="https://github.com/apple-opensource/dyld/blob/b6b86eb2db14440d373f6f7fd21be4a2bc0da897/include/mach-o/dyld-interposing.h">convenient macro</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LrupnPtczTWTtmsCQujPfw.png" /></figure><p>Now putting this all together, we can see how to implement a framework that does what we wanted from our original example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/887b27467c9b84a795ca4a7d7e1b0892/href">https://medium.com/media/887b27467c9b84a795ca4a7d7e1b0892/href</a></iframe><h4>What can you do with this?</h4><p>A lot! At <a href="https://www.emergetools.com">Emerge Tools</a> we use this technique for all our runtime performance measurements such as order file generation and performance testing. With interposing you can hook into unmodified apps to measure app behavior, extract information, or completely change behavior. It’s a powerful piece of the runtime for developer tools to take advantage of. Anytime you find yourself needing to swizzle a C/C++ function or anything that isn’t part of Objective-C (so it can’t be swizzled) it’s good to be aware of interposing as an alternative you can turn to. One disclaimer: I haven’t tried this in an app on the app store, but in general I would recommend it for local testing only!</p><h4>Other Methods</h4><p>There are a few other ways to achieve code injection outside of the Objective-C runtime in iOS apps. <a href="https://github.com/facebook/fishhook">Fishhook</a> is a popular one created by Facebook. Similar to dyld interposing it takes advantage of Mach-O symbol binding. With fishhook you don’t need a separate dylib, which can be much more convenient if you have control of the apps source code. I prefer to use dyld interposing when possible because it’s an entirely first-party solution, but fishhook is only a few hundred lines of C code and can be instructive to get a lower level view of how the symbol binding process works.</p><p>Intrepid readers might notice another feature in the dyld source: dynamic interposing with the function dyld_dynamic_interpose. This is just a way to tell dyld at runtime to start interposing a function. That’s similar to how fishook works; you don’t need to always interpose a function and can instead programmatically install a hook. At least one use case of this API was found by <a href="https://twitter.com/steipete/status/1258482647933870080?s=21">Peter Steinberger in the Chrome source code</a>. It looks like Chrome is overriding CoreAudio to modify it’s behavior, interesting to see an example of interposing being used in production!</p><p>[1] If you’re interested in other dyld environment variables, check out man dyld</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3008441c62dd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/code-injection-with-dyld-interposing-3008441c62dd">Code Injection with Dyld Interposing</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Order Files Reduce App Startup Time]]></title>
            <link>https://medium.com/geekculture/how-order-files-reduce-app-startup-time-c01f7765d29?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/c01f7765d29</guid>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-development]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[mobile]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Mon, 31 Jan 2022 17:04:01 GMT</pubDate>
            <atom:updated>2022-01-31T17:04:01.611Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4RBWMzVFrPinNK2X7MvgRg.jpeg" /></figure><p>A 150MB+ app binary file, like the one in Uber’s app, takes between 500 ms and 1 second just to be loaded into memory (measured on an iPhone 6s). Loading large files like this is just a fraction of the app’s launch time. To put in perspective, Apple’s recommended startup time is just 400ms. That‘s already 1–2x the recommended completed launch time without any code even executing!</p><p>By default, apps can need to read more than 75% of their binary during startup. However, with the help of <strong>order files</strong>, we can read only the functions we need during start up.</p><h3>Pages</h3><p>Why is so much of the binary used on startup? The answer lies in how iOS deals with memory. As new instructions are fetched from an app‘s binary file they must be loaded from disk into memory. Rather than loading one instruction or one byte at a time, the kernel loads one large chunk of memory, known as a <strong>page</strong>. On iOS a page is 16kb[1]. So the first time an instruction is needed from the app binary, the entire 16kb region surrounding that page is mapped into memory. This process is called a <strong>page fault</strong>.</p><p>Say the first 16kb of a binary contains code used by an obscure feature like captcha verification, but happens to include a single 100 byte function that‘s executed during app startup. Now the entire captcha feature needs to be loaded just to start the app. In practice, most or all of the binary will end up being loaded into memory during startup like this, as the startup functions are scattered about the binary.</p><h3>Using order files</h3><p>Luckily, apps don’t need their entire binary to start up, they only need the code executed on app launch. This is where order files come into play. They rearrange binaries to allow reading just the code you need.</p><p>Each function in source code is represented as an indivisible unit (symbol) in a binary. The linker decides how these symbols are ordered and by default will group code defined in the same source file close together in the binary. However, when you provide the linker with an order file, this default behavior is overriden. The order file instructs the linker to put functions in the order that it gives. By ordering the binary so all the startup functions are together, we only need to load that clump of pages, and not the other pages with the rest of the functions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gc8IS63w4GxHYfpo2Ccbvg.png" /><figcaption>Code from multiple source files needs to be ordered to end up in a single binary file.</figcaption></figure><h3>Faults in Theory</h3><p>This is worth doing because a page fault is expensive. Like most computers, the iPhone has a memory hierarchy with an order of magnitude delay added between levels. A page fault reads data from the slowest level — the phone’s flash storage (NAND). Accessing memory that has already been brought into RAM is much faster, and memory residing in the processor cache is the fastest. Each level gets smaller and smaller, that’s why it’s important to use as little memory as possible. The more memory used, the sooner low-latency caches fill up. In practice there are even more sources of latency, for example validating code signatures. Binaries are signed per-page, the first time a page is accessed the data must be hashed and compared to the signature. You can see this process in the kernel function <a href="https://opensource.apple.com/source/xnu/xnu-7195.81.3/osfmk/vm/vm_fault.c">vm_fault_validate_cs</a>.</p><h3>Faults in Practice</h3><p>To measure page faults in practice, just dereference a pointer to each page in an app’s binary.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a4e7dd03018362b021ba6d66a674e9d1/href">https://medium.com/media/a4e7dd03018362b021ba6d66a674e9d1/href</a></iframe><p>I tested this with several apps on the store, and measured time to access each page in a linear scan. By plotting the time for each access you can see distinct bands representing each level of the cache hierarchy. The following experiments were on an iPhone 6S.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k9QXswFwoMe0jDbNLPXpmQ.png" /><figcaption>The y-axis is on a log scale, so each layer is separated by a 10x difference in performance.</figcaption></figure><p>The outliers on the bottom right of the graph are pages that had extremely fast access times. These turn out to be in the Objective-C metadata and constant string[2] sections of the binary. Because the code measuring fault time is run after the Obj-C runtime is initialized, these pages were all already accessed. They were loaded in from flash storage even before the first line of code ran, and were <strong>already in a fast cache</strong> before I accessed them.</p><p>This distribution suggests that the worst case page fault takes about 1ms, but doesn’t tell us much about the expected duration of a fault. To get that, I looked at cumulative time to read all pages of the binary.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6AMbqG1NhBxKzwhXeB8TiQ.png" /></figure><p>That’s a clear trend! The average time for a fault is 0.06ms. However, it doesn’t explain why we saw much fewer slow faults than fast faults. Let’s zoom in on a small section of the graph:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hn0b6GwlrZd1ZdESlkJfVA.png" /></figure><p>Most faults are fast, but every 40–50 faults there is a very slow access. It looks like a step function, and can be explained by system <strong>prefetching</strong>. Each new fault triggers an expensive lookup, which is actually bringing multiple pages into memory in anticipation of more pages being used. There is a performance cost to this, loading fewer pages would result in a faster worst-case [3], but this behavior makes a trade off favoring the amortized time. <strong>This trade-off is best exploited by in-order access. Out-of-order access can be even worse than not prefetching at all.</strong></p><p>To see why, imagine a simplified case where 3 pages are prefetched.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LTvQidAtLy5RaRAVe8ZP3A.png" /></figure><p>The out-of-order case needs 3 slow faults to read 3 pages, plus the time to cache 3 additional pages. The in-order case only takes 2 slow faults. If we changed which pages were used instead of just their order it could be only one slow fault.</p><h4>Comparing Orders</h4><p>To measure this effect, I built a dylib that can be inserted into any app to detect the order of memory access. I ran it with large apps from the App Store, and reproduced their order of access in my measurement function. Combining this real-world page access pattern with the ideal in-order and a random access pattern shows us how much room for improvement there is.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pdf4P3b5oVcERb5mqSvmgQ.png" /></figure><p>As expected, random order starts off much slower than linear order and the total time is significantly greater. The slope tapers off towards the end because so much of the app has already been prefetched. Reproducing the order of pages faulted during app launch was somewhere in between the best case and random access. <strong>Order files turn your app from the green line into the red line, while reducing the total number of pages.</strong></p><p>Finally, to confirm that page access in a binary without an order file was relatively random, this graph shows the fault number (order of faults observed during app launch) vs. the page number (where the page is located within the binary).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ai41xtZV4db6KtGB_en_Zw.png" /></figure><p>Most of the faults appear in a random order, except for a clear linear pattern at the very beginning of app launch (circled in red). All the pages on this line contain protocol conformance metadata. This behavior happens because of the <a href="https://www.emergetools.com/blog/posts/SwiftProtocolConformance">linear scan of protocol conformances discussed in a previous blog post</a>.</p><h3>🚀️ Launch Booster</h3><p>In summary, accessing a page of memory can vary from milliseconds to 1/100s of a millisecond depending on which caches have the page. Ordering symbols in an app binary lets the system optimize memory access to decrease the likelihood of a slow page fault and reduce the total number of page faults by placing the memory you need on as few pages as possible.</p><p><strong>This research has led to Emerge’s new </strong><a href="https://docs.emergetools.com/docs/launch-booster"><strong>Launch Booster</strong></a><strong>, a binary ordering service that profiles apps in CI to determine the optimal ordering. Using Launch Booster, we’ve seen apps benefit from a 5–10% reduction in startup time!</strong></p><p>If you’re interested in Launch Booster or have any questions about order files, please <a href="mailto:team@emergetools.com">let us know!</a></p><p>[1] There are multiple systems that operate on pages and use different sizes. For example, code signing is done with 4kb pages. Page faults always involve a 16kb page, so we focus on that definition of page in this article.</p><p>[2] Including Objective-C class names, which are registered by the runtime before static initializers run.</p><p>[3] This can be seen by performing the same experiments on a smaller app binary. Fewer pages get prefetched, and each worst case is slightly faster.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c01f7765d29" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/how-order-files-reduce-app-startup-time-c01f7765d29">How Order Files Reduce App Startup Time</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Cost of a Byte]]></title>
            <link>https://medium.com/geekculture/the-cost-of-a-byte-3d675d00c25e?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/3d675d00c25e</guid>
            <category><![CDATA[environment]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[carbon-footprint]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Sun, 09 Jan 2022 04:02:27 GMT</pubDate>
            <atom:updated>2022-01-14T17:36:34.782Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AY2K7NrbhtaA9IBQYRQtdA.jpeg" /></figure><p>The size of an app has wide-reaching consequences on user experience.<br>At first glance, you might consider slower install times, more install failures, and a higher uninstall rate. But it doesn’t stop there. Unnecessary code leads to rising compile times. Large binaries increase how much work the runtime does to <a href="http://emergetools.com/blog/posts/SwiftProtocolConformance">lookup conformances</a>, slowing down every aspect of an app. The more classes you have, the more work is done by dyld, slowing app launches, and increasing memory usage. Some users pay for bandwidth usage, creating a literal direct cost to download your app. The effects even reach beyond your users, to the energy usage of the Internet as it transfers app downloads.</p><p>With more and more time spent online, people have been taking an interest in the energy usage and <a href="https://www.bbc.com/future/article/20200305-why-your-internet-habits-are-not-as-clean-as-you-think">environmental impact of the Internet</a>. Researchers from MIT, Purdue, and Yale even demonstrated that <a href="https://news.mit.edu/2021/how-to-reduce-environmental-impact-next-virtual-meeting-0304">turning off the camera during a Zoom meeting shrinks the environmental footprint by 96%</a>. This got me thinking about the environmental footprint of app size. While an app download is small compared to live video streaming, the scale of downloads can be in the 100s of millions. As is often the case with development at scale, a single decision shipped to this wide user base gives us a big opportunity.</p><h3>The Internet’s Footprint</h3><p>There are a few estimates for how much the Internet contributes to global emissions, falling somewhere around <a href="https://www.climatecare.org/resources/news/infographic-carbon-footprint-internet/">3% of the global total</a>. This is on par with the amount generated by the airline industry. While technology is getting more efficient, the number of people connected to the internet and the amount of bandwidth they require is also increasing rapidly.</p><p>In 2019 a <a href="https://theshiftproject.org/en/article/unsustainable-use-online-video/">report from the Shift project</a> made headlines claiming that 30 minutes of video streaming emits 1.6kg of CO2, the same amount as 4 miles of driving [1]. This figure was proven wrong and has since been updated. The most recent estimates are in the range of 30–80g, only 2–5% of the original estimate [2]. Great news for an individual’s contribution, but not changing the global contribution of technology and the Internet.</p><h3>An App’s Footprint</h3><p>Internet bandwidth is now primarily composed of video streaming, claiming <a href="https://www.sandvine.com/press-releases/sandvine-releases-2020-mobile-internet-phenomena-report-youtube-is-over-25-of-all-mobile-traffic">over 60% on mobile networks</a>. Of course, some network usage is going to app downloads, and we can use publicly available information to estimate the footprint of this traffic. First, we need to determine the energy usage per GB transferred over the Internet. There are many estimates out there, including <a href="https://www.iea.org/commentaries/the-carbon-footprint-of-streaming-video-fact-checking-the-headlines">reports from the International Energy Association</a> rebutting the Shift project’s original report, and a follow-up <a href="https://theshiftproject.org/en/article/shift-project-really-overestimate-carbon-footprint-video-analysis/">response from the Shift project</a>. Researchers have been <a href="https://www.sfu.ca/sca/projects---activities/streaming-carbon-footprint/">comparing these findings and more</a> to determine what data is most accurate. Many discrepancies come from different boundary definitions (data centers, networks, end devices), how far in the network data needs to travel (CDN served data could be more efficient), or network type (cellular vs Wi-Fi). The Shift Project created a <a href="https://theshiftproject.org/en/lean-ict-2/">1-byte report</a> which uses 2.24e-10 kWh/byte for Wi-Fi and 9.56e-10 kWh/byte for cellular.</p><p>Network efficiency is always improving, although it takes time to deploy new technologies, so let’s assume optimistically that the Wi-Fi estimate can apply to all app downloads. The US average electricity produces <a href="https://www.eia.gov/tools/faqs/faq.php?id=74&amp;t=11">0.386kg of CO2 per kWh</a> bringing our total number to 8.646e-5kg CO2 / MB.</p><p>Assuming updates are released weekly (4 times a month), an app’s carbon footprint is given by:</p><blockquote><strong>8.6464e-5 * (S_D * D + S_U * U * 4) = kg CO2/month</strong></blockquote><p><strong>S_D = App download size (MB)</strong></p><p><strong>D = Number of app downloads per month</strong></p><p><strong>S_U = App update size (MB)</strong></p><p><strong>U = Number of users who update</strong></p><p>Let’s plug in the numbers for the Uber iOS app.</p><p>8.6464e-5 * (110 * 4e6 + 69 * 23e6 * 4)=<strong>586.92</strong> metric tons CO2/month [3]</p><p>With the same usage stats, you can vary the size number to see that <strong>increasing app download size by 1MB increases emissions by 8,300 kg CO2/month</strong>.</p><h3>Relative Size</h3><p>These numbers are rough estimates because we don’t know the exact distribution of users or what networks they use. Nonetheless, compared to commonly cited contributors to a carbon footprint, adding/removing 1MB in app size for a month has a surprisingly large impact.</p><ul><li><a href="https://www.theguardian.com/environment/ng-interactive/2019/jul/19/carbon-calculator-how-taking-one-flight-emits-as-much-as-many-people-do-in-a-year">Round-trip flight from London to LAX</a> is <strong>20%</strong> of 1 MB</li><li><a href="https://interactive.carbonbrief.org/what-is-the-climate-impact-of-eating-meat-and-dairy/">1kg of beef</a> is <strong>less than 1%</strong> of 1 MB</li><li><a href="https://ecocostsavings.com/electric-car-kwh-per-mile-list/">Driving a Tesla Model S</a> for <strong>76,800 miles</strong> uses as much energy as 1MB</li></ul><p>The huge scale factor that applies when you take a small 1 MB download and multiply it by the millions of times an app like Uber is downloaded, drives the unexpectedly large carbon footprint. This is not to say that making smaller apps will be a huge help to climate change, quite the opposite. Despite all that we’ve shown, most of the energy consumed by a mobile device is gone before the user even gets a hold of it, in the manufacturing process [2]. A fully booked long-haul flight would create ~50x more emissions than 1 MB, only the per capita emissions are lower. Not to mention the switch to renewable energy could reduce the cost of a byte to zero.</p><p>When looking for ways to reduce one’s personal impact on the environment, like walking/biking instead of driving or substituting a veggie burger for a hamburger, it can be discouraging to consider the relatively low impact of these personal choices. It’s good to know those individual choices to reduce bandwidth when designing systems have an impact as well. Depending on user base size this can be an even more effective change. Nonetheless, every bit counts.</p><p>[1] <a href="https://nypost.com/2019/10/28/why-climate-change-activists-are-coming-for-your-binge-watch/">https://nypost.com/2019/10/28/why-climate-change-activists-are-coming-for-your-binge-watch/</a></p><p>[2] <a href="https://doi.org/10.1145/3490165">https://doi.org/10.1145/3490165</a></p><p>[3] 4M downloads per month according to <a href="https://app.sensortower.com/ios/publisher/uber-technologies-inc/368677371">Sensor Tower</a>. <a href="https://backlinko.com/uber-users">93M active users</a> with 50% of them on iOS and assuming half have auto-update enabled. Download and update sizes taken from iPhone 13.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3d675d00c25e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/the-cost-of-a-byte-3d675d00c25e">The Cost of a Byte</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Surprising Cost of Protocol Conformances in Swift]]></title>
            <link>https://medium.com/geekculture/the-surprising-cost-of-protocol-conformances-in-swift-dfa5db15ac0c?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/dfa5db15ac0c</guid>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Fri, 03 Dec 2021 09:04:17 GMT</pubDate>
            <atom:updated>2021-12-03T09:04:17.919Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*65L3b5U827Qz_HZ0aqlh9Q.png" /></figure><p>In my <a href="https://www.emergetools.com/blog/posts/SwiftReferenceTypes">last</a> <a href="https://www.emergetools.com/blog/posts/iOS15LaunchTime">two</a> posts I wrote about pre-main startup time, and how app size has a direct impact on how much work dyld does to initialize your app. In this post I’ll take a closer look at a Swift runtime feature, protocol conformance checks, to see how this common operation slows down post-main time as your binary size increases.</p><p>The first hint that this might be a cause for concern in app performance comes from the <a href="https://www.swift.org/blog/swift-5-4-released/">Swift 5.4 release notes</a>:</p><blockquote><em>In Swift 5.4, protocol conformance checks at runtime are significantly faster, thanks to a faster hash table implementation for caching previous lookup results. In particular, this speeds up common runtime </em><em>as? and </em><em>as!casting operations.</em></blockquote><p>Protocol conformance checks are when the runtime needs to look up if a variable conforms to a protocol. In your code this looks like myVar as? MyProtocol. Note that as? operations can also be used to cast variables to non-protocol types, and these do not cause a protocol conformance check. This is part of the dynamic nature of the Swift runtime. The as? operator indicates that a runtime cost will be paid for not guaranteeing the type at compile time.</p><p>From the release notes we know as? operations were slow enough that there was room to be “significantly” faster, but how slow are they exactly? Since a faster cache speeds it up, when do we hit the un-cached state and how slow is that? I used the <a href="https://docs.emergetools.com/docs/startup-flame-chart">app launch time visualizations</a> in Emerge to see if this would show up in stack trace samples of Swift apps... and sure enough... 👀 spotted! You can see below that swift_conformsToProtocol is taking 100+ms of app launch time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y-XMmr_O16UX6uFdEn7SNQ.png" /></figure><p>Let’s look at the Swift source code to see what causes the slowdown and how much time it takes in practice. Then we’ll discuss strategies to avoid this in your app, and even implement a faster replacement for the Swift runtime.</p><h3>What happens when you check a protocol conformance?</h3><p>The entry point to our investigation is <a href="https://github.com/apple/swift/pull/33487">Mike Ash’s PR</a> which implements a 13x faster cache that was released in Swift 5.4. From here you can see where the new cache is defined:</p><pre><strong>struct</strong> <strong>ConformanceState</strong> {<br>   ConcurrentReadableHashMap&lt;ConformanceCacheEntry&gt; Cache;<br>...</pre><p>The latest version of the code is <a href="https://github.com/apple/swift/blob/aec4ec4c4a0f647ae6670745cd389aba767f3111/stdlib/public/runtime/ProtocolConformance.cpp#L865">ProtocolConformance.cpp</a> in the function swift_conformsToProtocolMaybeInstantiateSuperclasses, where most time is spent based on the startup time visualizations. There are three high-level paths through this function, creating a 2 level cache followed by a slower full search for the conformance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IGU-zE08rNmH4-VeOrUmrQ.png" /></figure><ul><li>_dyld_find_protocol_conformance: Before consulting the ConcurrentReadableHashMap added in Swift 5.4, the runtime will check a cache managed by dyld. Presumably this allows the cache to be persisted between launches of the app. Support for this cache was added in iOS 15, and you can confirm the function is present in dyld using nm /usr/lib/dyld | grep _dyld_find_protocol_conformance | c++filt. Unfortunately, Swift imports this from dyld_priv.h so we can’t look at the implementation until Apple releases sources for the latest OS versions. You can set a symbolic breakpoint on this function and verify in a test app that it’s always called when using as?. It seems to be a work in progress, because in practice the second cache is always consulted (verified using another symbolic breakpoint).</li><li>ConcurrentReadableHashMap.find() Next the runtime checks the cache added in Swift 5.4, an in-memory cache that is not persisted between multiple runs of the app. You can verify it’s called by placing a symbolic breakpoint on _ZN5swift25ConcurrentReadableHashMapIN12_GLOBAL__N_121ConformanceCacheEntryENS_11StaticMutexEE4findINS1_19ConformanceCacheKeyEEENSt3__14pairIPS2_jEERKT_NS4_12IndexStorageEmS9_. This is the C++ mangled symbol[1] name for the function that takes a ConformanceCacheKey and returns a ConformanceCacheEntry. Looking at the definition of the cache key we can see it’s based on the conforming type and the protocol</li></ul><pre><strong>struct</strong> <strong>ConformanceCacheKey</strong> {<br>  const Metadata *Type;<br>  const ProtocolDescriptor *Proto;</pre><p>This means the cache key is specific to this type/protocol pair, multiple conformance checks for the same protocol but different types (or vice versa) won’t hit the cache. This can also be verified with symbolic breakpoints.</p><ul><li>The linear scan is a worst case, but is always done for each type/protocol pair before the cache is populated. Swift creates a special mach-o section __TEXT.__swift5_proto which is a list of pointers to each protocol conformance record in the binary. A conformance record is generated for every type/protocol pair, and allows the runtime to determine which conformances are in your app. This kind of binary metadata is the basis for how Emerge Tool’s analysis works, we use it to attribute parts of your source code to binary size.[2] The scan of all conformances is done for every loaded dylib in your app, including frameworks or system libraries like the Swift standard library itself. This means a single conformance lookup is O(n) and looking up every possible conformance is O(n^2), not great for performance!</li></ul><h3>How slow is it really?</h3><p>We now see that the speed of protocol conformance lookups is dependent on the number of conformances in your app. This will be influenced by how many Swift libraries you link to, and how many conformances you include in your own code. otool -l Helix.app/Helix | | grep _swift5_proto -A 4 tells us Uber’s app has a 411200 byte protocol conformance section. Each 4 bytes is a relative pointer so 411200 / 4 = 102,800 conformances. Based on this the test bed for my experiments is an app with 100k conformances. The classes were codegened, each conforming to the same protocol. All tests were performed on an iPhone 7 running iOS 15.1</p><p>Test 1: First conformance check The first time you perform a conformance check all the virtual memory for sections of your app binary containing protocol runtime metadata needs to be paged in. With 100k conformances this is a significant cost and makes the first conformance check much slower, ~20 milliseconds in my tests.</p><p>Test 2: Cache miss If the second conformance check is for a new type it will be uncached and still require the full scan of conformances, but this time it won’t cause any page faults. This took about 3.8 milliseconds in my tests. It may not seem like much, but it’s already 23% of the 16 milliseconds you have between frames on a 60fps device. These can add up to a substantial amount of time when multiple uncached protocol checks are performed.</p><p>Test 3: Cache hit To measure the time of the ConcurrentReadableHashMap we simply do the same as?operation in a loop and average the time it takes. As expected it’s very fast, about 0.0004 milliseconds. Once the cache is populated, protocol conformance checks aren’t a major bottleneck to performance, that’s why it’s particularly problematic during app launch when the cache is empty.</p><p>Test 4: Negative result The runtime loop that runs for each conformance has an <a href="https://github.com/apple/swift/blob/aec4ec4c4a0f647ae6670745cd389aba767f3111/stdlib/public/runtime/ProtocolConformance.cpp#L936">early return</a> that avoids most of the work if the protocol being checked isn’t equal to the protocol in a conformance record. To test the impact of this, I measured the time it takes to run a conformance check on a protocol that wasn’t the one conformed to by my 100k generated classes. It still took a significant time, 0.9 milliseconds, but this best-case time is ¼ of the previous worst case.</p><p>These tests provide some insight into the variation you’ll see with protocol conformance checks, but to really understand their impact I uploaded a few large Swift apps from the app store to Emerge and used the inverted flamegraph view to get a ballpark estimate of the time spent checking protocol conformance during app launch. Consistently over 100ms was spent in conformance checks for apps like Uber, DoorDash, and Grab.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5q63BR6h7cFA-_WfkWE8Qg.png" /></figure><h3>When does this happen?</h3><p>We already saw that apps pay the price for slow conformance lookups when doing an as? operation, but you can encounter the same performance hit with an as!. Swift verifies whether or not the protocol is satisfied when you use as! and will always abort or continue.</p><p>Generic type metadata is another source of protocol conformance checks. You’ll encounter a conformance lookup with code as simple as this:</p><pre><strong>class</strong> <strong>Test</strong>&lt;<strong>T</strong>: <strong>Decodable</strong>&gt; {}<br><strong>let</strong> _ = Test&lt;Int&gt;.<strong>self</strong></pre><p>Joe Groff explains in <a href="https://forums.swift.org/t/understanding-code-that-leads-to-swift-checkgenericrequirements-calls/35128">this thread</a> that Swift generates runtime metadata for the type from a mangled name in the binary. This demangling requires conformance lookups. You can see this kind of stack trace frequently in a Swift app launch, here’s an example that came from launching the Slack app with Emerge Tool’s app launch instrumentation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/952/1*pTBVDAftwE76Z5uB9BQ0DA.png" /></figure><p>Both JSON decoding and string interpolation also lead to as? operations. String interpolation calls <a href="https://github.com/apple/swift/blob/85d9507fde3deb5889f71f81f09a05319898b029/stdlib/public/core/OutputStream.swift#L404">_print_unlocked</a> which has 3 conformance checks and uses Mirror, internally performing even more conformance checks. The combined effect of this is a big hit to app performance.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0620dbf390896d9b8f77f274ca09d8eb/href">https://medium.com/media/0620dbf390896d9b8f77f274ca09d8eb/href</a></iframe><h3>Strategies to help improve app performance</h3><p>The two ways to approach improving app performance from protocol conformance checks is to minimize the number of conformance and as? operations. Emerge Tool’s app size analysis can help with both of these. We’ve always known app size is a leading indicator for app quality, and it’s demonstrated clearly here in the case of protocol conformances. By focusing on binary size reductions you’ll remove conformances from your app, and make the runtime faster.</p><p>One source of low hanging fruit that might be in your app is removing protocols that are used only for providing stub implementations in unit tests. These can be compiled out of release builds of the app to avoid them being included in runtime metadata.</p><blockquote><strong>TIP: </strong>Over in Objective-C land, there was an attribute added to clang earlier this year objc_non_runtime_protocol which instructs the compiler to not emit any metadata for a protocol. This reduces app size and improves runtime performance, if you know the protocol is only used at compile time. More detail is available in the <a href="https://clang.llvm.org/docs/AttributeReference.html#objc-non-runtime-protocol">attribute reference</a>.</blockquote><p>Profiling your app using tools like Instruments or the Emerge startup time visualization can help you identify where conformance checks are most often used in your app. Then you can refactor code to avoid them entirely. Consider these examples:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0c8691f538df0bf2c2a4eef36f285852/href">https://medium.com/media/0c8691f538df0bf2c2a4eef36f285852/href</a></iframe><p>In the second case, as long as the compiler knows the type of event at the callsite, it avoids the dynamic cast entirely.</p><p>As a concrete example, <a href="https://github.com/noahsmartin/lottie-ios/commit/3d3deed0d47f2c10abebdc029c41168b4c9f9f88">this commit</a> re-writes a small portion of code in the animation framework, Lottie, to avoid 22 possible conformance checks. There were 11 animation node types, each of which could have been checked against 2 protocols depending on what kind of animation was being loaded. The change easily bypasses dynamic casts, allowing the compiler to guarantee protocol conformance.</p><h3>Implementing a faster runtime</h3><p>At the lowest level the app binary is storing protocol conformances in a list, so there isn’t a way to do a complete conformance check without an O(n) scan through the whole list. However, that doesn’t mean we can’t convert the list into a data structure better suited for our needs. With so much of app launch time tied up in protocol conformance checks, we could bypass the runtime entirely and make our own data structure that allows for faster conformance checks. So that’s what we did.</p><p>The concept behind <a href="https://github.com/EmergeTools/zconform">zconform</a> is to eagerly load all possible protocol conformances and store them in a map keyed by the protocol’s address in memory. The value for each entry is a set holding the addresses of all conforming types. If a given type isn’t found in the cache, we know it’s not possible for the conformance to succeed and can early return nil without the as? operator.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c72334d0f58b930d65974939674176ac/href">https://medium.com/media/c72334d0f58b930d65974939674176ac/href</a></iframe><p>Zconform initialization uses getsectiondata to retrieve the address and size of TEXT.__swift5_proto for each mach-o image, then follows the chain of pointers to build up an unordered_map as the cache.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/df6e1ec32d46c016ba04bbae7614bb36/href">https://medium.com/media/df6e1ec32d46c016ba04bbae7614bb36/href</a></iframe><p>To check a conformance, input types are cast to their runtime representation using unsafeBitCast. The representations of these types in memory is found by examining the swift ABI, for example <a href="https://github.com/apple/swift/blob/e65ae80172ade4120ef51c100e1a69026866936e/include/swift/ABI/Metadata.h#L2059">ExistentialTypeMetadata</a>. Since you can compose protocols like typealias MultipleProtocols = MyProtocol1 &amp; MyProtocol2, we loop over the number of protocol conformances and validate each one.[3]</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/33051b210354080216c655a4d07c24ab/href">https://medium.com/media/33051b210354080216c655a4d07c24ab/href</a></iframe><p>Some details are left out in this example, but it‘s all available in the <a href="https://github.com/EmergeTools/zconform/blob/main/ZConformDemo/ZConform/ZConform.swift#L107">GitHub project</a>.</p><p>My benchmarking shows a 3 millisecond overhead to build up this cache. If your usage pattern matches what’s being optimized here, many conformance checks that may result in nil, it can entirely eliminate the worst case 3.8 milliseconds for as? we measured previously. The project is a proof of concept, and there are still some features necessary for a full rollout. It only supports checking conformance of structs to non-class bound protocols.</p><p><em>Support for more cases can be added, if you’re interested in using this for your app you can </em><a href="mailto:team@emergetools.com"><em>get in touch</em></a><em> with the </em><a href="https://emergetools.com"><em>Emerge</em></a><em> team.</em></p><p>[1] Symbol <a href="https://en.wikipedia.org/wiki/Name_mangling">name mangling</a> converts the human readable function name to the format stored in a binary symbol table.</p><p>[2] There is a bit more detail on runtime metadata layout when we build our own conformance check function, but if you’re interested in all the details of metadata in the binary check out this <a href="https://knight.sc/reverse%20engineering/2019/07/17/swift-metadata.html">post by Scott Knight</a>.</p><p>[3] For an introduction to these runtime metatypes and the difference between existentials and protocols see <a href="https://swiftrocks.com/whats-type-and-self-swift-metatypes">this post by Bruno Rocha</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dfa5db15ac0c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/the-surprising-cost-of-protocol-conformances-in-swift-dfa5db15ac0c">The Surprising Cost of Protocol Conformances in Swift</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How iOS 15 makes your app launch faster]]></title>
            <link>https://medium.com/geekculture/how-ios-15-makes-your-app-launch-faster-51cf0aa6c520?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/51cf0aa6c520</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[wwdc]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Wed, 23 Jun 2021 08:02:22 GMT</pubDate>
            <atom:updated>2021-07-28T20:15:28.933Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ce9bGZAEZVIJvhizc4P_Gg.png" /></figure><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/iOS15LaunchTime">Emerge Tools Blog</a></h4><p>The most intriguing feature from WWDC21 was buried deep in the <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-13-beta-release-notes">Xcode 13 release notes</a>:</p><blockquote>All programs and dylibs built with a deployment target of macOS 12 or iOS 15 or later now use the chained fixups format. This uses different load commands and LINKEDIT data, and won’t run or load on older OS versions.</blockquote><p>There isn’t any documentation or sessions to learn more about this change, but we can reverse engineer it to see what Apple is doing differently on the new OSes and if it will help your apps. First, a bit of background on the program that controls app startup.</p><h4>Meet dyld</h4><p>The dynamic linker (dyld) is the entry point of every app. It’s responsible for getting your code ready to run, so it would make sense that any improvement to dyld would result in improved app launch time. Before calling main, running static initializers, or setting up the Objective-C runtime, dyld performs fixups. These consist of rebase and bind operations which modify pointers in the app binary to contain addresses that will be valid at runtime. To see what these look like, you can use the dyldinfo command line tool.</p><pre>% xcrun dyldinfo -rebase -bind Snapchat.app/Snapchat<br>rebase information (from compressed dyld info):<br>segment section          address     type<br>__DATA  __got            0x10748C0C8  pointer<br>...<br>bind information:<br>segment section address     type    addend dylib        symbol<br>__DATA  __const 0x107595A70 pointer 0      libswiftCore _$sSHMp</pre><p>This means address 0x10748C0C8 is located in __DATA/__got and needs to be shifted by a constant value (known as the slide). While address 0x107595A70 is in __DATA/__const and should point to the protocol descriptor for Hashable[1] found in libswiftCore.dylib</p><p>dyld uses the LC_DYLD_INFO load command and<a href="https://developer.apple.com/documentation/kernel/dyld_info_command">dyld_info_command</a> struct to determine the location and size of rebases, binds and <a href="https://github.com/qyang-nj/llios/blob/main/exported_symbol/README.md">exported symbols</a>[2] in a binary. <a href="https://emergetools.com">Emerge</a> (disclaimer: I’m the founder 😬), parses this data to let you visualize their contribution to binary size as well as suggest linker flags to make them smaller:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xuvbj8du9_jfDScrZ9lTRA.png" /></figure><h4>A new format</h4><p>When I first uploaded an app built for iOS 15 to <a href="https://emergetools.com">Emerge</a> there was no visualization of dyld fixups. This was because the LC_DYLD_INFO_ONLY load command was missing, it had been replaced by LC_DYLD_CHAINED_FIXUPS and LC_DYLD_EXPORTS_TRIE.</p><pre>% otool -l iOS14Example.app/iOS14Example | grep LC_DYLD<br>      cmd LC_DYLD_INFO_ONLY</pre><pre>% otool -l iOS15Example.app/iOS15Example | grep LC_DYLD<br>      cmd LC_DYLD_CHAINED_FIXUPS<br>      cmd LC_DYLD_EXPORTS_TRIE</pre><p>The export data is exactly the same as before, a trie where each node represents part of a symbol name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5K-xerw4dop2E_Um-slQMQ.png" /><figcaption>Portion of the exports trie for Wikipedia</figcaption></figure><p>The only change in iOS 15 is the data is now referenced by a <a href="https://developer.apple.com/documentation/kernel/linkedit_data_command">linkedit_data_command</a> which contains the offset of the first node. To validate this, I wrote a short Swift app to parse the iOS 15 binary and print each symbol:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7e619550e919f7ad1d05aed3c3efdb6/href">https://medium.com/media/b7e619550e919f7ad1d05aed3c3efdb6/href</a></iframe><h4>Chaining</h4><p>The real change is in LC_DYLD_CHAINED_FIXUPS. Before iOS 15, rebases, binds and lazy binds were each stored in a separate table. Now they have been combined into chains, with pointers to the starts of the chains contained in this new load command…</p><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/iOS15LaunchTime">Emerge Tools Blog</a></h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=51cf0aa6c520" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/how-ios-15-makes-your-app-launch-faster-51cf0aa6c520">How iOS 15 makes your app launch faster</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Swift Reference Types Are Bad for App Startup Time]]></title>
            <link>https://medium.com/geekculture/why-swift-reference-types-are-bad-for-app-startup-time-90fbb25237fc?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/90fbb25237fc</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[objective-c]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Thu, 04 Mar 2021 09:03:05 GMT</pubDate>
            <atom:updated>2021-08-04T16:10:20.404Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mjZP9T5eEzSbLezRzcleeg.jpeg" /></figure><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/SwiftReferenceTypes">Emerge Tools Blog</a></h4><p>The app launch experience is the first impression you make on a user. Every millisecond they wait for your app to start is valuable time they could spend elsewhere. If your app has high engagement and is used multiple times a day then users have to wait for launch over and over. Apple <a href="https://developer.apple.com/videos/play/wwdc2019/423/?time=305">recommends</a> the first frame be drawn in under 400ms. This ensures your app is ready to be used when Springboard’s app open animation finishes.</p><p>With only 400ms to spare, developers need to be very careful not to accidentally increase app startup time. However, app launch is such a complicated process with so many moving parts that it’s difficult to know what exactly contributes to it. I started diving deeper into the relationship between app binary size and startup time while working on <a href="https://www.emergetools.com/">Emerge</a>, the app size profiler. In this post, I’ll demystify one of the more esoteric aspects of app launch and show you how Swift reference types contribute to the binary size and slower app start times.</p><h3>Dyld</h3><p>Your app starts when the Macho-O executable is loaded by dyld. Dyld is Apple’s program responsible for getting an app ready to use. It runs in the same process as the code you write and starts by loading all dependent frameworks¹, including any system frameworks.</p><p>Part of dyld’s job is “rebasing” pointers in binary metadata that describe types in your source code. This metadata allows for dynamic runtime features, but can be a common source of binary size bloat. Here’s the layout of an Obj-C class found in a compiled app binary:</p><pre><strong>struct</strong> ObjcClass {<br>  <strong>let</strong> isa: UInt64<br>  <strong>let</strong> superclass: UInt64<br>  <strong>let</strong> cache: UInt64<br>  <strong>let</strong> mask: UInt32<br>  <strong>let</strong> occupied: UInt32<br>  <strong>let</strong> taggedData: UInt64<br>}</pre><p>Each UInt64 is the address of another piece of metadata. This is in the app binary, so everyone in the world downloads the exact same data from the app store. However, each time your app is launched it’s placed in a different location in memory (as opposed to always starting at 0) due to address space layout randomization (<a href="https://en.wikipedia.org/wiki/Address_space_layout_randomization">ASLR</a>). This is a security feature designed to make it hard to predict where a particular function is in memory.</p><p>The problem with ASLR is the address hardcoded into your app binary is now wrong, it’s offset by a random start location. Dyld is responsible for correcting this by rebasing all pointers to take into account the unique start location. This process is done for every pointer in your executable, and all dependent frameworks, including recursive dependencies. There are other kinds of metadata setup done by dyld which impact startup time, such as “binding”, but for this article, we’ll just focus on rebases.</p><p>All this pointer setup increases app startup time, so <strong>reducing it results in a smaller app binary and a faster start time</strong>. Let’s see where it comes from and exactly what the impact can be.</p><h3>Swift and Obj-C</h3><p>We saw that rebase time is caused by Obj-C metadata in your app, but what exactly causes this metadata in a Swift app? Swift has the @objc attribute to make declarations visible from Objective-C code, but metadata is generated even when your Swift type is not visible to Obj-C code. This is because <strong>all Swift class types contain Objective-C metadata on Apple platforms</strong>. Let’s see this in action with the following declaration:</p><pre><strong>final class</strong> TestClass { }</pre><p>This is pure Swift, it doesn’t inherit from NSObject and doesn’t use @objc However, it will produce an Obj-C class metadata entry in the binary and add 9 pointers that need rebasing! To prove this, inspect the binary with a tool like Hopper and see the objc_class entry for your “pure Swift” class:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YG3ujvEuShNIKChjypdSxg.png" /><figcaption>Obj-C metadata in the app binary</figcaption></figure><p>You can view the exact amount of pointer rebasing needed to launch an app by setting the DYLD_PRINT_STATISTICS_DETAILS environment variable to 1. This will print the total number of rebase fixups to the console after the app launch. We can even map out exactly where these 9 pointers are found.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kOLgPsjKEuVZUqAzOceyZQ.jpeg" /></figure><p>Not all Swift types add the same number of rebases. If you expose methods to Obj-C by overriding from a superclass or conforming to an Obj-C protocol you’ll add even more rebases. Plus every property on your Swift class will generate an ivar in Objective-C metadata.</p><h3>Measuring</h3><p>The actual launch time impact of rebasing will vary based on the device type and what else is running on the phone. I measured on one of the oldest devices still commonly supported, the iPhone 5S.</p><p>iOS launches can be roughly categorized into warm and cold. Warm is when the system has already launched the app and cached some dyld setup information. Since the first launch I tested was a cold start it was a bit slower than others².</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fef7beb3cca14c6f25690542edcd19f9/href">https://medium.com/media/fef7beb3cca14c6f25690542edcd19f9/href</a></iframe><p>In this case, we’re seeing ~1ms increase per 2000 rebase operations. This won’t be an absolute addition to startup time because some operations can be done in parallel, but it does give us a lower bound, and with 400k rebases we are already halfway to Apple’s recommended limit of 400ms.</p><h3>Examples</h3><p>Measuring the number of rebase operations in a few popular apps gives a sense of how common these are in practice.</p><pre>% xcrun dyldinfo -rebase TikTok.app/TikTok | wc -l<br>2066598</pre><p>TikTok has over 2 million rebases, this results in a whole second of startup time! TikTok uses Objective-C, but I also tested a few of the largest Swift apps that use a monolithic binary architecture (as opposed to frameworks) and found between 685k and 1.8m rebases.</p><h3>What can be done?</h3><p>Although each class increases rebase operations, I’m not recommending replacing every Swift class with a struct. Large structs can also increase the binary size and in some cases, you just need reference semantics. As with any performance improvement, you should avoid premature optimization and start with measurement. <a href="https://www.emergetools.com/">Emerge</a> can determine how many rebases are in your app, which modules they come from, and what types in those modules are the biggest contributors. Once you’ve measured the problem you can look for areas of improvement in your own app. Here are a few common cases:</p><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/SwiftReferenceTypes">Emerge Tools Blog</a></h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90fbb25237fc" width="1" height="1" alt=""><hr><p><a href="https://medium.com/geekculture/why-swift-reference-types-are-bad-for-app-startup-time-90fbb25237fc">Why Swift Reference Types Are Bad for App Startup Time</a> was originally published in <a href="https://medium.com/geekculture">Geek Culture</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How 7 iOS Apps Could Save You 500MB of Storage]]></title>
            <link>https://medium.com/swlh/how-7-ios-apps-could-save-you-500mb-of-storage-a828782c973e?source=rss-26887d1ff012------2</link>
            <guid isPermaLink="false">https://medium.com/p/a828782c973e</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[apps]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[app-development]]></category>
            <dc:creator><![CDATA[Noah Martin]]></dc:creator>
            <pubDate>Thu, 14 Jan 2021 17:40:20 GMT</pubDate>
            <atom:updated>2021-08-04T16:11:44.148Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vuK6ppU-zevGLmMt42VZog.jpeg" /><figcaption>Breakdown of the Spark app, red indicates duplicates</figcaption></figure><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/7AppsThatCouldSaveYou500MB">Emerge Tools Blog</a></h4><h3><strong>Background</strong></h3><p>Many mobile developers can attest to the problems of app size increase.</p><p>Sometimes it’s a quick feature release with an uncompressed image that adds 5%. Sometimes it’s a slow, steady increase of 0.5% each week.</p><p>I worked on these issues as a software engineer at Airbnb for 4.5 years, and in my own apps with ThnkDev. Recently, I personally analyzed over 150 apps to try to solve the issue of app size once and for all. I wanted to share my findings about common mistakes with everyone so we can all build lighter and faster apps! 🚀</p><p>Knowing exactly what goes into your app is tricky for fast moving teams, thats why <strong>automated testing and alerts around best practices</strong> makes apps more efficient. <a href="https://www.emergetools.com/">Emerge</a> aims to do this for your apps.</p><h3><strong>Why?</strong></h3><p>App quality is always top of mind for developers and one of the most important factors is what actually gets shipped to users. However, it’s not easy to validate what goes into your final .app and bugs can often slip through unnoticed. Developers can easily get into situations where duplicate files need to be kept in sync, extra code delays app startup, or file fragmentation bloats the install size. Your app’s size is a user’s first impression of your app, longer download times can lead to missed users or failed installs. Not to mention, uninstall rate goes up as app size increases and users look for ways to free up space on their devices.</p><h4>Experiments indicate as much as a 0.5% drop in install conversions per megabyte added.</h4><h3><strong>What is app size anyway?</strong></h3><p>App size can be tricky to measure, there are many places that display size which can be contradicting and enough context isn’t always provided. In general there are 2 measurements to know: install and download size.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*rztO9TJTMBFmSamJ" /><figcaption>Install and download size for Hopper</figcaption></figure><p>Users most often see install size. It’s on the App Store page and shown in system settings. Install size is how much storage your app takes up on a user’s phone. To keep things simple in the rest of this post when we refer to size, it’s install size, because we care most about what users see. <strong>You can read all about the different ways to measure size in the </strong><a href="https://docs.emergetools.com/docs/what-is-app-size"><strong>Emerge documentation</strong></a><strong>.</strong></p><h4>Now let’s get started!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3VNS6E8dFgTEyk6xXDxg7g.png" /><figcaption><a href="https://www.emergetools.com/app/example/dropbox">https://www.emergetools.com/app/example/dropbox</a></figcaption></figure><p>Dropbox has the most savings of any app we’ve measured. Weighing in at 269 MB, our tool’s analysis shows <strong>potential size reductions of 40%</strong>. Most of this extra size is coming from duplicate localization files. As you can see from their app breakdown, each app extension has the same copy of Localizable.strings as the main app bundle, for all 21 supported languages. In addition to duplicates, the Localizable.strings files contain comments used to provide context to translators. These aren’t needed in the production app, stripping comments in production would save ~46mb. With the current setup, every new language will account for about 4mb. There are a few other things that could be improved, but let’s go on to some other apps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*C-_n5DFchOwKmGn_-cDlZA.png" /><figcaption><a href="https://www.emergetools.com/app/example/spark">https://www.emergetools.com/app/example/spark</a></figcaption></figure><p>The Spark app is almost 10% font files. Taking a closer look at the fonts you might notice some familiar names:</p><ul><li>SF-Pro-Text-Semibold.otf</li><li>SF-Pro-Text-Light.otf</li><li>SF-Pro-Text-Medium.otf</li><li>SF-Pro-Text-Heavy.otf</li><li>SF-Pro-Text-Bold.otf</li><li>SF-Pro-Text-Regular.otf</li></ul><p>Each of these font files appear twice in the app, once in the main app bundle and once in the sharing extension. These are the default fonts included in iOS, and can be downloaded <a href="https://developer.apple.com/fonts/">here</a>. According to the license agreement shown when installing these fonts:</p><blockquote><strong>IMPORTANT NOTE: THE APPLE SAN FRANCISCO FONT IS TO BE USED SOLELY FOR CREATING MOCK-UPS OF USER INTERFACES TO BE USED IN SOFTWARE PRODUCTS</strong> <strong>RUNNING ON APPLE’S iOS, iPadOS, macOS OR tvOS OPERATING SYSTEMS, AS APPLICABLE.</strong></blockquote><p>They shouldn’t need to be included in any app targeting iOS 11+, as they can all use the default system font. Removing them would reduce Spark’s size by ~20mb.</p><h4>Edit: Some have pointed out that the SF fonts change even in later iOS versions. There still may be some license agreement violations for using these, and even if they are needed it doesn’t change the total savings for Spark which is calculated assuming the duplicate copy of the font is removed and only one is kept.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RC2HW8idAFq28_am0cmNdA.png" /><figcaption><a href="https://www.emergetools.com/app/example/ebay">https://www.emergetools.com/app/example/ebay</a></figcaption></figure><p>The Ebay app has an interesting architecture where the main app’s executable is only ~150kb although 86% of the app size is executable files. This is because most of their app consists of frameworks, the biggest one (34mb) is EbayApp.framework.<strong> Almost 100mb of these frameworks are unnecessary</strong> swift symbols. When you build a swift framework, the binary contains a symbol table which you can read with the nm command.</p><pre>nm -m eBayApp.framework/eBayApp<br>...<br>s7eBayApp28SettingsViewControllerLegacyC13viewDidAppearyySbFTo</pre><p>Ebay has tens of thousands of these non-external symbols. After stripping them using the method described <a href="https://docs.emergetools.com/docs/strip-binary-symbols">here</a> the size is greatly reduced.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ya5OH02p1GZ8gXdUugiPzA.png" /><figcaption><a href="https://www.emergetools.com/app/example/twitch">https://www.emergetools.com/app/example/twitch</a></figcaption></figure><h4>Read the full version of this post on the <a href="https://www.emergetools.com/blog/posts/7AppsThatCouldSaveYou500MB">Emerge Tools Blog</a></h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a828782c973e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/how-7-ios-apps-could-save-you-500mb-of-storage-a828782c973e">How 7 iOS Apps Could Save You 500MB of Storage</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>