Five traps for a beginner swifter

Hello! I am a beginner swifter, that is, I learn Swift without ObjC experience. Recently, my companions and I started a project that required an application for iOS. We also have an idée fixe: a student from Fiztekh should certainly work with us, and the application should be written in Swift. And so, while we are looking for physicists and getting to know them, I decided not to waste time and simultaneously start sawing a project on Swift on my own. So I first opened Xcode.

Suddenly a lot of acquaintances were discovered who, likewise having no experience in mobile development, began to master it precisely through Swift, and not ObjC. Some of them pushed me to share experience on Habré.

So, here are the top five "traps", a timely understanding of which would definitely save me time.

1. Blocks (short circuits) may cause memory leaks


If you, like me, came to mobile development bypassing ObjC, then probably Apple’s Automatic Reference Counting documentation would be one of the most important input materials . The fact is that with a “speedy” learning a new language by immersion (that is, by starting to cut a real project right away), you may develop a tendency to skip a “theory” that is not related to tasks such as “show a pop-up window here and now”. However, the ARC manual contains a very important section that specifically explains the non-obvious closure property that causes leaks.

So, an example of a "trap." A simple controller that will never be cleared from memory:

class ViewController: UIViewController {
    var theString = "Hello World"
    var whatToDo: (()->Void)!
    override func viewDidLoad() {
        whatToDo = { println(self.theString) }
    }
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        whatToDo()
        navigationController!.setViewControllers([], animated: true)
    }  
    deinit { println("removed from memory") }
}

Launch and with a finger on the screen. If we have little experience, then we mistakenly expect to see in the console:

Hello World
removed from memory

But actually we see:

Hello World

That is, we lost the ability to contact our controller, and he remained hanging in memory.

Why so? It turns out that calling self in this innocent line
{ println(self.theString) }

automatically creates a strict controller link from whatToDo loopback. Since the controller itself strictly refers to whatToDo, as a result we get two objects in memory that strictly refer to each other - and they will never be cleared.

If the self call is NOT used inside the closure, then this trick does NOT occur.

Swift, of course, provides a solution that for some reason Apple calls elegant. Here it is:

whatToDo = { [unowned self] in println(self.theString) }

Et voila! Conclusion: be careful with the life cycle of all closures that contain the self call.

2. Array, Dictionary, and Struct are by default non-mutable types that are never passed by reference


When the task is to learn a new language very quickly, I am inclined to hammer on reading docks on such intuitively obvious types as arrays and dictionaries, relying on the fact that autocomplete will teach me everything that is needed directly in the coding process. Such a hasty approach nevertheless failed me in a key place, when I perceived “arrays of arrays” and “arrays of strikes” all the way as sets of links (by analogy with JS) - they turned out to be sets of copies .

After reading the docks, I nevertheless got a glimpse: in Swift, arrays and dictionaries are strats, and therefore, like any strata, they are transmitted not by reference, but by value (by copying, which the compiler optimizes under the hood).

An example illustrating the mega-catch that Swift prepared for you:

struct Person : Printable {
    var name:String
    var age:Int
    var description:String { return name + " (\(age))" }
}
class ViewController: UIViewController {
    var teamLeader:Person!
    var programmers:[Person] = []
    func addJoeyTo(var persons:[Person]) {
        persons.append(Person(name: "Joey", age: 25))
    }
    override func viewDidLoad() {
        teamLeader = Person(name: "Peter", age: 30)
        programmers.append(teamLeader)
        // Строим ошибочные ожидания...
        teamLeader.name = "Peter the Leader"
        addJoeyTo(programmers)
        // ...и вот он, момент истины
        println(programmers)
    }
}

At startup, if we mistakenly think in the key "transfer by reference", we expect to see in the console:

[Peter the Leader (30), Joey (25)] // Результат 1

Instead, we see:

[Peter (30)] // Результат 2

Be careful! How to get out of the situation if we really need exactly the first result? In fact, each case requires an individual solution. In this example, the option of replacing struct with class and replacing [Person] with NSMutableArray will work.

3. Singleton Instance - choose the best "hack"


The catch is that currently classes in Swift cannot have static stored properties , but only static methods (class func) or static computed properties (class var x: Int {return 0}).

image

At the same time, Apple itself has no prejudice against global instances in the spirit of the Singleton pattern - we regularly verify this using pearls such as NSUserDefaults.standardUserDefaults (), NSFileManager.defaultManager (), NSNotificationCenter.defaultCenter (), UIApplication.shared , and so on. We really get static variables in the next general update - Swift 1.2.

So how do we create our own such instances in the current version of Swift? There are several possible “hacks” under the general name Nested Structbut the most concise of them is the following:

extension MyManager {
    class var instance: MyManager {
        func instantiate() -> MyManager {
            return ... // постройте свой инстанс здесь
        }
        struct Static {
            static let instance = instantiate() // lazily loaded + thread-safe!
        }
        return Static.instance
    }
}

Swift lines not only support static stored properties, but also by default give them deferred thread-oriented initialization. This is a profit! Without knowing this in advance, you can waste time writing and debugging excess code.

Attention! In the next version of swift (1.2) this "hack" is no longer needed, but the date of the general release is not known. (A beta version is already available for testing, but this also requires a beta version of XCode6.3, the build of which Appstore will not accept from you. In short, we are waiting for a global release.)

4. The didSet and willSet methods will not be called during constructor execution


It seems like a trifle, but it can lead you into a total stupor when debugging bugs if you do not know this. Therefore, if you have planned some kind of manipulation set inside didSet, which is important both during initialization and further during the life cycle of an object, you need to do it this way:

class MyClass {
    var theProperty:OtherClass! {
        didSet {
            doLotsOfStuff()
        }
    }
    private func doLotsOfStuff () {
        // здесь реагируем на didSet theProperty
    }
    ...
    init(theProperty:OtherClass)
    {
        self.theProperty = theProperty
        doLotsOfStuff()
    }
}

5. You can’t just take and update the UI when the response from the server


Programmers with ObjC experience can laugh at this “trap” because it must be well known: UI-related methods can only be pulled from the main thread. Otherwise, unpredictability and bugs pushing into a total stupor. But for some reason, this instruction passed me by, until I finally encountered terrible bugs.

An example of a "problem" code:

func fetchFromServer() {
    let url = NSURL(string:urlString)!
    NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { data, response, error in
        if (error != nil) {
            ...
        } else {
            self.onSuccess(data)
        }
    })!.resume()
}
func onSuccess(data) {
    updateUI()
}

Pay attention to the completionHandler block - all this will be executed outside the main thread ! For those who have not yet encountered the consequences, I advise you not to experiment, but just remember to arrange updateUI as follows:

func onSuccess(data) {
    dispatch_sync(dispatch_get_main_queue(), {
        updateUI()
    })
}

This is a typical solution. In one line, we return updateUI back to the main thread and avoid surprises.

That's all for today. All newcomers success!

Experienced Habrovites from mobile - your comments will be very useful to me and to all novice swifters.

Also popular now: