スウィフトvs. コトリン。 違いは重要です







この投稿は、 Swift vs.の無料翻訳です。 Kotlin- Krzysztof Turekによる重要な違い







おそらく、SwiftとKotlinのこの比較を見ました: http : //nilhcem.com/swift-is-like-kotlin/ かなり面白いですね。 これらの言語には多くの類似点があることに同意しますが、この記事では、それにもかかわらずそれらを異なるものにするいくつかの側面に注意を払います。







2013年からAndroid開発に携わり、ほとんどの時間でJavaアプリケーションを開発しています。 最近、iOSとSwiftを試す機会を得ました。 Swiftで非常にクールなコードを書くことが判明したという事実に感銘を受けました。 努力すれば、コードは詩のように見えます。







7か月後、Androidに戻りました。 しかし、Javaの代わりに、Kotlinでコーディングを開始しました。 GoogleはGoogle IO 2017で、KotlinがAndroidの公式言語になったことを発表しました。 そして私は彼に教えることにしました。 KotlinとSwiftの類似点に気付くのにそれほど時間はかかりませんでした。 しかし、それらが非常に似ているとは言いません。 以下に、それらの違いを示します。 すべてを説明するのではなく、興味のあるものだけを説明します。 いくつかの例を見てみましょう。







構造対 データクラス。 値と参照



構造体とデータクラスは、クラスの簡易バージョンです。 使用方法は似ていますが、このように見えます







コトリン:







data class Foo(var data: Int)
      
      





スイフト:







 struct Foo { var data: Int }
      
      





しかし、クラスはまだクラスです。 この型は参照渡しされます。 しかし、構造-値によって。 「だから何?」 お願いします。 例で説明します。







Kotlinでデータクラスを作成し、Swiftで構造を作成して、結果を比較しましょう。







コトリン:







 var foo1 = Foo(2) var foo2 = foo1 foo1.data = 4
      
      





スイフト:







 var foo1 = Foo(data: 2) var foo2 = foo1 foo1.data = 4
      
      





両方のケースで等しいfoo2データは何ですか? Kotlinデータクラスの場合は4、Swift構造体の場合は2です。













Swiftのvar foo2 = foo1は構造インスタンスのコピーを作成し(詳細はこちら )、Kotlinでは同じオブジェクトへのもう1つのリンク(詳細はこちら )であるため、結果は異なります







Javaを使用している場合は、おそらく防御コピーパターンに精通しているでしょう。 そうでない場合、追いつきます。 ここで、トピックに関する詳細情報を見つけることができます。







一般的に、オブジェクトの状態を内側または外側から変更することが可能です。 最初のオプションが望ましい、より一般的ですが、2番目のオプションはそうではありません。 特に、参照型を使用していて、その状態の変化を期待しない場合。 これにより、バグの検索が複雑になる可能性があります。 この問題を防ぐには、他の場所に転送する前に、変更可能なオブジェクトの安全なコピーを作成する必要があります。 このような状況では、KotlinはJavaよりもはるかに便利ですが、不注意は依然として問題を引き起こす可能性があります。 簡単な例を考えてみましょう。







 data class Page(val title: String) class Book { val pages: MutableList<Page> = mutableListOf(Page(“Chapter 1”), Page(“Chapter 2”)) }
      
      





このオブジェクト内でページを変更(追加、削除など)したいので、 ページMutableListとして宣言しました。 外部からページの状態にアクセスする必要があるため、 ページは プライベートはありません。 これまでのところ、すべてが順調に進んでいます。







 val book = Book() print(“$book”) // Book(pages=[Page(title=Chapter 1), Page(title=Chapter 2)])
      
      





これで、本の現在の状態にアクセスできます。







 val bookPages = book.pages
      
      





bookPagesに新しいページを追加しています:







 bookPages.add(Page(“Chapter 3”))
      
      





残念ながら、ソースブックの状態も変更しました。 そして、これは私が望んでいたものではありません。







 print(“$book”) // Book(pages=[Page(title=Chapter 1), Page(title=Chapter 2), Page(title=Chapter 3)])
      
      





安全なコピーを使用してこれを回避できます。 Kotlinでは非常に簡単です。







 book.pages.toMutableList()
      
      





今ではすべてが順調です。 :)







しかし、Swiftはどうですか? すべてが箱から出して動作します。 はい、配列は構造体です。 前述のように、構造は値によって渡されるため、次のように記述します。







 var bookPages = book.pages
      
      





