[iOS] Learning Journal (P.1 maybe)
In this post, I compile some interesting questions about iOS development. The answers are given by me with or without some reference of the original sites where I found them. Links to the references is listed inline.

Part 1. Textbook Knowledge
1. Point out the pros and cons of using Swift over Objective-C
- [Pros/Cons, depend on usage] Swift is a modern language with a strongly typed and static type system. Swift can be considered safer during development because it support
Optionaltype. See here for difference between “strongly typed vs weakly typed”, and here for “static type vs dynamic type”. The safety Swift creates places some restriction to what a developer can do (less flexible). - [Pros/Cons, depend on usage] Swift support the use of value type and reference type. An object of a value type is a block of contagious memory, where as an object of a reference type is the address, pointing to the location where the real value is stored. When we pass value type object around, the block of memory is copied, any changes is contained to that instance. When we pass reference type object around, we do not duplicate the value. We simply give out access to the same object we have. Any changes made by such reference will be reflected in other holders of the reference.
- [Pros/Cons, depend on usage] Swift supports generic types (reference here and here) which helps reduce code duplication (I like to use generic when parsing result from API calls — gist demo). However, using generic types has performance implication, use them with care. Some time ago,
lottie-iosmaintainer claimed that the use of generic and struct in Swift may have result in subpar performance in his project when he rewrote it from Objective-C to Swift (reference). - [Cons] Some Objective-C runtime feature (which, I think, is more permissive and powerful than Swift). (Did not know much about this, need more research)
- [Super pros!!!] Code indenting is sane and not much nested
[[[[[[...]]]]]
2. Memory Management in iOS environment
iOS manages memory by a system called reference counting. Whenever an object is referenced, the count is increased. When the count reaches 0, the memory occupied by it is immediately released. However, not all form of reference is the same (next sections).
2a. strong, weak, assign, and retain (Objective-C) (reference)
The main reason for memory leaks in iOS has to be retain cycle, where object A references object B, and object B references object A. When this happens, reference count of A and B is at least 1, thus none can be release. This happens by default with strong reference. Because reference counting only counts the number of strong references an object has. Weak references can be used to access the object, but not to keep it alive.
Using block carelessly may also leads to memory leaks. There could be a retain cycle or a block which is never nil out that keeping reference to our object. Thus we may need to make our block references to weakly to objects outside the block (typeof(self) __weak weakSelf = self for example).
retain is the deprecated old version of strong (never used it, not sure…)
assign is used for primitives and struct. I believe it is the default modifier for non-pointer type object. (never used it, not sure…)
2b. weak vs. unowned (Swift)
Both weak and unowned reference do not increase reference count of an object. Both can be used to avoid retain cycle.
unowned is generally less safe than weak. If an object under weak reference is deallocated, the weak reference become nil, and accessing it is similar to accessing an Optional object. On the other hand, unowned reference acts as a force unwrapped optional. Moreover, unowned reference is non-zeroing. This means when the object is deallocated, the unowned reference still points to its address. Thus, using unowned reference in some cases may lead to a dangling pointer. (reference this, and this).
If 2 references cannot exist without one another, it is fine to use unowned
// Safe, use [unowned self] to ease the syntax
class A {
var closure: () -> Void { [unowned self] in
...
}
}However, when holder of closure is something lives outside the scope of captured object, it is not safe to use unowned
// Unsafe!!! Use [weak self] instead
URLSession.dataTask(with: request, completion: { [unowned self] _, _, _ in
...
})3. atomic vs. nonatomic
atomic and nonatomic property in Objective-C is used to indicate if an object should be accessed in a thread safe way. atomic objects are locked for exclusive access before reading/writing at the cost of reduced performance. Only use atomic when you need it.
Note that atomic only assures the value of object is well-formed without regarding the order of reading/writing to it. To serialize read/write is the responsibility of developers.
4. Grand Central Dispatch
Most of concurrent operation in iOS can be accomplished by using GCD (Grand Central Dispatch). GCD is a high-level API to help developers cope with concurrency without explicitly using locks and/or semaphores.
GCD manages a shared thread pool to perform concurrent tasks.
Reference: Here, here, and some cute visualization.
4a. DispatchQueue: SerialQueue vs ConcurrentQueue
DispatchQueue is a task queue, which is an abstraction layer on top of GCD queue. Tasks are always executed in the order they’re added to the queue.
There are two types of DispatchQueue I have encountered: serial and concurrent.
SerialQueue — Tasks submitted to the SerialQueue will not overlap with each other. Next task will not begin execution until the first is done.
ConcurrentQueue — Tasks submitted to the ConcurrentQueue is executed as soon as there are resources available. Although tasks begin execution in order, it is not guaranteed that first task will complete before second task, even with the same execution time when run in isolation (I don’t know where I got this idea but it seems natural. Concurrency is usually nondeterministic 😃).
4b. Dispatch synchronously and asynchronously
Tasks can be submitted to any DispatchQueue in synchronous or asynchronous fashion.
dispatch_sync — Current task is halted until the task submitted to queue is done. For example:
- We are on
Thread Tand dispatch_syncTask ton toQueue Q Thread Twill be halted. It waits forQueue Qto process all tasks beforeTask t,- Now
Task texecutes,Thread Twaits for it to finish executing. - Finally,
Thread Tmoves to the next instruction in its previous context.
// Thread T
print("A")
serialQueue.sync { // Task t
print("B")
}
print("C")The above snippet creates deterministic result: A printed → B printed → C printed.
Use dispatch_sync when it is okay to wait until some long lasting task is done (when the current queue is not the main queue) and it is necessary to wait for a task to be done on another queue (for example updating some UI element on main queue while on background queue, and wait for the update to finish and get some view info).
Calling dispatch_sync on the same queue as the caller will result in a deadlock.
dispatch_async- Submit task to suitable queue then resume execution of current task. Use dispatch_async when we don’t need to wait for the result of the task or waiting on current task is inappropriate.
4c. DispatchGroup, DispatchBarrier
There are times when we need to combine serial and concurrent execution, using SerialQueue or DispatchQueue alone is not enough. For example, we are executing on one of the background threads. We want to use one API to get a list of item IDs, then for each item, we need to call separate a API to get the item details. When all the details are loaded, we will push all of them onto main queue to update and render the UI.
Because all of the API calls on each item ID is IO-bound (read more here), all our CPUs are idle. Thus, we can establish more connections as soon as possible instead of waiting for first item detail to load, and then the second, etc. Using ConcurrentQueue solve the first part, parallelize the connection. But it is not enough, because we want to wait for all of our get item detail APIs to be done, then update UI. We cannot batch the result when using concurrent queue. We can use DistpatchGroup or barrier to wait for all items to be loaded before updating UI.
DispatchGroup
In this scenario, we can use DispatchGroup to help us achieve desired behavior. DispatchGroup allows aggregation of a set of tasks (get items detail API calls) and synchronize behavior of a group as if it is one task (treat all get items detail APIs as one task, then serially dispatch to main queue and update UI).
DispatchBarrier
Another approach is to use DispatchBarrier. When a DispatchBarrier task is at front of a queue, all subsequent tasks won’t be executed until the barrier block is finished. Furthermore, the barrier task won’t be executed until all tasks submitted to the queue before the barrier task finishes execution.
5. UIViewController life-cycle (main reference)
UIViewController is at the core of constructing iOS applications. They are responsible for hosting the user interface of the application regardless of which method you choose to create them (storyboards, xibs, or programmatically). We need good understanding of UIViewController life-cycle in order to construct our application effectively. (reference)
The basic life-cycle methods of UIViewController which I’ve been using a lot are:
loadView: This method is used to supply a customUIViewforUIViewController.viewproperty. If this method is override,super.loadView()should not be called. By default, the view created withUIViewController.loadView()hasautoresizingMaskof[.flexibleWidth, .flexibleHeight]. These masks can be helpful when we allow ourUIViewControllerto rotate, so unless you know what you’re doing, it is a good idea to set the custom view you created to these masks.viewDidLoad: This method is called afterloadView. In general, I tend to do setup of the view controller’s view here (setting background color, add sub views, add constraints, etc.)viewWillAppear: Indicates that view controller’s view is about to appear on screen. I believe that in this method, theviewControlleris not ready to receive user interaction. In this method, we can subscribe to observers, fetch API, etc. so that when the view finally appear, it is (hopefully) filled with data already. #todo: when in a real application when this method is called.viewDidAppear: Called when a view finally appear completely and is ready to receive user interaction. When we pop aUIViewControllerusing interactive gesture, this method is called when user cancel interactive pop gesture.viewWillLayoutSubviews: Whenever the view bounds changes (orsetNeedsLayout) on the view is called, the view go through a layout pass. This method is called to do manual frame-based layout. In case theUIViewControlleris appearing for the first time, this method is the first method where its view has meaningful bounds.viewDidLayoutSubviews: This method is called after the layout is done. In this method, all views laid out by autolayout is done. If we need to layout frame-based views in mix with and dependent on auto-laid-out view, we can use this method. If we do not interfere with the layout process, the child views of viewController’s view will trigger theirlayoutSubviewafterviewDidLayoutSubviewreturns.viewWillDisappear: Called when the view is about to disappear. There are two cases of disappearing, either when it is blocked by other view (e.g presenting or pushing anotherUIViewController) or when it is about to be get rid (e.g dismissing or popping). When we use interactive gesture to popUIViewController, the moment interactive gesture starts,viewWillDisappearis called on currentUIViewController.viewDidDisappear: Called when the view is completely disappeared. This means anotherUIViewControllerfinishes being presented or pushed over thisUIViewController, or thisUIViewControlleris completely popped or dismissed).viewWillTransition(to:coordinator): This method is called when the view controller is rotated.
5a. Child ViewControllers
Child viewControllers is a great way to compose UI to support reusable UIViewController or simply managing complexity of current screen. Apple’s official documentation goes in-depth about creating container view controllers.
5b. Manual life-cycle for child UIViewController
Adding child viewControllers is convenient, however, we cannot add child viewControllers to UITabBarController the way we do with other viewController. This is because UITabBarController uses its childViewControllers property to manage the tab. As a result, when we add a child viewController to UITabBarController, the behavior is not what we expected.
To use another UIViewController inside UITabBarController, we will add subView only, then manually manage its life-cycle.
Apple’s documentation explicitly warned us not to call viewWillAppear/viewWillDisappear/viewDidAppear/viewDidDisappear directly. We must instead use beginAppearanceTransition and endAppearanceTransition. The documentation did not state why, but I’ve figured one case which this does not work. It is when we call viewWillAppear/viewWillDisappear/viewDidAppear/viewDidDisappear on a viewController, the life-cycle method is NOT passed to that child viewController’s children viewControllers (if any).
5c. YouTube-liked video player ViewController
YouTube application has a viewController which homes the video player (let’s call it PlayerViewController). Users can use their finger to drag the view down. In its collapsed state, it docks on top of the tabBar. Let’s say we want to create a similar feature in our applications, we can follow these steps:
- When the
PlayerViewControllerbegins to move, we will callbeginAppearanceTransition(false)onUITabBarControllerandbeginAppearanceTransition(true)onPlayerViewController(if we regard collapsed state as disappeared. - When the user releases his/her finger. a) If the player successfully expanded — we call
endAppearanceTransitiononUITabBarController. If we callbeginTransitionAppearanceonPlayerViewController, be sure to callendAppearanceTransition. Be careful inUITabBarControllerdisappear methods, we may wanna check if it disappears due to some otherUIViewControllerwas pushed on top of tabBar or due toPlayerViewControllerfully expanded. b) If the player fails to expand, we callbeginTransitionAppearance(true)onUITabBarControllerfirst, thenendAppearanceTransition()on it. The opposite must be done onPlayerViewControllerif needed. Note that no matter how many time we callbeginTransitionAppearancemethod, we callendTransitionAppearancemethod exactly once after everything is done. - When collapsing, we repeat a similar process in the opposite direction.
Part 2. Problems I never thought would occur
1. Decodable unexpected behavior
Decodable protocol is very helpful in decoding JSON response from backend servers. However, the default behavior is unexpected in a few cases. I figured a way to overcome some of the problem I found along the way. This topic is already covered here by me.
2. Animating corner radius
Let’s say we have a button with corner radius equals half its height (which creates rounded sides). Now we want to shrink it (setting frame’s width and height to zero), keeping the corner nicely rounded the whole way. My naive approach was to simply do this:
UIView.animate(withDuration: 0.3) {
self.myView.bounds = CGRect(origin: .zero, size: .zero)
self.layer.cornerRadius = 0
}However… the button’s corner radius snapped to 0 immediately, creating a perfect square button, instead of a nice rounded button, then slowly shrink. What we have now is the clip on the left, and what we want to achieve is the effect on the right clip.

