迅速な機能

Yandex Mobile Campの一環として、同僚のDenis Lebedevが新しいSwiftプログラミング言語に関するレポートを発表しました。 彼の報告では、彼はObjective-Cとの相互作用の特徴に触れ、言語の特徴について話しました。 また、Githubのどこに行くべきか、実際の世界でSwiftで何ができるかを理解するためにどのリポジトリを探すべきかについても説明します。



Swift開発は2010年に始まりました。 クリス・ラトナーはそれに従事していました。 2013年まで、このプロセスはあまり活発ではありませんでした。 徐々にますます多くの人々が関与するようになりました。 2013年、Appleはこの言語の開発に注力しました。 WWDCでのプレゼンテーションの前に、約200人がSwiftを知っていました。 彼に関する情報は極秘に保管されていました。









Swiftはマルチパラダイム言語です。 OOPがあり、機能的なものを試すことができますが、関数型プログラミングの支持者は、Swiftは少し場違いだと信じています。 しかし、そのような目標は設定されていなかったように見えますが、それは数学者のためではなく、人々のための言語です。 手続き型のスタイルで記述できますが、これがプロジェクト全体に当てはまるかどうかはわかりません。 Swiftで非常に興味深いのは、すべてが類型化されていることです。 動的なObjective-Cとは異なり、静的です。 型推論もあります。 つまり ほとんどの変数型宣言は単純に省略できます。 さて、Swiftの優れた機能は、Objective-Cとの非常に深い相互作用と考えることができます。 後でランタイムについて説明しますが、今のところは、SwiftのコードをObjective-Cで使用でき、その逆も同様であるという事実に限定します。 SwiftのすべてのObjective-CおよびC ++開発者になじみのあるポインターはありません。



機能に移りましょう。 たくさんあります。 私は自分のためにいくつかの重要なものを特定しました。



最後の機能をさらに詳しく考えてみましょう。 Objective-Cでは、何を返すのかわからない場合、オブジェクトにはnilを返し、スカラーには-1またはNSNotFoundを返します。 オプションのタイプは、この問題を根本的に解決します。 オプションのタイプは、値を含むか、何も含まないボックスと考えることができます。 そして、どのタイプでも機能します。 この署名があるとします:



(NSInteger) indexOfObjec: (id)object;
      
      





Objective-Cでは、メソッドが何を返すかは不明です。 オブジェクトがない場合、-1、NSNotFound、または開発者のみが知っている他の定数になります。 Swiftで同じメソッドを見ると、疑問符が付いたIntが表示されます。



 func indexOF(object: AnyObject) -> Int?
      
      





この構造は、数値またはvoidが返されることを示しています。 したがって、パックされたIntを受け取ったら、アンパックする必要があります。 解凍には、安全(すべてがif / elseに変わる)と強制の2種類があります。 後者を使用できるのは、仮想ボックスに値があることが確実にわかっている場合のみです。 彼がそこにいない場合、実行時にクラッシュが発生します。



ここで、クラス、構造、および列挙の主な機能について簡単に説明しますが、クラスと構造の主な違いは、参照によって渡されることです。 構造体は値で渡されます。 ドキュメンテーションが私達に言うように、構造を使用することははるかに少ないリソースを消費します。 そして、すべてのスカラー型とブール変数は構造体を通して実装されます。



転送を選択したいと思います。 これらは、C、Objective-C、およびその他の言語の対応する言語とはまったく異なります。 これは、クラス、構造、およびそれ以上の組み合わせです。 私の言いたいことを示すために、例を考えてみましょう。 enum



を使用してツリーを実装するとします。 3つの要素(空、ノード、シート)を含む小さなリストから始めましょう。



 enum Tree { case Empty case Leaf case Node }
      
      





これをどうするかは不明です。 しかし、Swiftでは、各enum



要素は何らかの値を保持できます。 これを行うには、 Int



をシートに追加し、ノードにはさらに2つのツリーがあります。



 enum Tree { case Empty case Leaf(Int) case Node(Tree, Tree) }
      
      





