{"id":3176,"date":"2012-12-17T08:13:13","date_gmt":"2012-12-17T16:13:13","guid":{"rendered":"http:\/\/blog.xamarin.com\/?p=3176"},"modified":"2020-08-18T06:00:10","modified_gmt":"2020-08-18T13:00:10","slug":"handling-large-assets-on-ios","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/xamarin\/handling-large-assets-on-ios\/","title":{"rendered":"Handling Large Assets on iOS"},"content":{"rendered":"<p>Most mobile application ship with read-only assets: images, sounds, videos, documents, databases. Assets, both in numbers and in size, tend to grow over time. Large assets that take many seconds or even minutes to transfer to the device can really slow down your development. Copying the same static assets each time you deploy your application is wasted time. Surely we can do better. On iOS there&#8217;s a little-known solution to this situation. The trick is to: 1. Enable iTunes sharing on your application. That&#8217;s basically adding <code>&lt;a href=\"http:\/\/developer.apple.com\/library\/ios\/documentation\/general\/Reference\/InfoPlistKeyReference\/Articles\/iPhoneOSKeys.html#\/\/apple_ref\/doc\/uid\/TP40009252-SW20\"&gt;UIFileSharingEnabled&lt;\/a&gt;<\/code> to your application&#8217;s <code>Info.plist<\/code> file; 2. <a href=\"http:\/\/support.apple.com\/kb\/HT4094\">Use iTunes to copy your assets into your device(s)<\/a>; 3. Adjust your application to load the assets from the <em><Application_Home><\/em><code>\/Documents<\/code> directory; This last step can be done by changing your paths from using <code>NSBundle.MainBundle.BundlePath<\/code> to <code>Environment.GetFolderPath(Environment.SpecialFolder.Personal)<\/code>. Adding those few changes can save you quite a bit of time. However they require modifications to your application and project files that might cause you other issues later. E.g. \n*   Enabling iTunes sharing is useful for some applications, i.e. you might want to keep this option in your released applications. However it does not make sense for many applications so you&#8217;ll likely want to disable it for non-debug builds. Apple has been known to reject applications enabling this settings without a useful purpose;\n*   You must remove the asset references from your project, or they will still be uploaded to the device. This will break your non-debug builds. Here is a safer, step-by-step way to do this for<\/p>\n<p><strong>Debug<\/strong> builds, while keeping your other build configurations unmodified. 1. Let&#8217;s create an empty MonoTouch application called <strong>LargeAssets<\/strong>. Replace the <code>AppDelegate.cs<\/code> source with this:<\/p>\n<div style=\"font-size: 11px\">\n<pre class=\"lang:csharp decode:true\">using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Threading;\n\nusing MonoTouch.Foundation;\nusing MonoTouch.UIKit;\n\nnamespace LargeAssets {\n\n    [Register (\"AppDelegate\")]\n    public partial class AppDelegate : UIApplicationDelegate {\n\n        UIWindow window;\n        UIAlertView alert;\n        const string dbname = \"LargeDatabase.sqlite\";\n\n        public override bool FinishedLaunching (UIApplication app, NSDictionary options)\n        {\n            window = new UIWindow (UIScreen.MainScreen.Bounds);\n\n            ThreadPool.QueueUserWorkItem (delegate {\n                \/\/ for NSBundle.MainBundle.BundlePath for \"BuildAction == Content\"\n                string database = Path.Combine (NSBundle.MainBundle.BundlePath, dbname);\n\n                FileInfo asset = new FileInfo (database);\n                InvokeOnMainThread (delegate {\n                    string message = String.Format (\"Size: {0} bytes\", asset.Length);\n                    alert = new UIAlertView (\"Asset Status\", message, null, null, \"Ok\");\n                    alert.Show ();\n                });\n            });\n\n            window.RootViewController = new UIViewController ();\n            window.MakeKeyAndVisible ();\n            return true;\n        }\n    }\n}\n<\/pre>\n<\/div>\n<ol>\n<li>Add a large asset, e.g. a 80MB sqlite database, to your application and make sure its <\/li>\n<\/ol>\n<p><strong>Build Action<\/strong> is set to <strong>Content<\/strong>. <a href=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-build-action-content.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-build-action-content.png\" alt=\"\" width=\"525\" height=\"507\" class=\"aligncenter size-full wp-image-42560\" srcset=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-build-action-content.png 525w, https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-build-action-content-300x290.png 300w\" sizes=\"(max-width: 525px) 100vw, 525px\" \/><\/a> 3. Build and then deploy to your device. For reference note how much time is needed to deploy the application to your device. For the 80MB test case deploying on an iPad 3 takes 19015 ms for a <strong>Debug<\/strong> build and 17910 ms on a <strong>Release<\/strong> build. The last being a bit faster since binaries are smaller (smaller AOT&#8217;ed executable, IL stripped assemblies) and no debugging symbols files are present. Now that we have our baseline let&#8217;s optimize it &#8211; starting by the creation of two files for each existing asset. From a Terminal windows inside your project directory do the following:<\/p>\n<pre class=\"lang:shell\">% ls -l *.sqlite\n-rw-r--r-- 1 me staff 80734208 14 Dec 16:33 LargeDatabase.sqlite\n\n% cp LargeDatabase.sqlite LargeDatabase.sqlite.Release\n% touch LargeDatabase.sqlite.Debug\n\n% ls -l *.sqlite*\n-rw-r--r-- 1 me staff 80734208 14 Dec 16:33 LargeDatabase.sqlite\n-rw-r--r-- 1 me staff 0 14 Dec 16:33 LargeDatabase.sqlite.Debug\n-rw-r--r-- 1 me staff 80734208 14 Dec 16:33 LargeDatabase.sqlite.Release\n<\/pre>\n<p>Keep your<\/p>\n<p><code>LargeAssets.csproj<\/code> unchanged, i.e. referencing <code>LargeDatabase.sqlite<\/code>. Note: If you keep your assets under source control then make sure you keep only one huge asset (and two empty versions). Next we need to add a <strong>Custom Command<\/strong> to your project &#8211; for <strong>all<\/strong> Configurations (e.g. Debug, Release, Ad Hoc and AppStore). The <strong>Command<\/strong> to invoke, <strong>Before Build<\/strong>, is:<\/p>\n<pre class=\"lang:shell\">sh .\/asset.sh ${ProjectConfig}\n<\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-project-options-300x196.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2019\/04\/large-assets-project-options-300x196.png\" alt=\"\" width=\"300\" height=\"196\" class=\"aligncenter size-full wp-image-42561\" \/><\/a>\nThe shell script itself, below, can be added inside your project.<\/p>\n<div style=\"font-size: 11px\">\n<pre class=\"lang:shell decode:true\">#! \/bin\/sh\nif [ \"$1\" = \"Debug.iPhone\" ] ; then\n    \/usr\/libexec\/PlistBuddy -c \"Add :UIFileSharingEnabled bool true\" Info.plist\n    for file in *.Debug ; do\n        cp \"$file\" \"${file\/.Debug\/}\"\n    done\nelse\n    \/usr\/libexec\/PlistBuddy -c \"Delete :UIFileSharingEnabled\" Info.plist \n    for file in *.Release ; do\n        cp \"$file\" \"${file\/.Release\/}\"\n    done\nfi\n<\/pre>\n<\/div>\n<p>That will ensure the<\/p>\n<p><code>asset.sh<\/code> script is being called before starting a new build. In turn the script will do two actions for your project: \n1.  It will use <code>PlistBuddy<\/code> to add the <code>UIFileSharingEnabled<\/code> value on <strong>Debug<\/strong> build. It will also remove this key from all other build configurations;\n2.  It will copy either the <code>*.Debug<\/code> or <code>*.Release assets<\/code> to their original asset name (note: only in the main project directory, you&#8217;ll need to adapt the script if you have several directories or different requirements). This ensure that: \n    *   all assets will be empty for <strong>Debug<\/strong> builds, giving your faster device deployment times;\n    *   all assets will be just like before for <strong>non-Debug<\/strong> builds;\n    *   your .csproj files are kept unmodified. Next you need to adjust your application to load the assets from the &#8220;right&#8221; location. For our example replace the earlier<\/p>\n<p><code>AppDelegate.cs<\/code> source code with this one:<\/p>\n<div style=\"font-size: 11px\">\n<pre class=\"lang:csharp decode:true\">using System;\nusing System.IO;\nusing System.Threading;\n\nusing MonoTouch.Foundation;\nusing MonoTouch.ObjCRuntime;\nusing MonoTouch.UIKit;\n\nnamespace LargeAssets {\n\n    [Register (\"AppDelegate\")]\n    public partial class AppDelegate : UIApplicationDelegate {\n\n        UIWindow window;\n        UIAlertView alert;\n        const string dbname = \"LargeDatabase.sqlite\";\n\n        public override bool FinishedLaunching (UIApplication app, NSDictionary options)\n        {\n            window = new UIWindow (UIScreen.MainScreen.Bounds);\n\n            ThreadPool.QueueUserWorkItem (delegate {\n#if DEBUG\n                \/\/ In Debug mode we can cheat and avoid deploying *every* time large, \n                \/\/ read-only assets to device. We simply deploy it once using iTunes\n                \/\/ and load it from the app\/Document directory\n                string path = Environment.GetFolderPath (Environment.SpecialFolder.Personal);\n                string database = Path.Combine (path, dbname);\n                if (!File.Exists (database)) {\n                    if (Runtime.Arch == Arch.SIMULATOR) {\n                        \/\/ we can even cheat further by copying the database into the simulator directory\n                        \/\/ e.g. if it does not exists or if a newer version is available...\n                        File.Copy (\"\/Users\/me\/path\/to\/your\/LargeDatabase.sqlite\", database);\n                    } else {\n                        \/\/ oops, forgot to copy assets on a new device ?\n                        InvokeOnMainThread (delegate {\n                            alert = new UIAlertView (\"DEBUG : Missing assets\", \n                                \"Use iTunes to copy assets files into your application\", \n                                null, null, \"Ok\");\n                            alert.Show ();\n                        });\n                        return;\n                    }\n                }\n#else\n                \/\/ In Release mode we need to copy\/deploy the real assets in the correct location\n                \/\/ for NSBundle.MainBundle.BundlePath for \"BuildAction == Content\"\n                string database = Path.Combine (NSBundle.MainBundle.BundlePath, dbname);\n#endif\n                FileInfo asset = new FileInfo (database);\n                InvokeOnMainThread (delegate {\n                    string message = String.Format (\"Size: {0} bytes\", asset.Exists ?\n                        asset.Length : -1);\n                    alert = new UIAlertView (\"Asset Status\", message, null, null, \"Ok\");\n                    alert.Show ();\n                });\n            });\n\n            window.RootViewController = new UIViewController ();\n            window.MakeKeyAndVisible ();\n            return true;\n        }\n    }\n}\n<\/pre>\n<\/div>\n<p>Finally try building\/deploying the application again for both<\/p>\n<p><strong>Debug<\/strong> and <strong>Release<\/strong> builds and compare the required time with the original version. With our 80MB database we now get 4801 ms for a <strong>Debug<\/strong> build (only 25% of the original time) and 17567 ms for the <strong>Release<\/strong> build (98%) which not a surprise since it&#8217;s almost identical to the original code and .app size. Exactly how much time you can save depends on your assets size and how often you update them, i.e. need to use iTunes to copy them. <a href=\"http:\/\/forums.xamarin.com\/discussion\/604\/handling-large-assets\">Discuss this post on the Xamarin forums<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most mobile application ship with read-only assets: images, sounds, videos, documents, databases. Assets, both in numbers and in size, tend to grow over time. Large assets that take many seconds or even minutes to transfer to the device can really slow down your development. Copying the same static assets each time you deploy your application [&hellip;]<\/p>\n","protected":false},"author":5741,"featured_media":39167,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[303],"tags":[6,4],"class_list":["post-3176","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ios","tag-ios","tag-xamarin-platform"],"acf":[],"blog_post_summary":"<p>Most mobile application ship with read-only assets: images, sounds, videos, documents, databases. Assets, both in numbers and in size, tend to grow over time. Large assets that take many seconds or even minutes to transfer to the device can really slow down your development. Copying the same static assets each time you deploy your application [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/3176","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/users\/5741"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/comments?post=3176"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/3176\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media\/39167"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media?parent=3176"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/categories?post=3176"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/tags?post=3176"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}