Tuesday, February 18, 2020

iOS Optimization Tips

Rony Fadel (tweet):

We’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.


When you dispatch_async 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’t find an idle thread, it will have to create a new thread for the work item. Quickly dispatching blocks to a concurrent queue could leads to quickly creating new threads.


The concurrent queues you get from dispatch_get_global_queue are bad at forwarding QoS information to the system and should be avoided.


Avoid using dispatch_semaphore_t to wait for asynchronous work


UIKit implements tags using objc_get/setAssociatedObject(), meaning that every time you set or get a tag, you’re doing a dictionary lookup, which will show up in Instruments[…]

Jared Sorge:

I don’t know how many times I have kept text in a label when I perhaps didn’t need to. I also didn’t know that the right place to nil-out text in reusable views (UI{Table|Collection}ViewCell) is not in their prepareForReuse() method but in the delegate’s didEndDisplaying method instead.


Update (2020-02-24): Pierre Habouzit explains how to avoid semaphores (thread):

QoS is a label, its rules of propagation are semi complex, but DO NOT depend on the state of the system.

It’s propagated by only 2 mechanisms (and anything built atop of it), and one secondary obsolete subsystem.


So what you need for priority inversion to kick in, is a wait primitive that has ownership information, IO primitives that record ownership

The list is pretty short:

- pthread mutexes and os unfair locks (and things built on top)

- dispatch_sync() (but for reasons not onto the main queue, but that doesn’t matter for apps)

- xpc_connection_send_with_message_sync()


dispatch_block_wait() isn’t multi-hop. It works for the main thread to wait in certain circumstances (if what you wait on has been asynced before you start to wait on is an important one), because in an app it’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’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’t been done yet, then do it yourself.

Pierre Habouzit:

If you wait from the main thread, and that the thing you wait on was dispatched onto a serial queue before you wait, then [dispatch_block_wait()] does a good job.

For other more complex cases, use locks the way I explained in the thread.

2 Comments RSS · Twitter

Maarut Chandegra

I saw this article yesterday and this particular point jumped out at me (I had just finished a piece of work that does exactly this) -

"Avoid using dispatch_semaphore_t to wait for asynchronous work"

I spent the afternoon researching how I could avoid using the structure to wait for async work, but couldn't come up with a practical and pragmatic solution. Is there something that I'm missing?

@Maarut I haven’t seen a good way to do that if a synchronous API doesn’t already exist. Some have suggested dispatch_group_t and DispatchWorkItem, but I’m not convinced they solve the problem better than semaphores.

Leave a Comment