ただし、Swiftはジェネリックをサポートしているため、任意のタイプのサポートをツリーに追加します。



 enum Tree<T> { case Empty case Leaf(T) case Node(Tree, Tree) }
      
      





ツリー宣言は次のようになります。



 let tree: Tree<Int> = .Node(.Leaf(1), .Leaf(1))
      
      





Swiftはこれらの型をコンパイル段階で表示するため、列挙の名前を書くことはできません。



Swiftのenum



には、別の興味深い機能があります。構造体やクラスのように、関数自体を含むことができます。 ツリーの深さを返す関数を書きたいとします。



 enum Tree { case Empty case Leaf(Int) case Node(Tree, Tree) func depth<T>(t: Tree<T>) -> Int { return 0 } }
      
      





この関数について私が気に入らないのは、treeパラメーターを受け入れることです。 関数に値を返すだけで、何も渡す必要はありません。 ここでは、Swiftの別の興味深い機能、ネストされた関数を使用します。 なぜなら アクセス修飾子はまだありません-これは関数をプライベートにする1つの方法です。 したがって、ツリーの深さを考慮する_depth



があります。



 enum Tree<T> { casefunc depth() -> Int { func _depth<T>(t: Tree<T>) -> Int { return 0 } return _depth(self) } }
      
      





標準のスイッチが表示されます。迅速なものはありません。ツリーが空のときにオプションを処理するだけです。 さらに興味深いことが始まります。 シートに保存されている値を解凍します。 しかし、私たちはそれを必要とせず、ユニットを返すだけなので、アンダースコアを使用します。つまり、シート内の変数は必要ありません。 次に、左右のパーツを取得するノードをアンパックします。 次に、depth関数を再帰的に呼び出して、結果を返します。 結果として、いくつかの基本的な操作でenum



実装されたそのようなツリーを取得します。



 enum Tree<T> { case Empty case Leaf(T) case Node(Tree, Tree) func depth() -> Int { func _depth<T>(t: Tree<T>) -> Int { switch t { case .Empty: return 0 case .Leaf(let_): return 1 case .Node(let lhs, let rhs): return max(_depth(lhs), _depth(rhs)) } } return _depth(self) } }
      
      





このenum



の興味深い点は、このenum



で記述されたこのコードは機能するはずですが、機能しないことです。 現在のバージョンでは、バグにより、 enum



は再帰型をサポートしていません。 将来的には動作します。 これまでのところ、このバグを回避するためにさまざまなハックが使用されています。 そのうちの1つについては少し後で説明します。



私のストーリーの次の段落は、標準ライブラリで配列、辞書、および文字列(チャームのコレクション)で表されるコレクションです。 スカラーのようなコレクションは構造体であり、NSDictionaryやNSArrayなどの標準的な基盤タイプとも交換可能です。 さらに、何らかの奇妙な理由でNSSetタイプがないことがわかります。 それらはおそらくあまりにもまれに使用されます。 一部の操作(例: filter



およびreverse



)には遅延計算があります:



 func filter<S :Sequence>(…) -> Bool) -> FilterSequenceView<S> func reverce<S :Collection …>(source: C) -> ReverseView<C>
      
      





つまり FilterSequenceView



およびReverseView



タイプは、処理されたコレクションではなく、その表現です。 これは、これらのメソッドのパフォーマンスが高いことを示しています。 Objective-Cでは、この言語の作成時点では誰もそのような概念について考えていなかったため、このようなunningな構造は見つかりません。 現在、レイジーコンピューティングはプログラミング言語に浸透しています。 私はこの傾向が好きで、時には非常に効果的です。



次の機能は、おそらく新しい言語に何らかの形で興味を持っているすべての人によって既に気づかれています。 しかし、とにかく彼女についてお話しします。 Swiftには変数の組み込みの不変性があります。 変数を宣言するには、2つの方法がありますvar



let