What is the problem here? It turns out the UIView.animate does not work quite well with direct manipulation of backing CALayer… (I also want to point out that on some OS version and/or devices, this works as expected, but not on others).
Although under the hood, animations in the block submitted to UIView.animate are converted to a series of CAAnimations, sublayer animations are not well treated. I am aware of 2 ways to solve this problem. One using CATransaction and one using action(for:forKey:) (Apple’s doc). Both approach involves creating an instance of CAAnimation object. However, only the latter works for me.
Using CATransaction to animate layer and views together
(I did not succeed using this approach… But many sources points to this approach so I only mention it shortly and add some links.)
As per Apple doc, to animate view and layers together, we can put the CAAnimation into the animation block. But it did not work for me (on simulator running iOS 10.3.1).
This post talks about animating corner radius together with size change, I tried following the exact same code. It did not work as well (on simulator running iOS 10.3.1).
Using action(for:forKey:)
I find that creating a bunch of CAAnimation code whenever I need to animate corner radius is time wasting (It violates DRY a bit. And the above solution did not work for me also… but I did not figure out why…).
Luckily, there is another cleaner way to achieve desired behavior, which requires subclassing. As we know,UIView is an abstraction over core animation classes. Each view has a backing CALayer and it is also the layer’s delegate.
When an animation is ran on a layer, action(for:forKey) is called on its delegate (which is our view). We create an object conforming CAAction protocol and returns our own implementation.
Even though in UIView.animate block, we set frame for the view, core animation translates that into bounds center/origin and size change. This is because there is no frame property in CALayer classes. When we set or get frame from a CALayer, it triggers corresponding accessor, then frame is translated to or calculated from CALayer’s bounds and center/origin.
One more thing, because we actively look for bounds.size animation in CornerRadiusAction, so it is required that we put set frame before set cornerRadius.
To be continued? Maybe…
Happy coding.





