📜 ⬆️ ⬇️

From Objective-C to Swift. Recommendations

Swift is a new programming language from Apple, which it presented this year at WWDC. Together with the programming language, Apple has released an excellent guide to the Swift language, which I recommend reading or reading it. However, reading a book is a very long time! So if you do not have much time and you just want to learn about the new Swift language, then this article is for you.

In this article I would like to share some thoughts about the transition from Objective-C to Swift. I will try to give you some tips and point out the shortcomings with a different approach to both languages. Therefore, without further ado, let's move on to the article itself.

Single file vs. interface implementation file


The first, most significant change worth mentioning is the abandonment of the interface.h / implementation.m structure.

I must admit that I am a supporter of this model. Retrieving and sharing information about a class simply by using an interface file is safe and fast.
')
In Swift, the interface and implementation are not divided into two files. We simply implement our class (and at the time of writing it is not even possible to add visibility modifiers).

If it is really difficult to cope with this change, then you can use the following: common sense.

We can easily increase the readability of our classes by good documentation. For example, you can move items that you want to make “public” at the top of our file using extensions to delimit data that is accessible to everyone and personal information.
Another very common technique is to put an underscore "_" for private methods and variables.

Here is a small example of their mixing:

// Public extension DataReader { var data { } func readData(){ var data = _webserviceInteraction() } } // Private implementation class DataReader: NSObject { let _wsURL = NSURL(string: "http://theurl.com") func _webserviceInteraction()->String{ // ... } } 


Although we cannot change the visibility of the elements of our classes, we can try to make access to some of the “more difficult” ones.
A non-standard solution is to use a nested class that partially hides personal data (at least in auto-complete)