ます。 前者の場合、変数を変更できます、後者の場合-いいえ。



 var  = 3 b += 1 let a = 3 a += 1 // error
      
      





ここから面白いことが始まります。 たとえば、 let



ディレクティブを使用して宣言されたディクショナリを見ると、キーを変更または新しいキーを追加しようとすると、エラーが発生します。



 let d = ["key": 0] d = ["key"] = 3 //error d.updateValue(1, forKey: "key1") //error
      
      





配列は少し異なります。 配列のサイズを増やすことはできませんが、その要素を変更することはできます。



 let c = [1, 2, 3] c[0] = 3 // success c.append(5) // fail
      
      





実際、それは非常に奇妙です。何が問題なのかを理解しようとすると、言語開発者によって確認されたバグであることが判明しました。 近い将来、修正されます。 これは本当に非常に奇妙な動作です。



Swiftの拡張機能は、Objective-Cのカテゴリに非常に似ていますが、より言語に浸透しています。 Swiftでインポートを記述する必要はありません。コード内の任意の場所に拡張機能を記述できます。これは、すべてのコードによって選択されます。 したがって、同様に、構造およびエナムを拡張することが可能であり、これも時々便利です。 拡張機能の助けを借りて、コードを非常にうまく構成できます。これは標準ライブラリに実装されています。



 struct: Foo { let value : Int } extension Foo : Printable { var description : String { get {return "Foo"} } } extension Foo : Equatable { } func ==(lhs: Foo, rhs: Foo) -> Bool { return lhs.value == rhs.value }
      
      





次に、Swiftにないものについて話しましょう。 特定の何かが欠けているとは言えません 本番環境ではまだ使用していません。 しかし、多くの人が不満を言うものがあります。



それでは、Objective-CとSwiftに干渉する方法について説明しましょう。 SwiftからObjective-Cコードを呼び出すことができることは誰もがすでに知っています。 逆の方向では、すべてがまったく同じように機能しますが、いくつかの制限があります。 列挙、タプル、一般化された型は機能しません。 ポインターはありませんが、CoreFoundation型は直接呼び出すことができます。 多くの人にとって、Swiftから直接C ++コードを呼び出せないことが障害になっています。 ただし、Objective-Cでラッパーを作成し、既に呼び出すことができます。 さて、Swiftから実装されていないObjective-Cクラスではサブクラス化できないのは当然です。



上で言ったように、いくつかのタイプは交換可能です:





Swiftで記述されたクラスの例を示しますが、Objective-Cで使用できます。必要なディレクティブは1つだけです。

 @objc class Foo { int (bar: String) { /*...*/} }
      
      





Objective-Cのクラスに別の名前(たとえば、 Foo



ではなくobjc_Foo



)を付け、メソッドのシグネチャを変更する場合、すべてが少し複雑になります。



 @objc(objc_Foo) class Foo{ @objc(initWithBar:) init (bar: String) { /*...*/} }
      
      





したがって、Objective-Cではすべてが絶対に期待されます。



 Foo *foo = [[Foo alloc] initWithBar:@"Bar"];
      
      





当然、すべての標準フレームワークを使用できます。 すべてのヘッダーについて、Swiftでのそれらの表現は自動的に生成されます。 convertPoint



関数があるとしましょう:



 - (CGPoint)convertPoint:(CGPoint)point toWindow:(UIWindow *)window
      
      





唯一の違いはありますが、完全にSwiftに変換されます: UIWindow



近くに感嘆符があります。 これは、前述の非常にオプションのタイプを示しています。 つまり nilがあり、それをチェックしない場合、実行時にクラッシュが発生します。 これは、ジェネレーターがこれらのヘッダーを作成するときに、nilにできるかどうかわからないため、これらの感嘆符をどこにでも配置するためです。 おそらくすぐに彼らはそれを何らかの形で修正するでしょう。



 finc convertPoint(point: CGPoint, toWindow window: UIWindow!) -> GCPoint
      
      





