This post is a free translation of the article Swift vs. Kotlin - the differences that matter by Krzysztof Turek
You have probably seen this comparison between Swift and Kotlin: http://nilhcem.com/swift-is-like-kotlin/ . Pretty interesting, right? I agree that there are many similarities in these languages, but in this article I will focus on some aspects that still separate them.
I have been developing Android since 2013 and most of the time I developed Java applications. Recently, I also had the opportunity to try iOS and Swift. I was impressed that Swift was able to write very cool code. If you make an effort, your code will be like a poem.
After seven months, I returned to Android. But instead of Java, I began to code on Kotlin. Google announced on Google IO 2017 that Kotlin is now the official language for Android. And I decided to teach him. It did not take me long to notice the similarities between Kotlin and Swift. But I would not say that they are very similar. Below I will show the differences between them. I will not describe everything, but only those that are interesting to me. Consider the examples.
Structures and Data Classes are simplified versions of classes. They are similar in use, it looks like this
Kotlin:
data class Foo(var data: Int)
Swift:
struct Foo { var data: Int }
But the class is still a class. This type is passed by reference. But the structure - by value. "So what?" you ask. I will explain with an example.
Let's create our data class in Kotlin and the structure in Swift, and then compare the results.
Kotlin:
var foo1 = Foo(2) var foo2 = foo1 foo1.data = 4
Swift:
var foo1 = Foo(data: 2) var foo2 = foo1 foo1.data = 4
What is data for foo2 in both cases? Answer 4 for the Kotlin data class and 2 for the structure on Swift.
The results are different, because var foo2 = foo1 in Swift creates a copy of the structure instance (more detailed here ), and in Kotlin another link to the same object ( more detailed here )
If you're working with Java, you're probably familiar with the Defensive Copy pattern. If not - we catch up. Here you will find more information on the topic.
In general: there is the possibility of changing the state of an object from inside or outside. The first option is preferable and more common, but the second is not. Especially when you work with a reference type and do not expect changes in its state. This can complicate the search for bugs. To prevent this problem, you should create a protected copy of the mutable object before transferring it to somewhere. Kotlin is much more useful in such situations than Java, but through negligence problems can still arise. Consider a simple example:
data class Page(val title: String) class Book { val pages: MutableList<Page> = mutableListOf(Page(“Chapter 1”), Page(“Chapter 2”)) }
I declared pages as MutableList , because I want to change them inside this object (add, delete, etc.). Pages are not private , because I need access to their state from the outside. So far, everything is going fine.
val book = Book() print(“$book”) // Book(pages=[Page(title=Chapter 1), Page(title=Chapter 2)])
Now I have access to the current state of the book:
val bookPages = book.pages
I add a new page in bookpages :
bookPages.add(Page(“Chapter 3”))
Unfortunately, I also changed the state of the source book. And this is not what I wanted.
print(“$book”) // Book(pages=[Page(title=Chapter 1), Page(title=Chapter 2), Page(title=Chapter 3)])
We can use a protected copy to avoid this. It is very easy in Kotlin.
book.pages.toMutableList()
Now we are fine. :)
And what about Swift? Everything works out of the box. Yes, arrays are structures. Structures are passed by value, as we mentioned above, so when you write:
var bookPages = book.pages
You are working with a copy of the list of pages.
Thus, we are dealing with data transfer by value. This is very important for understanding differences, if you do not want to experience a headache during debugging. :) Many "objects" are structures in Swift, for example Int, CGPoint, Array, etc.
This is my favorite topic. : D
Let's start by comparing the interface and protocol. In principle, they are identical.
In addition, protocols may require a specific initializer (constructor in Kotlin).
Kotlin:
interface MyInterface { var myVariable: Int val myReadOnlyProperty: Int fun myMethod() fun myMethodWithBody() { // implementation goes here } }
Swift:
protocol MyProtocol { init(parameter: Int) var myVariable: Int { get set } var myReadOnlyProperty: Int { get } func myMethod() func myMethodWithBody() } extension MyProtocol { func myMethodWithBody() { // implementation goes here } }
* Please note that you cannot add the default implementation of the method directly inside the protocol. That's why I added a star to the last item in the list. You need to add an extension for this. And this is a good way to go to the more interesting part - extensions!
Extensions allow you to add functionality to existing classes (or structures;)) without inheriting them. It is so simple. Agree, this is a cool opportunity.
This is something new for Android developers, so we like to use it all the time! Create extensions in Kotlin - do not launch rockets into space.
You can create extensions for properties:
val Calendar.yearAhead: Calendar get() { this.add(Calendar.YEAR, 1) return this }
or for functions:
fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { return ContextCompat.getDrawable(this, drawableRes) ?: throw NullPointerException("Can not find drawable with id = $drawableRes") }
As you can see, we have not used any keywords here.
Kotlin has some predefined extensions that are quite steep, for example "orEmpty ()" for optional strings:
var maybeNullString: String = null titleView.setText(maybeNullString.orEmpty())
This useful extension looks like this:
public inline fun String?.orEmpty(): String = this ?: ""
'?:' tries to get a value from 'this' (which is the current value of our string). If there is a null, an empty string will be returned instead.
So, now let's look at the Swift extensions.
They have the same definition, so I will not repeat as a broken record.
If you search for an extension like "orEmpty ()", I have bad news for you. But you can add it, right? Let's try!
extension String? { func orEmpty() -> String { return self ?? "" } }
but here's what you see:
The optional in Swift is a generic enumeration, with a given type of Wrapped . In our case, Wrapped is a string, so the extension will look like this:
extension Optional where Wrapped == String { func orEmpty() -> String { switch self { case .some(let value): return value default: return "" } } }
and in
let page = Page(text: maybeNilString.orEmpty())
It looks harder than the Kotlin counterpart, right? And, unfortunately, there is also a disadvantage. As you know, the option in Swift is a generic enumeration, so your extension will be available for all optional types. It doesn't look very good:
However, the compiler will protect you and will not compile this code. But if you add more of these extensions, your self-help will be littered with garbage.
So Kotlin extensions are more convenient than Swift? I would say that Swift extensions are meant for other purposes;). Android developers, hold on!
Protocols and extensions are created to work together. You can create your own protocol and extension for the class to match this protocol. It sounds crazy, but that's not all! There is such a thing as conditional compliance with the protocol . This means that a class / structure may conform to the protocol when certain conditions are met.
Suppose we have a lot of places where you need to show a pop-up alert. We like the principle of DRY and we do not want copy-paste code. We can solve this problem using protocol and extension.
First, create a protocol:
protocol AlertPresentable { func presentAlert(message: String) }
Then, the extension with the default implementation:
extension AlertPresentable { func presentAlert(message: String) { let alert = UIAlertController(title: “Alert”, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: “OK”, style: .default, handler: nil)) } }
So, the presentAlert method only creates an alert, but does not show anything. We need a link to the view controller for this. Can we pass it as a parameter to this method? Not a good idea. Let's use the where condition!
extension AlertPresentable where Self: UIViewController { func presentAlert(message: String) { let alert = UIAlertController(title: “Alert”, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: “OK”, style: .default, handler: nil)) self.present(alert, animated: true, completion: nil) } }
What do we have here? We have added a specific requirement for expanding our protocol. It is intended only for UIViewController. Because of this, we can use the UIViewController methods in the presentAlert method. This allows us to display an alert on the screen.
Go ahead:
extension UIViewController: AlertPresentable {}
Now all UIViewController has a new feature:
Also, a combination of protocols and extensions is very useful for testing. Guys, how many times have you tried to test the Android final class in your application? This is not a problem for Swift.
Look at this situation and assume that we have a final class in Swift. If we know the method signature, we can create a protocol with the same method, and then add an extension that implements this protocol to our final class, and voila! Instead of using this class directly, we can use the protocol and easily test it. Sample code instead of a thousand words.
final class FrameworkMap { private init() { … } func drawSomething() { … } } class MyClass { … func drawSomethingOnMap(map: FrameworkMap) { map.drawSomething() } }
In the test, we need to check whether the drawSomething method is called on the map object when the drawSomethingOnMap method is tested. This can be difficult even with the Mockito (a well-known test library for Android). But with protocol and extension, it will look like this:
protocol Map { func drawSomething() } extension FrameworkMap: Map {}
And now your drawSomethingOnMap method uses a protocol instead of a class.
class MyClass { … func drawSomethingOnMap(map: Map) { map.drawSomething() } }
Finally, I would like to mention the listings.
There are no differences between Java transfers and Kotlin transfers, so there is nothing to add here. But we have something new in return, and these “super-listings” are sealed classes. Where does the term "super-listing" come from? Refer to the Kotlin documentation :
"... They are, in a sense, extensions for enum classes: the set of possible values ​​for enums is also limited, but each enum constant exists only in a single instance, while the heir to the sealed class can have many instances that can store state . "
Okay, cool, they can keep state, but how can we use it?
sealed class OrderStatus { object AwaitPayment : OrderStatus() object InProgress : OrderStatus() object Completed : OrderStatus() data class Canceled(val reason: String) : OrderStatus() }
This is a sealed class, which is an order status model. Very similar to the way we work with transfers, but with one reservation. The Canceled value contains the reason for the cancellation. Reasons for cancellation may be different.
val orderStatus = OrderStatus.Canceled(reason = "No longer in stock") … val orderStatus = OrderStatus.Canceled(reason = "Not paid")
We cannot do so with ordinary transfers. If the enumeration value is created, it will not be changed.
Did you notice other differences? I used another chip sealed-class. This is related data of different types. Classical enumeration involves the transfer of related data for all variants of enumeration values, and all values ​​must be of the same type.
Swift has the equivalent of a sealed class and it is called ... an enumeration. Enumeration in Kotlin is just a relic of Java, and you will use sealed classes for 90% of the time. It is difficult to distinguish the sealed class from the Swift enumeration. They differ only in name and, of course, the sealed class is passed by reference, and enumeration in Swift - by value. Please correct me if I'm wrong.
There are also nuances of the influence of memory management on the way code is written. I know that I didn’t cover all aspects, and that’s because I’m still learning. If you guys notice any other differences between these two languages ​​- let me know. I am always open to new things!
Source: https://habr.com/ru/post/350746/
All Articles