Example:

 import UIKit class DataReader: NSObject { // Public *********************** var data:String?{ get{return private.internalData} } init(){ private = DataReaderPrivate() } func publicFunction(){ private.privateFunc() } // Private ********************** var private:DataReaderPrivate class DataReaderPrivate { var internalData:String? init(){ internalData = "Private data!" } func privateFunc (){} } } 


We put the private implementation in a private persistent case and use the “regular” implementation in a class as a public interface. Private elements, in fact, are not hidden, but to access them, we must pass through "private" constants.

 let reader = DataReader() reader.private.privateFunc() 


The question arises: is it worth the ultimate goal, partial concealment of personal elements?
My suggestion is to wait for visibility modifiers (Apple is working on this), but for now use good documentation with or without extensions.

Constants and variables


In Objective-C, I rarely used constant keywords, even when I knew that some data would never change. In Swift, Apple developers suggest using a constant (let) instead of a variable (var). So just follow it and try to figure out the role of your variables. Ultimately, you will use more constants than you expect.

Write only what you need to write


Look at 2 lines of codes and find the difference:

 let wsURL:NSURL = NSURL(string:"http://wsurl.com"); vs let wsURL = NSURL(string:"http://wsurl.com") 


During the first two weeks of working with Swift, I forced myself to remove the semicolon from each row of code. It's easier now (and I already forgot what it is in Objective-C).

Type inference allows you to assign a type to a variable, deriving it directly from its definition. This is another benefit that is a bit difficult to master when it comes out using the detailed language Objective-C.

We should try to use the methods of sequential naming, otherwise it would be difficult for another developer (and for you) to determine the type derived from the unsuccessful choice of naming:

 let a = something() 


A better name makes it easier to work:

 let a = anInt() 


The following major change is the use of parentheses, which are no longer needed:

 if (a > b){} vs if a > b {} 


Remember, what you write in parentheses is evaluated as an expression and is not always allowed to record in this way. When linking variables, for example, we cannot use brackets:

 if (let x = data){} // Error! if let x = data {} // OK! 


You do not need to select an interface or delete a semicolon and parentheses, but you can consider these options as one of the ways to write code in Swift. Ultimately, we increase readability and save time on typing and characters.

Optional


When working with functions that return “value” (value) or “nothing” (nothing), have you ever wondered which is the best way to determine “nothing”? I used NSNotFound, -1, 0, custom return values.

Thanks to Optionals (Optional) we have “nothing-value”, which is completely defined, we just need to add a question mark after the data type.

We can write:

 class Person{ let name:String let car:Car? // Optional value init(name:String){ self.name = name } } // ACCESSING THE OPTIONAL VALUE *********** var Mark = Person(name:"mark") // use optional binding if let car = Mark.car { car.accelerate() } // unwrap the value Mark.car?.accelerate() 


In this example, the Man-owned vehicle relationship is defined as Optional. This means that the property "car" can be zero, and the person could not have a car.

Then we address this value using an additional binding (if you allow (let) a car =) or with the help of the expanded phrase (car?). If we do not define a property as optional, we must set a value for this property, otherwise the initialization function will make an error.
The last possibility is to determine a non-additional property value - within the initialization function.

Thus, we must determine how the properties of the class will interact with the rest of the class, and how they will behave during the existence of class instances.

These improvements completely change the way we present our classes.

Additional unpacking


If you find it difficult to work with options, because you cannot understand why the compiler will ask you to give a detailed value before using it ...

 Mark.car? 


... I propose to think of the option additionally as a structure (it is a structure, so it should not be too difficult), which does not contain the value directly, but adds a layer around it (envelops, fragments-wrap). If the internal value is determined, you delete the layer (expand-unwrap), and get the desired value, otherwise you get zero.

Forced unwrapping through the “!” Sign is simply a way to remove a layer without worrying about the inner size. You risk trying to access value by layer. If this value is zero, the application simply fails.

Delegation Template


After several years of programming on Objective-C and Cocoa, we are dependent on the delegate pattern. However, we still use this scheme. Here is a super simple example of using a delegate:

 @objc protocol DataReaderDelegate{ @optional func DataWillRead() func DataDidRead() } class DataReader: NSObject { var delegate:DataReaderDelegate? var data:NSData? func buildData(){ delegate?.DataWillRead?() // Optional method check data = _createData() delegate?.DataDidRead() // Required method check } } 


We replace the delegate existence check and use respondToSelector with an extra chain.

 delegate?.DataWillRead?() 


Notice that we have to add the protocol using the @obj keyword, because we used @optional. By the way, the compiler warns us with a message in case we forget to do it.

To implement this delegate, we implement the protocol in another class, and assign it, as well as in Objective-C:

 class ViewController: UIViewController, DataReaderDelegate { override func viewDidLoad() { super.viewDidLoad() let reader = DataReader() reader.delegate = self } func DataWillRead() {...} func DataDidRead() {...} } 


Programming Template - Target-action


Another common method that we use in Swift is the interactive element (target-action), and in this case we apply it as in Objective-C.

 class ViewController: UIViewController { @IBOutlet var button:UIButton override func viewDidLoad() { super.viewDidLoad() button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside) } func buttonPressed(sender:UIButton){...} } 


The small difference is in how we determine the address of a segment (selector). We simply write the prototype method using a string that will automatically be modified to:

 Selector("buttonPressed:") 


Singleton Programming Template


Love it or hate it, Singleton is still one of the most accepted programming models.

Whether you like it or not, the Singleton pattern is one of the most applicable patterns. We can implement it using GDC and dispatch_once or rely on the precision-safe nature of the let keyword.

 class DataReader: NSObject { class var sharedReader:DataReader { struct Static{ static let _instance = DataReader() } return Static._instance } ... } 


Let's look at this code:
1. SharedReader is a static component (we can replace it with a function).
2. Static (non-constituent) properties are not yet resolved in the class implementation. So, thanks to nested types, we add a nested structure to the class. The structure supports static properties, so we simply add a static property here.
3. The _instance property is a constant. It cannot be replaced with another value, and is thread safe.

We can access a single DataReader instance using:

DataReader.sharedReader

Structures and calculations


In Swift, structure and computation have many characteristics that you hardly apply in other languages.

They support:

 struct User{ // Struct properties let name:String let ID:Int // Method!!! func sayHello(){ println("I'm " + self.name + " my ID is: \(self.ID)") } } let pamela = User(name: "Pamela", ID: 123456) pamela.sayHello() 


As you can see, the structure uses an initialization function, which in this case is automatically created by Swift (we can add other input parameters for clients).

The enum syntax is slightly different from the one we used. It is defined by the case keyword:

 enum Fruit { case orange case apple } 


Enum is not limited to its properties:

 enum Fruit:String { case .orange = "Orange" case .apple = "Apple" } 


We can also build an enum with more complex characteristics:

 enum Fruit{ // Available Fruits case orange case apple // Nested type struct Vitamin{ var name:String } // Compound property var mainVitamin:Vitamin { switch self{ case .orange: return Vitamin(name: "C") case .apple: return Vitamin(name: "B") } } } let Apple = Fruit.apple var Vitamin = Apple.mainVitamin 


In the previous code, we added a nested type (vitamin) and an additional property (mainVitamin), which assigns the initial values ​​of the elements for this structure depending on the value of enum.

Changeable and unchangeable


With Objective-C, we are accustomed to unchanging and mutable versions of any class. Some examples are NSArray and NSDictionary.

With Swift, we don’t need different types of data, we just use a constant or variable value in a new way.

A variable array is mutable, while with an array constant we cannot change our saved values. So just keep in mind, the rule “let = immutable, var = variable” (Bug fixed: you can change an immutable array before Beta 3).

Blocks vs Closure


I like the syntax of blocks, it is so clear and easy to remember!

 </IronicMode> 


By the way, after several years of Cocoa development, we are used to this syntax, and sometimes I prefer to replace light delegation tasks with blocks. They are filled with meaning, quick and well applicable.

In Swift, similar block elements are closure elements. They have many properties, and Apple has done a great job trying to simplify the way they are written. An example on official Swift documentation deprives me of speech. It begins with the following definition:

 reversed = sort(names, { (s1: String, s2: String) -> Bool in return s1 > s2 }) 


and redesigned to:

 reversed = sort(names, >) 


Thus, there are various ways to implement closures due to type inference, abbreviations ($ 0, $ 1), and direct functions (>).

In this article, I do not describe the syntax of a closed expression, but I want to say a few words about the values ​​of data collection within a closed expression.

In Objective-C, we define a variable as __block when we intend to change its value through Block. The use of closures, in this case, becomes unnecessary.

We can access and modify any value of the surrounding area. In fact, closed expressions are reasonable enough to capture external elements. The item is entered as a copy or link. If the closing changes the value of the element, it creates a link, if not, it creates a copy.

If a closure refers to an entry that contains or uses it, we may encounter a circulation cycle.

Let's see an example:

 class Person{ var age:Int = 0 @lazy var agePotion: (Int) -> Void = { (agex:Int)->Void in self.age += agex } func modifyAge(agex:Int, modifier:(Int)->Void){ modifier(agex) } } var Mark:Person? = Person() Mark!.modifyAge(50, Mark!.agePotion) Mark = nil // Memory Leak 


The agePotion enclosed expression is used by keeping a reference to the current instance. At the same time, this instance contains a link to the closing - and here the circulation cycle occurs.

To avoid this problem, we use the Capture list. This list links the weak link to the instance that we want to use in the Close. The syntax is very simple - add a weak link before the definition of Close and the instance will get a weak link instead of a strong one.

 @lazy var agePotion: (Int) -> Void = { [unowned self](agex:Int)->Void in self.age += agex } 


Unowned and Weak Links


We already know how a weak link works in Objective-C. Similarly, it works in Swift, there are no changes.

I really appreciated the introduction of this keyword because it is a good hint for determining the relationship between classes.

We describe a simple relationship between a person and his bank account:

1. A person may have a bank account (optional)
2. The bank account must belong to the person (required)

 We can describe this relation with code: class Person{ let name:String let account:BankAccount! init(name:String){ self.name = name self.account = BankAccount(owner: self) } } class BankAccount{ let owner:Person init(owner:Person){ self.owner = owner } } 


These relationships are going to create a cycle. The first solution will be the addition of a weak link to the “Bank Account.owner” property. However, with the help of a weak link, we define another useful limitation: a property must always have a value, it cannot be zero (thus, we satisfy clause 2 from the previous list).

In fact, there is nothing more to say about a weak link. It works just like a weak reference without increasing the register to which it points and provides a nonzero value.

Conclusion


I have to admit: sometimes I still work on compiler errors. The more I use Swift, the clearer it becomes that I do not waste my time experimenting and studying. There are many interesting changes and things compared to Objective-C that did not exist before and that motivate me to practice more.

This is a welcome new development in iOS / OSX and I’m sure you will love it!

ps Translation does not claim to be the most correct and the best translation in Habré, if you have any comments, write in a personal, I will rule. Thank you for understanding.

Source: https://habr.com/ru/post/233607/


All Articles