詳細には、Swiftの内部とパフォーマンスについて話すのは時期尚早です。これは、現在のランタイムが最初のバージョンまで生き残るかどうかがわからないためです。 したがって、これまでのところ、このトピックについては表面的にのみ触れます。 まず、すべてのSwiftオブジェクトはObjective-Cオブジェクトです。 新しいルートクラスSwiftObjectが表示されます。 メソッドは、クラスではなく仮想テーブルに保存されるようになりました。 別の興味深い機能は、変数の型が個別に保存されることです。 したがって、クラスをその場でデコードするのはもう少し難しくなります。 メソッドのメタデータをエンコードするには、名前マングリングと呼ばれるアプローチが使用されます。 たとえば、 Bool



返すbar



メソッドを持つFoo



クラスを見てください:

 class Foo { func bar() -> Bool { return false } }
      
      





バイナリを見ると、 bar



メソッドの場合、次の形式の署名が表示されます: _TFC9test3Foo3barfS0_FT_Sb



ここに、3文字の長さのFoo



があり、メソッドの長さも3文字です。最後のSb



は、メソッドがBool



返すことを意味します。 これは非常に楽しいこととは関係ありません。Xcodeのデバッグログはすべてこの形式に完全に該当するため、それらを読み取ることはあまり便利ではありません。

おそらく誰もがすでにSwiftが非常に遅いことを読んでいるでしょう。 概して、これは事実ですが、それを理解してみましょう。 -O0



フラグを使用してコンパイルする場合、つまり 最適化を行わないと、SwiftのC ++は10〜100倍遅くなります。 -O3



フラグを指定してコンパイルすると、C ++よりも10倍遅くなります。 -Ofast



フラグ-Ofast



、ランタイムなどでintオーバーフローチェックを無効にするため、あまり安全で-Ofast



ません。 実稼働環境では使用しないほうがよいでしょう。 ただし、C ++のレベルまでパフォーマンスを改善できます。

あなたは言語が非常に若いことを理解する必要があります、それはまだベータ版です。 将来、速度に関する主な問題は修正されます。 さらに、SwiftのObjective-Cレガシーストレッチは、たとえば、Swiftには本質的に不要な膨大な数の保持とリリースがありますが、パフォーマンスが非常に遅くなります。



さらに、開発プロセス中に遭遇した、互いにあまり関係のないことについてお話しします。 前述したように、マクロはサポートされていないため、クロスプラットフォームビューを作成する唯一の方法は次のとおりです。



 #if os(iOS) typealias View = UView #else typealias View = NSView #endif class MyControl : View { }
      
      





このif



はプリプロセッサではなく、プラットフォームをテストするための単なる言語構成要素です。 したがって、どのプラットフォームにいるかを返すメソッドはありません。 これに応じて、 View



エイリアスを作成しView



。 したがって、iOSとOS Xの両方で動作するMyControl



を作成します。



次の機能-サンプルとの比較-私は本当に好きです。 私は関数型言語が少し好きで、非常に広く使用されています。 例として問題を考えてみましょう。平面上に点があり、4つの象限のうちどれが入っているかを理解したいのです。 私たちは皆、Objective-Cでどのようなコードになるのか想像しています。 各象限ごとに、xとyがこのフレームワークに該当するかどうかを確認する必要がある、絶対にワイルドな条件があります。



 let point = (0, 1) if point.0 >= 0 && point.0 <= 1 && point.1 >= 0 && point.1 <= 1 { println("I") } ...
      
      





この場合、Swiftはいくつかの便利なピースを提供します。 最初に、3つのポイントを持つトリッキーな範囲演算子があります。 したがって、 case



は、ポイントが第1象限に該当するかどうかを確認できます。 そして、コード全体は次のようになります。



 let point = (0, 1) switch point { case (0, 0) println("Point is at the origin") case (0...1, 0...1): println("I") case (-1...0, 0...1): println("II") case (-1...0, -1...0): println("III") case (0...1, -1...0): println("IV") default: println("I don't know") }
      
      





