Hello! I am a beginner sweater, that is, I learn Swift without ObjC experience. Recently, we started a project with companions requiring an iOS application. We also have an idée fixe: a student from the Physics and Technology Institute should work with us, and the application should be written in Swift. And so, while we are looking for physical students and getting acquainted with them, I decided not to waste time and at the same time begin to saw the project on Swift on my own. So I first opened Xcode.
Suddenly, there were many acquaintances who, having no mobile development experience in the same way, began to master it precisely through Swift, and not ObjC. One of them pushed me to share experiences on Habré.
So, here are the top five “traps”, the timely understanding of which would definitely save me time.
1. Blocks (closures) can cause memory leaks
If you, like me, came to mobile development bypassing ObjC, then I would probably call Apple’s
Automatic Reference Counting documentation one of the most important introductory materials. The fact is that when “speeding” learning a new language by immersing (that is, by starting to immediately cut a real project) you may develop a tendency to skip “theory” that is not related to tasks like “show pop-up window here and now”. However, the ARC manual contains a very important section that specifically explains the
non-obvious property of closures that generates leaks.
')
So an example of a “trap.” A simple controller that never clears 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") } }
Run and poke your finger on the screen. If we have little experience, we mistakenly expect to see in the console:
Hello World removed from memory
But actually we see:
Hello World
That is, we have lost the opportunity to contact our controller, but he remained in memory.
Why so? It turns out that the
self call is in this innocent line.
{ println(self.theString) }
automatically creates a
strict reference to the controller from the whatToDo circuit. Since whatToDo is already strictly referenced by the controller itself, as a result we get two objects in memory that strictly refer to each other - and they will never be cleaned.
If the self call is NOT used inside the closure, then this trick DOES NOT occur.In the swift, of course, a solution is provided, which 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 containing call self.
2. Array, Dictionary and Struct defaults to unbutable types never passed by reference.
When the task is to master a new language very quickly, I tend to score on reading docks in 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. This hastily approach still failed me in a key place, when all the way I took “arrays of arrays” and “arrays of plants” as sets of links (by analogy with JS) - they turned out to be sets of
copies .
After reading the docks, I nevertheless saw the light:
in Swift, arrays and dictionaries are campaigns and therefore, like any strategies, are transmitted not by reference, but by value (by copying, which the compiler optimizes under the hood).
An example illustrating the mega trick Swift prepared 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)
At launch, if we mistakenly think in the key “pass by reference”, we expect to see in the console:
[Peter the Leader (30), Joey (25)]
Instead, we see:
[Peter (30)]
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 a struct with a class and replacing [Person] with an NSMutableArray will work.
3. Singleton Instance - choose the best "hack"
The trap is that currently
classes in Swift cannot have static stored properties , but only static methods (class func) or static calculated properties (class x: Int {return 0}).

At the same time, Apple itself has no prejudices against global instances in the spirit of the Singleton pattern - we regularly see this using pearls such as NSUserDefaults.standardUserDefaults (), NSFileManager.defaultManager (), NSNotificationCenter.defaultCenter (), UIApplication.anfax.anfedManager (), NSNotificationCenter.defaultCenter (), UIApplication.anfedManager (), NSNotificationCenter. and so on. We will actually get static variables in the next general update - Swift 1.2.
So how do we create our own same instances in the current version of Swift? There are several possible “hacks” under the general name
Nested Struct , but the most concise of them is the following:
extension MyManager { class var instance: MyManager { func instantiate() -> MyManager { return ...
Swift contracts not only support static stored properties, but also by default give them a deferred thread-oriented initialization. Here is a profit! Not knowing this in advance, you can waste your time writing and debugging extra 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, a build from 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 a trifle, but it can put you in total stupor when debugging bugs, if you do not know this. Therefore, if you have planned some set of manipulations inside didSet, which is important both during initialization and later during the object's life cycle, this should be done in the following way:
class MyClass { var theProperty:OtherClass! { didSet { doLotsOfStuff() } } private func doLotsOfStuff () {
5. It is impossible to just take and update the UI, when the answer came from the server
Programmers with ObjC experience can laugh at this “trap” because it must be well known: the methods associated with the UI are safe to pull only from the main thread. Otherwise - unpredictability and bugs, pushing into a total stupor. But for some reason this instruction passed by me, until I finally encountered terrible bugs.
Example of "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 ! To those who have not yet encountered the consequences, I advise you not to experiment, but simply remember to arrange updateUI as follows:
func onSuccess(data) { dispatch_sync(dispatch_get_main_queue(), { updateUI() }) }
This is a typical solution. With one line, we return updateUI back to the main thread and avoid surprises.
That's all for today. All newcomers success!
Experienced habrovchane from mobile - your comments will be very useful to me and to all novice SWIFTERs.