Debugging
Debugging is an everyday task for a developer that is usually done to fix a bug. There is no right or wrong way for debugging, and there is no formula to find where the issue is. On the other hand, we have some tricks that can help us find where the bug is. In this section, you are going to see some of them. They can be very useful if you would like to modify any app in this book and you don't get the expected result.
First of all, let's see a common problem. Swift was created to be compatible with Objective-C and Objective-C in its early days was created to be some kind of Smalltalk over the C layer. The idea at that time was to create a language where containers and functions didn't need to be of only a specific type like an array of integers, even the value returned from a function could be of any type. You can see, for example, NSArray
, NSDictionary
, and NSSet
that can store NSString
, NSNumber
, and the objects of any other class in the same container.
How Swift receives this kind of information? One way is by using generics but, usually, they are received as AnyObject
. Sometimes, the debugger is not able to show you the right variable type. For a better example, have a look at the following code:
var request = NSURLRequest(URL: NSURL(string: "http://date.jsontest.com/")!)
NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil)
// json Type???
}.resume()
In this case, you can ask yourself what is the JSON variable type? Of course, the first step is to check the debugger information, but you might get some information like the following one:
You can check with the is
operator like if json is Int
, but it can be very exhausting and, sometimes, even impossible. For scenarios like this, you can use the _stdlib_getDemangledTypeName
function, which displays the variable type name. In this case, we can add the following println
instruction to our code, as it is shown in the next code:
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) println("json variable type: \( _stdlib_getDemangledTypeName(json!) )") }.resume()
Now, you can see on the log console that, in this case, it is a dictionary as follows:
Note
In Swift 2, many functions have changed their input or output header from AnyObject
to a specific type, making life easier for developers.
Another important thing to bear in mind is lldb, which is the debugger command line. It can look complex and not much intuitive, but when you get used to it, it is very useful and logical. For example, you can set breakpoints using a pattern like br s -r viewDid*
to add breakpoints in functions like viewDidLoad
and viewDidAppear
.
Of course, there are tasks that you can do with lldb and also with Xcode like using the lldb command po [[UIWindow keyWindow] recursiveDescription]
, which gives you the window view hierarchy, or simply using the new Debug View Hierarchy button (displayed next) that comes since Xcode 6, which gives you 3D information on the current view.
Once it is pressed, you can rotate the image and have an output similar to the following screenshot:
A very common problem in our apps is the excess of memory usage. When we start receiving memory warnings, we need to check the amount of memory that is being increased in some parts of our code. To do it, just keep a function to retrieve the memory whenever you want. The following code is valid to retrieve the amount of memory that is being consumed:
func getMemoryUsage() -> mach_vm_size_t{ let MACH_TASK_BASIC_INFO_COUNT = sizeof(mach_task_basic_info_data_t) / sizeof(natural_t) let flavor = task_flavor_t(MACH_TASK_BASIC_INFO) var size = mach_msg_type_number_t(MACH_TASK_BASIC_INFO_COUNT) var pointer = UnsafeMutablePointer<mach_task_basic_info>.alloc(1) let kerr = task_info(mach_task_self_, flavor, UnsafeMutablePointer(pointer), &size) let info = pointer.move() pointer.dealloc(1) if kerr == KERN_SUCCESS { return info.resident_size } else { let message = String(CString: mach_error_string(kerr), encoding: NSASCIIStringEncoding)! fatalError(message) } }
Then, call this function in some parts of your code where you think there is memory wastage and print it like the following code:
println("File: \(__FILE__) at line \(__LINE__) memory usage \(getMemoryUsage())")
Note
There are third-party products that can also help you to debug your app like Fabric (http://www.fabric.io) that sends reports on user crashes to the developer.