私の意見では、これはObjective-Cが提供できるものよりも10倍読みやすくなっています。



Swiftには、関数型プログラミング言語からも生まれた絶対的なニッチがもう1つあります-関数のカリー化:



 func add(a: Int)(b: Int) -> Int { return a + b } let foo = add(5)(b: 3) // 8 let add5 = add(5) // (Int) -> Int let bar = add(b: 3) // 8
      
      





このようなトリッキーな宣言を持つadd



関数があることがわかります。1対ではなくパラメーター付きの2組のブラケットです。 これにより、この関数をほぼ通常のように呼び出して結果8を取得するか、1つのパラメーターで呼び出すことができます。 2番目のケースでは、魔法が発生します。出力では、 Int



を受け取り、 Int



も返す関数を取得します。 5つにadd



関数を部分的に適用しました。 したがって、トリプルでadd5



関数を使用すると、8の数字が得られます。



私が言ったように、プリプロセッサが欠落しているので、 assert



実装assert



ことさえ簡単なことではありません。 独自の何らかのassert



タスクがあるassert



ます。 デバッグのためにテストできますが、アサートで実行されないコードをクロージャーとして渡す必要があります。 つまり ブレースの中に5 % 2



あることがわかります。 用語では、Objective-Cはブロックです。



 func assert(condition:() -> Bool, message: String) { #if DEBUG if !condition() { println(message) } #endif } assert({5 % 2 == 0}, "5 isn't an even number.")
      
      





誰もそのようなアサートを使用しないことは明らかです。 そのため、Swiftには自動閉鎖があります。 メソッド宣言では、それぞれ@autoclosure



を参照します。最初の引数はクロージャーになり、中括弧は省略できます。



 func assert(condition: @auto_closure () -> Bool, message: String) { #if DEBUG if !condition() { println(message) } #endif } assert(5 % 2 == 0, "5 isn't an even number.")
      
      





文書化されていないが非常に便利なもう1つのことは、明示的な型変換です。 Swiftは型付き言語であるため、Objective-Cのように、オブジェクトにid型を設定することはできません。 したがって、次の例を検討してください。 初期化中に値を取得するBox



構造があり、変更できないと仮定します。 そして、パッケージ化されたInt



ユニットがあります。



 struct Box<T> { let _value : T init (_ value: T) { _value = value } } let boxedInt = Box(1) //Box<Int>
      
      





Int



を受け入れる関数もあります。 したがって、そこでboxedInt



転送することはできません。 コンパイラは、 Box



Int



変換されないことを通知します。 職人はスイフトの根性を少し切り裂き、 Box



タイプをそれ自体が隠す値に変換できる関数を見つけました。



 extension Box { @conversion Func __conversion() -> T { return _value } } foo(boxedInt) //success
      
      





また、言語の静的な型付けでは、Objective-Cで実行できるため、クラスを迂回してメソッドを置き換えることはできません。 現在の状態からは、オブジェクトプロパティのリストを取得して、現時点でそれらの値を表示することしかできません。 つまり メソッドに関する情報を受け取ることはできません。



 struct Foo { var str = "Apple" let int = 13 func foo() { } } reflect(Foo()).count // 2 reflect(Foo())[0].0 // "str" reflect(Foo())[0].1summary // "Apple"
      
      





swiftからCコードを直接呼び出すことができます。 この機能はドキュメントには反映されていませんが、役に立つ場合があります。



 @asmname("my_c_func") func my_c_func(UInt64, CMutablePointer<UInt64>) -> CInt;
      
      





もちろん、Swiftはコンパイルされた言語ですが、これはスクリプトのサポートを妨げません。 まず、 xcrun swift



コマンドで起動される対話型ランタイムがあります。 さらに、通常のスクリプト言語ではなく、Swiftで直接スクリプトを作成できます。 それらはxcrun -i 'file.swift'



を使用してxcrun -i 'file.swift'



ます。



最後に、一見の価値があるリポジトリについて説明します。




All Articles