ページリストのコピーを使用しています。







したがって、値によるデータの送信を扱っています。 デバッグ中に頭痛を経験したくない場合、これは違いを理解するために非常に重要です。 :) Int、CGPoint、Arrayなど、多くの「オブジェクト」はSwiftの構造です。







インターフェイスとプロトコルおよび拡張機能



これは私のお気に入りのトピックです。 :D







インターフェースとプロトコルを比較することから始めましょう。 原則として、それらは同一です。









さらに、プロトコルには特定の初期化子(Kotlinのコンストラクター)が必要な場合があります。







コトリン:







 interface MyInterface { var myVariable: Int val myReadOnlyProperty: Int fun myMethod() fun myMethodWithBody() { // implementation goes here } }
      
      





スイフト:







 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 } }
      
      





*デフォルトメソッドの実装をプロトコル内に直接追加できないことに注意してください。 これが、リストの最後の項目にアスタリスクを追加した理由です。 これには拡張機能を追加する必要があります。 そして、これはより興味深い部分である拡張機能に進む良い方法です!







拡張機能を使用すると、既存のクラス(または構造;)に機能を追加できますが、継承することはできません。 とても簡単です。 同意します、これは素晴らしい機会です。







これはAndroid開発者にとっては新しいものなので、常に使用したいです! Kotlinで拡張機能を作成します-ロケットを宇宙に発射しないでください。







プロパティの拡張機能を作成できます。







 val Calendar.yearAhead: Calendar get() { this.add(Calendar.YEAR, 1) return this }
      
      





または機能用:







 fun Context.getDrawableCompat(@DrawableRes drawableRes: Int): Drawable { return ContextCompat.getDrawable(this, drawableRes) ?: throw NullPointerException("Can not find drawable with id = $drawableRes") }
      
      





ご覧のとおり、ここではキーワードを使用していません。







Kotlinには、オプションの文字列の「orEmpty()」など、かなりクールな定義済みの拡張機能がいくつかあります。







 var maybeNullString: String = null titleView.setText(maybeNullString.orEmpty())
      
      





この便利な拡張機能は次のようになります。







 public inline fun String?.orEmpty(): String = this ?: ""
      
      





'?:'は 'this'(現在の文字列の値)から値を取得しようとしています。 nullの場合、空の文字列が返されます。







それでは、Swiftの拡張機能を見てみましょう。







それらの定義は同じであるため、私はハッキングされたレコードとして自分自身を繰り返しません。







「orEmpty()」のような拡張機能を探している場合、悪いニュースがあります。 でも追加できますよね? やってみましょう!







 extension String? { func orEmpty() -> String { return self ?? "" } }
      
      





しかし、これはあなたが見るものです:



















Swiftのオプションは、指定されたWrappedタイプの一般的な列挙です。 この場合、 Wrappedは文字列であるため、拡張子は次のようになります。







 extension Optional where Wrapped == String { func orEmpty() -> String { switch self { case .some(let value): return value default: return "" } } }
      
      





そしてビジネスで:







 let page = Page(text: maybeNilString.orEmpty())
      
      





Kotlinのカウンターパートよりも難しく見えますよね? そして、残念ながら、欠点もあります。 ご存じのように、Swiftのオプションは汎用の列挙であるため、すべてのオプションタイプで拡張機能を使用できます。 あまり良く見えません:













ただし、コンパイラーはユーザーを保護し、このコードをコンパイルしません。 しかし、これらの拡張機能をさらに追加すると、自動ヘルプがゴミで詰まってしまいます。







それでは、Kotlin拡張機能はSwiftより便利ですか? Swiftの拡張機能は他の目的のためだと思います;)。 Android開発者はお待ちください!







プロトコルと拡張機能は、連携して動作するように設計されています。 このプロトコルに準拠するクラスの独自のプロトコルと拡張機能を作成できます。 クレイジーに聞こえますが、それだけではありません! プロトコルとの条件付きコンプライアンスなどのものがあります 。 これは、クラス/構造が特定の条件下でプロトコルに準拠できることを意味します。







ポップアップアラートを表示する必要がある場所がたくさんあるとします。 DRYの原則は気に入っていますが、コードをコピーして貼り付けたくありません。 プロトコルと拡張機能を使用してこの問題を解決できます。







まず、プロトコルを作成します。







 protocol AlertPresentable { func presentAlert(message: String) }
      
      





