iOS, Objective-C, Swift, Design and Whatever Comes in Mind

Advanced Debugging with Xcode and LLDB – Sharing my Notes

I watched the WWDC talk and I would like to share my notes with you. You can find the recording as well as the mentioned script over here.

  • Xcode debug tab can be opened when a breaktpoint is hit via settings > Running > Pauses > check “Show tab named: Debug”
  • po <variable> to get the value of a variable
  • expression <expression> (eg. expression didReachSelectedHeight = true) to execute code at the breaktpoint, useful to always take one branch of an if-else-statement
  • Breakpoints can execute a LLDB command and then continue running. When double-clicked, one can enter the command via “Add Action”. Enabling “Automatically continue after Evaluation” tells the debugger to execute the command and continue to run the program. Combined with the aforementioned point it is possible to (always) take one branch of an if-else-statement whilst debugging.
  • Shortcut the "change>compile>run" workflow by injecting the change directly through LLDB to your running code. Just add another breakpoint and add the code you just inserted to your source file here, too. expression <code you just updated> does the trick that helps to tighten the workflow.
  • Debug navigator > add symbol > "add symbolic breakpoint" to add breakpoints to framework's code. For example [UILabel setText:] to add a breakpoint that hits every time a labels text is changed. Resolving the methods name succeeded when a text like “in UIKitCore” appears beside your new breakpoint. When this breakpoint hits, one can
    • po $arg1 to print the receiver of the Objective-C message (the UILabel in this case).
    • po (SEL)$arg2 to print the selector that is sent to the object.
    • po $arg3 to print the first argument passed into the method.
  • In a breakpoint, another breakpoint can be set dynamically with breakpoint set --one-shot true -name [UILabel setText:] (one shot breakpoint: only exists until it is triggered and then removed). Useful to “enable” a breakpoint only after a given part of code is executed, for example when the text of a label is updated in the following code.
  • Change the instruction pointer while the execution is paused by grabbing the grab-handle left besides “Thread 1: breakpoint…” in the green bar.
  • thread jump --by 1 tells the debugger to jump one code line ahead when the breakpoint is hit. Useful to skip code or execute different code by adding another action to the breakpoint.
  • Adding a breakpoint to a variable declaration will pause execution every time the variable is accessed.
  • po <variable> uses the debugDescription method of an object, p <variable> is more verbose in some cases.
  • Watchpoints pause the execution every time the value of a variable is changed. They can be added by right-clicking on the variable in the left side of the debugger pane. They appear below regular breakpoints in Xcode's navigator.
  • expression -l objc -O -- [’self.view' recursiveDescription] will print the description of the view as well as all contained subviews. It is an internal Objective-C method and can not be called using Swift. “’“ around self.view will tell the debugger to evaluate this expression first.
  • Printing the content of memory addresses like po 0x67aef7891 will not work using swift. One must use expression -l objc -O -- po 0x67aef7891​. A handy way to create a shortcut is by typing command alias poc expression -l objc -O --. Now it is possible to type poc 0x67aef7891.
  • In Swift, you can also use po unsafeBitcast(0x2d34bd25, to: YourView.self) instead of using Objective-C evaluation. You can even chain expressions together, like po unsafeBitcast(0x2d34bd25, to: YourView.self).center.y = 300. When a UIView property is changed in this way, one need to tell CoreAnimation to update its frame buffer, as the execution is currently paused: expression CATransaction.flush()
  • A python script that changes properties of the view using the debugger is presented. To use commands provided by this script, open ~/.lldbinit in you home directory and add command script import ~/nudge.py. You can also define your previously created aliases there.
  • Using the nudge tool, one can live update positions of views by using the debugger.

"Advanced Debugging with Xcode and LLDB – Sharing my Notes".

Open Source: FeedbackController

The Taptic Engine was introduced in Apples iPhone 7. It not only made possible to replace the mechanical home button but it also introduced API for developers. Through this API it became possible to give the user a subtle mechanical feedback about what is happening on screen.

For example an app can combine user interactions (like tapping a button, fading a slider oder flipping a view) with a light shake of the phone.

Even more sophisticated feedback types are possible. A notification can be combined with the according haptic feedback and then the user can literally feel if a task was successful or not.

My new open source project makes the usage of the mentioned API easier. You can find the project over here at GitHub. As always, I would love to hear feedback from you 🤗.

This blogpost describes the usage of the haptic feedback API and explains the approach of my new project. If it makes live easier for you, feel free to use it. Otherwise it could provide a reference implementation for you.

Implementation

To integrate the haptic feedback, one must use UIFeedbackGenerator. This superclass is derived by three different subclasses: UIImpactFeedbackGenerator, UINotificationFeedbackGenerator and UISelectionFeedbackGenerator.

These three subclasses can be used for three different types of feedback:

  1. A single tap-like feedback provided by UIImpactFeedbackGenerator
  2. A more complex feedback to indicate that a task has completed (successfully / erroneous) or that a warning occurred by using UINotificationFeedbackGenerator
  3. A single tap that implies a change in the users selection, for example when a switch is used or an image snaps into place, provided by UISelectionFeedbackGenerator

In any case, the Taptic Engine must be transitioned into its active state, by calling prepare() on any of the mentioned subclasses. To align the UI interaction or even sounds with the haptic feedback, prepare should be called about one or two seconds in advance.

What FeedbackController does

As it turns out, the API is not as straight forward as it could be. For example, the type of the notification-feedback is set when the feedback should fire.

Then again, the type of an impact feedback must be set during the prepare state.

In addition to that, the API is only available on iPhone 7 or newer, running iOS 11 or higher. Prior to using the described APIs, one must perform availability checks. As a reference to the UIFeedbackGenerator must be held strongly and calls to prepare and perform can occur in many places of your app, those checks need to be implemented quite frequently.

That is where FeedbackController comes into play. It simplifies the calls, needed to perform haptic feedback. It eliminates the need for the developer to keep in mind where the types of feedback can be configured.

In addition to that, it comes with easy to use extensions of UIViewController. They make it possible to use feedback everywhere in the app by two simple method calls:

image showing the usage of FeedbackController

However, a call to prepareFeedback(for:) should be made as early as possible so that the Taptic Engine can be powered on. Please see the example project for further details.

How to use it

If you integrate FeedbackController using CocoaPods, you need to import FeedbackController first.

When you expect that a user interaction will take place in the next couple of seconds, call prepareFeedback(for:). In doing so, the Taptic Engine will be powered on and is ready for your feedback. The timing is not as critical as might have guessed and it turned out that preparing the Taptic Engine in viewDidAppear(_:) is sufficient.

However, the type of feedback is determined by the call to prepareFeedback(for:).

Secondly, a feedback needs to be performed. To do so, just call

  1. hapticFeedbackImpactOccured() for a impact feedback.
  2. hapticFeedbackNotificationOccured(with:) for a notification feedback, specifying the type of said notification.
  3. hapticFeedbackSelectionChanged() for a selection feedback.

When you are done with the feedback, call doneWithHapticFeedback() to allow the Taptic Engine going back to its idle state.

Installation

You can install it by using CocoaPods or just by copying the FeedbackController.swift file.
Again, here is the link to the project.

Please let me know what you think about it.

"Open Source: FeedbackController".

Memory Management of Value Types

Yesterday, I read a great article about value and reference types and how their performance behaves. I want to share my notes with you.



Stack Allocation

  • Fast and simple place to store data
  • Uses a stack pointer that points to the top of the stack
  • Each scope provides the amount of memory it needs to run before it gets executed, then the stack pointer is moved and the scope is executed. Now, the scope can add and remove data to the memory it got allocated.
  • When the scope is done, the stack pointer is decreased and its data deallocated
  • Very little costs as only an integer (the pointer) needs to be increased/decreased
  • Some forms of value types can be stored directly on the stack, as long as their sizes are known at compile time
  • They also must not be contained by a reference type or contain one, as it would require reference counting
  • Absence of reference counting overhead leads to an increased performance
  • When a value type is assigned, it gets copied (called Copy-On-Assignment). However, the copy is performed in a constant time.

Heap Allocations

  • Used for objects that are flexible in size and whose lifetime can be longer than the scopes
  • Pointers point to the object in memory
  • When a resource should be allocated, the memory management is asked to reserve a certain amount of memory for that resource
  • If the resource is not used anymore, its memory can be freed (managed for us by ARC)
  • Heap allocation is slower than stack allocation because the data structure is more complex (page tables, …) and it needs to be thread save (in comparison: every thread has its own stack)

Unfortunately, it is not as easy as ’value types go on the stack and reference typed to the heap’.

Value Types on the Heap

  • Every struct whose size is not known at compile time must be stored on the heap.
  • Heap allocation is necessary when the struct contains a reference type or is contained by one (or even recursively, meaning it stores a value type object that stores a reference type)
  • Common situation when for example storing @escaping closures or if the struct is stored by a class​ directly
  • Possible for the struct to perform worse than a class​ when it comes to copying
  • Value types, that store heap allocated class objects, will not be per-se stored on the heap but will gain a reference counting overhead
  • Stack allocated value types are copied in constant time, heap allocated value types are not and will take more time.
  • Copy-On-Write (COW) prevents it from being problematic and many Standard Libraries data structures make heavy use of it (e.g. Array, Dictionary and Set)
  • With COW, an assigned property will not be copied immediately but only if its content get mutated

Problems with Value Types that store Reference Types

  • Consider the following struct and class​ that contain many reference type objects:
  • The next snippet will create HugeClass and HugeStruct many times and check how long the creation takes
  • Result: the struct needs more time then class, because it uses copy-on-assignment, in comparison to the class which just increases a reference count
  • If executed not only 10_000_000 but 20_000_000 times, the struct needs more than twice the time than before, the time of class only merely increases
  • The more reference types a value type contains (more instances of EmptyClass in HugeStruct), the more reference counting overhead is added on copying it.

Takeaway

  • Improve the performance by replacing unnecessary references with proper static size value types
  • For example, this:
  • May be replaced by this:
  • Now, the struct is statically sized and the reference counting overhead is removed.
  • Also keep asking yourself if you really need a struct with its Copy-On-Write behavior or if a class would fit you better

"Memory Management of Value Types".