{"id":28176,"date":"2020-02-18T15:40:16","date_gmt":"2020-02-18T20:40:16","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=28176"},"modified":"2020-02-24T16:28:13","modified_gmt":"2020-02-24T21:28:13","slug":"ios-optimization-tips","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2020\/02\/18\/ios-optimization-tips\/","title":{"rendered":"iOS Optimization Tips"},"content":{"rendered":"<p><a href=\"https:\/\/www.fadel.io\/blog\/posts\/ios-performance-tips-you-probably-didnt-know\/\">Rony Fadel<\/a> (<a href=\"https:\/\/twitter.com\/ronyfadel\/status\/1229728050448863232\">tweet<\/a>):<\/p>\n<blockquote cite=\"https:\/\/www.fadel.io\/blog\/posts\/ios-performance-tips-you-probably-didnt-know\/\">\n<p>We&rsquo;re tempted to think of labels as lightweight in terms of memory usage. In the end, they just display text. <code>UILabels<\/code> are actually stored as bitmaps, which could easily consume megabytes of memory.<\/p>\n<p>[&#8230;]<\/p>\n<p>When you <code>dispatch_async<\/code> a block onto a concurrent queue, GCD will attempt to find an idle thread in its thread pool to run the block on. If it can&rsquo;t find an idle thread, it will have to <strong>create a new thread<\/strong> for the work item. Quickly dispatching blocks to a concurrent queue could leads to quickly creating new threads.<\/p>\n<p>[&#8230;]<\/p>\n<p>The concurrent queues you get from <code>dispatch_get_global_queue<\/code> are bad at forwarding QoS information to the system and should be avoided.<\/p>\n<p>[&#8230;]<\/p>\n<p>Avoid using <code>dispatch_semaphore_t<\/code> to wait for asynchronous work<\/p>\n<p>[&#8230;]<\/p>\n<p>UIKit implements tags using <code>objc_get\/setAssociatedObject()<\/code>, meaning that every time you set or get a tag, you&rsquo;re doing a dictionary lookup, which will show up in Instruments[&#8230;]<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/jsorge.net\/2020\/02\/18\/ios-performance-tips\">Jared Sorge<\/a>:<\/p>\n<blockquote cite=\"https:\/\/jsorge.net\/2020\/02\/18\/ios-performance-tips\">\n<p>I don&rsquo;t know how many times I have kept text in a label when I perhaps didn&rsquo;t need to. I <em>also<\/em> didn&rsquo;t know that the right place to nil-out text in reusable views (<code>UI{Table|Collection}ViewCell<\/code>) is not in their <code>prepareForReuse()<\/code> method but in the delegate&rsquo;s <code>didEndDisplaying<\/code> method instead.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2019\/08\/06\/practical-concurrency-some-rules\/\">Practical Concurrency: Some Rules<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2018\/04\/20\/gcd-tips\/\">GCD Tips<\/a><\/li>\n<\/ul>\n\n<p id=\"ios-optimization-tips-update-2020-02-24\">Update (2020-02-24): <a href=\"https:\/\/twitter.com\/pedantcoder\/status\/1229999590482444288\">Pierre Habouzit<\/a> explains how to avoid semaphores (<a href=\"https:\/\/threadreaderapp.com\/thread\/1229999590482444288.html\">thread<\/a>):<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/pedantcoder\/status\/1229999590482444288\"><p>QoS is a label, its rules of propagation are semi complex, but DO NOT depend on the state of the system.<\/p><p>It&rsquo;s propagated by only 2 mechanisms (and anything built atop of it), and one secondary obsolete subsystem.<\/p><p>[&#8230;]<\/p><p>So what you need for priority inversion to kick in, is a wait primitive that has ownership information, IO primitives that record ownership<\/p><p>The list is pretty short:<\/p><p>- pthread mutexes and os unfair locks (and things built on top)<br><\/br>\n- dispatch_sync() (but for reasons not onto the main queue, but that doesn&rsquo;t matter for apps)<br><\/br>\n- xpc_connection_send_with_message_sync()<\/p><p>[&#8230;]<\/p><p>dispatch_block_wait() isn&rsquo;t multi-hop. It works for the main thread to wait in certain circumstances (if what you wait on has been asynced <em>before<\/em> you start to wait on is an important one), because in an app it&rsquo;s the highest priority you can have, so the likelyhood of requiring more than 1 hop to resolve an inversion is super low. It doesn&rsquo;t work nearly as well in other cases, for which a better pattern is to share a lock around your work, and have the waiter take that lock to see if the work was done or being done in which case the work will be boosted then. and if it hasn&rsquo;t been done yet, then do it yourself.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/pedantcoder\/status\/1230032438237118465\">Pierre Habouzit<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/pedantcoder\/status\/1230032438237118465\"><p>If you wait from the main thread, and that the thing you wait on was dispatched onto a serial queue <em>before<\/em> you wait, then [dispatch_block_wait()] does a good job.<\/p>\n<p>For other more complex cases, use locks the way I explained in the thread.<\/p><\/blockquote>","protected":false},"excerpt":{"rendered":"<p>Rony Fadel (tweet): We&rsquo;re tempted to think of labels as lightweight in terms of memory usage. In the end, they just display text. UILabels are actually stored as bitmaps, which could easily consume megabytes of memory. [&#8230;] When you dispatch_async a block onto a concurrent queue, GCD will attempt to find an idle thread in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2020-02-18T20:40:19Z","apple_news_api_id":"3c3787b9-7847-4837-a103-77972496cb9a","apple_news_api_modified_at":"2020-02-18T20:40:19Z","apple_news_api_revision":"AAAAAAAAAAD\/\/\/\/\/\/\/\/\/\/w==","apple_news_api_share_url":"https:\/\/apple.news\/APDeHuXhHSDehA3eXJJbLmg","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,800,880,31,1667,138,71,1473],"class_list":["post-28176","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-concurrency","tag-grand-central-dispatch-gcd","tag-ios","tag-ios-13","tag-optimization","tag-programming","tag-xpc"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/28176","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=28176"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/28176\/revisions"}],"predecessor-version":[{"id":28225,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/28176\/revisions\/28225"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=28176"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=28176"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=28176"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}