次に、デフォルト実装の拡張機能:







 extension AlertPresentable { func presentAlert(message: String) { let alert = UIAlertController(title: “Alert”, message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: “OK”, style: .default, handler: nil)) } }
      
      





したがって、presentAlertメソッドはアラートを作成するだけで、何も表示しません。 これには、View Controllerへのリンクが必要です。 このメソッドにパラメーターとして渡すことはできますか? 良い考えではありません。 Whereを使用しましょう!







 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) } }
      
      





ここには何がありますか? プロトコルを拡張するための特定の要件を追加しました。 UIViewControllerのみを対象としています。 これにより、presentAlertメソッドでUIViewControllerメソッドを使用できます。 これにより、アラートを表示できます。







どうぞ







 extension UIViewController: AlertPresentable {}
      
      





すべてのUIViewControllerに新しい機能が追加されました。













また、プロトコルと拡張機能の組み合わせは、テストに非常に役立ちます。 皆さん、あなたのアプリケーションでAndroidの最終クラスを何回テストしようとしましたか? これはSwiftにとって問題ではありません。







この状況を見てみましょう。Swiftにfinalクラスがあると仮定します。 メソッドのシグネチャがわかっている場合は、同じメソッドでプロトコルを作成し、このプロトコルを実装する拡張機能を最終クラスに追加します。 このクラスを直接使用する代わりに、プロトコルを使用して簡単にテストできます。 千語ではなくサンプルコード。







 final class FrameworkMap { private init() { … } func drawSomething() { … } } class MyClass { … func drawSomethingOnMap(map: FrameworkMap) { map.drawSomething() } }
      
      





テストでは、drawSomethingOnMapメソッドを実行するときに、マップオブジェクトでdrawSomethingメソッドが呼び出されるかどうかを確認する必要があります。 これは、Mockito(Android用の有名なテストライブラリ)を使用しても難しい場合があります。 しかし、プロトコルと拡張機能を使用すると、次のようになります。







 protocol Map { func drawSomething() } extension FrameworkMap: Map {}
      
      





そして今、あなたのdrawSomethingOnMapメソッドはクラスの代わりにプロトコルを使用します。







 class MyClass { … func drawSomethingOnMap(map: Map) { map.drawSomething() } }
      
      





シールドクラス-ステロイドの転送



最後に、列挙について言及したいと思います。







Java列挙とKotlin列挙の間に違いはないため、ここに追加するものはありません。 しかし、見返りに何か新しいものがあり、これらは「スーパー列挙」、つまり封印されたクラスです。 スーパーリストの概念はどこから来たのですか? Kotlinのドキュメントを参照してください。







「...ある意味、列挙クラスの拡張機能です。列挙に使用できる値のセットも制限されていますが、各列挙定数は単一のインスタンスにのみ存在します。 」

さて、クール、彼らは財産を保つことができますが、どのようにそれを使用できますか?







 sealed class OrderStatus { object AwaitPayment : OrderStatus() object InProgress : OrderStatus() object Completed : OrderStatus() data class Canceled(val reason: String) : OrderStatus() }
      
      





これは、注文ステータスモデルである封印されたクラスです。 乗り換えの方法と非常に似ていますが、注意点が1つあります。 Canceled値には、キャンセルの理由が含まれています。 キャンセルの理由は異なる場合があります。







 val orderStatus = OrderStatus.Canceled(reason = "No longer in stock") … val orderStatus = OrderStatus.Canceled(reason = "Not paid")
      
      





通常のリストではこれを行えません。 列挙値が作成された場合、変更できません。







他の違いに気づきましたか? 封印されたクラスの別の機能を利用しました。 これは、さまざまなタイプの関連データです。 古典的な列挙には、列挙値のすべてのバリアントの関連データの転送が含まれ、すべての値は同じタイプでなければなりません。







Swiftにはシールドクラスに相当するものがあり、列挙と呼ばれます。 Kotlinの列挙はJavaの単なる遺物であり、90%の時間はシールドクラスを使用します。 シールクラスとSwift列挙型を区別するのは困難です。 名前のみが異なり、もちろん、封印されたクラスは参照によって渡され、Swiftの列挙は値によって渡されます。 私が間違っている場合は修正してください。







さよならは言わない



コードの記述方法に対するメモリ管理の影響にはまだ微妙な違いがあります。 私はすべての側面をカバーしていないことを知っています。これは私がまだ勉強しているからです。 2つの言語の間に他の違いがあることに気づいたら、教えてください。 私は常に新しいことにオープンです!








All Articles