Objective-Cのバックグラウンドから来て、最初は、Swiftが私を抑制しているように感じました。 Swiftは、強く型付けされた性質のため、私が進歩することを許可していませんでした。
Objective-Cとは異なり、Swiftはコンパイル時に多くの要件を適用します。 id
など、Objective-Cでリラックスできるものタイプと暗黙の変換は、Swiftでは重要ではありません。 Int
を持っていてもおよびDouble
であり、それらを合計する場合は、それらを明示的に単一の型に変換する必要があります。
また、オプションは言語の基本的な部分であり、単純な概念ですが、慣れるまでに少し時間がかかります。
最初は、すべてを強制的にアンラップすることをお勧めしますが、最終的にはクラッシュにつながります。言語に精通するにつれて、コンパイル時に多くの間違いが検出されるため、ランタイムエラーがほとんど発生しないことが好きになります。
最も スイフトプログラマー Objective-Cでの重要な経験があります。これにより、他の言語で慣れ親しんでいるのと同じ方法を使用してSwiftコードを作成できる可能性があります。そして、それはいくつかの悪い間違いを引き起こす可能性があります。
この記事では、Swift開発者が犯す最も一般的な間違いとそれを回避する方法について概説します。
間違いありません-Objective-CのベストプラクティスはSwiftのベストプラクティスではありません。 つぶやきオプションの型の変数(例:String?
)は、値を保持する場合と保持しない場合があります。値を持たない場合は、nil
に等しくなります。オプションの値を取得するには、最初に次のことを行う必要があります アンラップ それら、そしてそれは2つの異なる方法で作ることができます。
1つの方法は、if let
を使用したオプションのバインディングです。またはguard let
、つまり:
var optionalString: String? //... if let s = optionalString { // if optionalString is not nil, the test evaluates to // true and s now contains the value of optionalString } else { // otherwise optionalString is nil and the if condition evaluates to false }
2つ目は、!
を使用して強制的にアンラップすることです。演算子、または暗黙的にアンラップされたオプションの型(String!
など)を使用します。オプションがnil
の場合、アンラップを強制するとランタイムエラーが発生し、アプリケーションが終了します。さらに、暗黙的にラップされていないオプションの値にアクセスしようとすると、同じことが発生します。
クラス/構造体初期化子で初期化できない(または初期化したくない)変数がある場合があります。したがって、それらをオプションとして宣言する必要があります。場合によっては、nil
ではないと想定しますコードの特定の部分では、それらを強制的にアンラップするか、暗黙的にアンラップされたオプションとして宣言します。これは、常にオプションのバインドを行うよりも簡単だからです。これは注意して行う必要があります。
これは、ペン先またはストーリーボード内のオブジェクトを参照する変数であるIBOutlet
sの操作に似ています。親オブジェクトの初期化(通常はViewControllerまたはカスタムUIView
)では初期化されませんが、nil
ではないことは確かです。いつviewDidLoad
(ビューコントローラ内)またはawakeFromNib
(ビューで)が呼び出されるので、安全にアクセスできます。
一般に、ベストプラクティスは、アンラップを強制したり、暗黙的にアンラップされたオプションを使用したりしないことです。オプションはnil
である可能性があることを常に考慮してくださいオプションのバインディングを使用するか、そうでないかどうかを確認して、適切に処理しますnil
アンラップを強制する前、または暗黙的にアンラップされたオプションの場合は変数にアクセスする前。
強力な参照サイクルは、オブジェクトのペアが相互に強力な参照を維持する場合に存在します。 Objective-Cにも同じ問題があり、経験豊富なObjective-C開発者がこれを適切に管理することが期待されているため、これはSwiftにとって新しいことではありません。強い参照と何が何を参照しているかに注意を払うことが重要です。 Swiftのドキュメントには このトピック専用のセクション 。
クロージャを使用するときは、参照を管理することが特に重要です。デフォルトでは、クロージャ(またはブロック)は、クロージャ内で参照されるすべてのオブジェクトへの強力な参照を保持します。これらのオブジェクトのいずれかがクロージャー自体を強く参照している場合、強い参照サイクルがあります。利用する必要があります キャプチャリスト 参照のキャプチャ方法を適切に管理します。
ブロックが呼び出される前に、ブロックによってキャプチャされたインスタンスの割り当てが解除される可能性がある場合は、それをとしてキャプチャする必要があります。 弱い参照 、nil
になる可能性があるため、オプションになります。これで、キャプチャされたインスタンスがブロックの存続期間中に割り当て解除されないことが確実な場合は、それを次のようにキャプチャできます。 所有されていない参照 。 unowned
を使用する利点weak
の代わりに参照はオプションではなく、値をアンラップせずに直接使用できるということです。
Xcode Playgroundで実行できる次の例では、Container
クラスには配列と、配列が変更されるたびに呼び出されるオプションのクロージャがあります( プロパティオブザーバー そうするには)。 Whatever
クラスにはContainer
がありますインスタンス、およびその初期化子で、クロージャをarrayDidChange
に割り当てます。このクロージャはself
を参照するため、Whatever
の間に強い関係が作成されます。インスタンスとクロージャ。
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この例を実行すると、deinit whatever
に気付くでしょう。印刷されることはありません。つまり、インスタンスw
メモリから割り当てが解除されません。これを修正するには、キャプチャリストを使用してself
をキャプチャしないようにする必要があります。強く:
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { [unowned self] array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この場合、unowned
を使用できるため、self
を使用できます。 nil
になることはありません閉鎖の存続期間中。
ほとんどの場合、キャプチャリストを使用して参照サイクルを回避することをお勧めします。これにより、メモリリークが減少し、最終的にはより安全なコードになります。
self
を使用するどこにでもObjective-Cとは異なり、Swiftではself
を使用する必要はありません。メソッド内のクラスまたは構造体のプロパティにアクセスします。 self
をキャプチャする必要があるため、クロージャでのみこれを行う必要があります。 self
を使用する必要のない場所は間違いではなく、問題なく機能し、エラーや警告はありません。しかし、なぜあなたがしなければならないより多くのコードを書くのですか?また、コードの一貫性を保つことも重要です。
Swiftは 値型 そして 参照型 。さらに、値型のインスタンスは、参照型のインスタンスの動作がわずかに異なります。各インスタンスがどのカテゴリに当てはまるかわからないと、コードの動作に誤った期待が生じます。
ほとんどのオブジェクト指向言語では、クラスのインスタンスを作成して他のインスタンスに渡し、メソッドの引数として渡すと、このインスタンスはどこでも同じであることが期待されます。つまり、実際には、まったく同じデータへの参照の集まりにすぎないため、変更はどこにでも反映されます。この動作を示すオブジェクトは参照型であり、Swiftではすべての型がclass
として宣言されています。参照型です。
次に、struct
を使用して宣言される値型があります。またはenum
。値型は、変数に割り当てられるか、関数またはメソッドに引数として渡されるときにコピーされます。コピーしたインスタンスで何かを変更しても、元のインスタンスは変更されません。値型は 不変 。 CGPoint
などの値型のインスタンスのプロパティに新しい値を割り当てる場合またはCGSize
の場合、変更を加えて新しいインスタンスが作成されます。そのため、配列のプロパティオブザーバーを使用して(上記のContainer
クラスの例のように)、変更を通知できます。実際に起こっていることは、変更を加えて新しいアレイが作成されることです。プロパティに割り当てられ、didSet
呼び出されます。
したがって、扱っているオブジェクトが参照型または値型であることがわからない場合は、コードが何をするかについての期待が完全に間違っている可能性があります。
列挙型について話すとき、私たちは一般的に基本的なC列挙型について考えます。これは、下にある整数である関連する定数の単なるリストです。 Swiftでは、列挙型ははるかに強力です。たとえば、各列挙ケースに値を付加できます。列挙型には、メソッドと読み取り専用/計算プロパティもあり、各ケースをより多くの情報と詳細で強化するために使用できます。
最高財務責任者の職務は何ですか
公式 列挙型に関するドキュメント 非常に直感的で、 エラー処理ドキュメント Swiftでの列挙型の追加パワーのいくつかの使用例を示します。また、以下の広範なをチェックしてください Swiftでの列挙型の探索 あなたがそれらでできるほとんどすべてを学ぶために。
Swift標準ライブラリは、関数型プログラミングの基本であり、次のような1行のコードで多くのことを実行できる多くのメソッドを提供します。 地図 、 減らす 、および フィルタ 、とりわけ。
いくつかの例を見てみましょう。
たとえば、テーブルビューの高さを計算する必要があります。あなたがUITableViewCell
を持っているとすると次のようなサブクラス:
class CustomCell: UITableViewCell { // Sets up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:) func configureWithModel(model: Model) // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:) class func heightForModel(model: Model) -> CGFloat }
モデルインスタンスの配列があるとしますmodelArray
; 1行のコードでテーブルビューの高さを計算できます。
let tableHeight = modelArray.map { CustomCell.heightForModel(ほとんどのSwift開発者が犯していることを知らない間違い
Objective-Cのバックグラウンドから来て、最初は、Swiftが私を抑制しているように感じました。 Swiftは、強く型付けされた性質のため、私が進歩することを許可していませんでした。
Objective-Cとは異なり、Swiftはコンパイル時に多くの要件を適用します。 id
など、Objective-Cでリラックスできるものタイプと暗黙の変換は、Swiftでは重要ではありません。 Int
を持っていてもおよびDouble
であり、それらを合計する場合は、それらを明示的に単一の型に変換する必要があります。
また、オプションは言語の基本的な部分であり、単純な概念ですが、慣れるまでに少し時間がかかります。
最初は、すべてを強制的にアンラップすることをお勧めしますが、最終的にはクラッシュにつながります。言語に精通するにつれて、コンパイル時に多くの間違いが検出されるため、ランタイムエラーがほとんど発生しないことが好きになります。
最も スイフトプログラマー Objective-Cでの重要な経験があります。これにより、他の言語で慣れ親しんでいるのと同じ方法を使用してSwiftコードを作成できる可能性があります。そして、それはいくつかの悪い間違いを引き起こす可能性があります。
この記事では、Swift開発者が犯す最も一般的な間違いとそれを回避する方法について概説します。
間違いありません-Objective-CのベストプラクティスはSwiftのベストプラクティスではありません。 つぶやきオプションの型の変数(例:String?
)は、値を保持する場合と保持しない場合があります。値を持たない場合は、nil
に等しくなります。オプションの値を取得するには、最初に次のことを行う必要があります アンラップ それら、そしてそれは2つの異なる方法で作ることができます。
1つの方法は、if let
を使用したオプションのバインディングです。またはguard let
、つまり:
var optionalString: String? //... if let s = optionalString { // if optionalString is not nil, the test evaluates to // true and s now contains the value of optionalString } else { // otherwise optionalString is nil and the if condition evaluates to false }
2つ目は、!
を使用して強制的にアンラップすることです。演算子、または暗黙的にアンラップされたオプションの型(String!
など)を使用します。オプションがnil
の場合、アンラップを強制するとランタイムエラーが発生し、アプリケーションが終了します。さらに、暗黙的にラップされていないオプションの値にアクセスしようとすると、同じことが発生します。
クラス/構造体初期化子で初期化できない(または初期化したくない)変数がある場合があります。したがって、それらをオプションとして宣言する必要があります。場合によっては、nil
ではないと想定しますコードの特定の部分では、それらを強制的にアンラップするか、暗黙的にアンラップされたオプションとして宣言します。これは、常にオプションのバインドを行うよりも簡単だからです。これは注意して行う必要があります。
これは、ペン先またはストーリーボード内のオブジェクトを参照する変数であるIBOutlet
sの操作に似ています。親オブジェクトの初期化(通常はViewControllerまたはカスタムUIView
)では初期化されませんが、nil
ではないことは確かです。いつviewDidLoad
(ビューコントローラ内)またはawakeFromNib
(ビューで)が呼び出されるので、安全にアクセスできます。
一般に、ベストプラクティスは、アンラップを強制したり、暗黙的にアンラップされたオプションを使用したりしないことです。オプションはnil
である可能性があることを常に考慮してくださいオプションのバインディングを使用するか、そうでないかどうかを確認して、適切に処理しますnil
アンラップを強制する前、または暗黙的にアンラップされたオプションの場合は変数にアクセスする前。
強力な参照サイクルは、オブジェクトのペアが相互に強力な参照を維持する場合に存在します。 Objective-Cにも同じ問題があり、経験豊富なObjective-C開発者がこれを適切に管理することが期待されているため、これはSwiftにとって新しいことではありません。強い参照と何が何を参照しているかに注意を払うことが重要です。 Swiftのドキュメントには このトピック専用のセクション 。
クロージャを使用するときは、参照を管理することが特に重要です。デフォルトでは、クロージャ(またはブロック)は、クロージャ内で参照されるすべてのオブジェクトへの強力な参照を保持します。これらのオブジェクトのいずれかがクロージャー自体を強く参照している場合、強い参照サイクルがあります。利用する必要があります キャプチャリスト 参照のキャプチャ方法を適切に管理します。
ブロックが呼び出される前に、ブロックによってキャプチャされたインスタンスの割り当てが解除される可能性がある場合は、それをとしてキャプチャする必要があります。 弱い参照 、nil
になる可能性があるため、オプションになります。これで、キャプチャされたインスタンスがブロックの存続期間中に割り当て解除されないことが確実な場合は、それを次のようにキャプチャできます。 所有されていない参照 。 unowned
を使用する利点weak
の代わりに参照はオプションではなく、値をアンラップせずに直接使用できるということです。
Xcode Playgroundで実行できる次の例では、Container
クラスには配列と、配列が変更されるたびに呼び出されるオプションのクロージャがあります( プロパティオブザーバー そうするには)。 Whatever
クラスにはContainer
がありますインスタンス、およびその初期化子で、クロージャをarrayDidChange
に割り当てます。このクロージャはself
を参照するため、Whatever
の間に強い関係が作成されます。インスタンスとクロージャ。
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この例を実行すると、deinit whatever
に気付くでしょう。印刷されることはありません。つまり、インスタンスw
メモリから割り当てが解除されません。これを修正するには、キャプチャリストを使用してself
をキャプチャしないようにする必要があります。強く:
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { [unowned self] array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この場合、unowned
を使用できるため、self
を使用できます。 nil
になることはありません閉鎖の存続期間中。
ほとんどの場合、キャプチャリストを使用して参照サイクルを回避することをお勧めします。これにより、メモリリークが減少し、最終的にはより安全なコードになります。
self
を使用するどこにでもObjective-Cとは異なり、Swiftではself
を使用する必要はありません。メソッド内のクラスまたは構造体のプロパティにアクセスします。 self
をキャプチャする必要があるため、クロージャでのみこれを行う必要があります。 self
を使用する必要のない場所は間違いではなく、問題なく機能し、エラーや警告はありません。しかし、なぜあなたがしなければならないより多くのコードを書くのですか?また、コードの一貫性を保つことも重要です。
Swiftは 値型 そして 参照型 。さらに、値型のインスタンスは、参照型のインスタンスの動作がわずかに異なります。各インスタンスがどのカテゴリに当てはまるかわからないと、コードの動作に誤った期待が生じます。
ほとんどのオブジェクト指向言語では、クラスのインスタンスを作成して他のインスタンスに渡し、メソッドの引数として渡すと、このインスタンスはどこでも同じであることが期待されます。つまり、実際には、まったく同じデータへの参照の集まりにすぎないため、変更はどこにでも反映されます。この動作を示すオブジェクトは参照型であり、Swiftではすべての型がclass
として宣言されています。参照型です。
次に、struct
を使用して宣言される値型があります。またはenum
。値型は、変数に割り当てられるか、関数またはメソッドに引数として渡されるときにコピーされます。コピーしたインスタンスで何かを変更しても、元のインスタンスは変更されません。値型は 不変 。 CGPoint
などの値型のインスタンスのプロパティに新しい値を割り当てる場合またはCGSize
の場合、変更を加えて新しいインスタンスが作成されます。そのため、配列のプロパティオブザーバーを使用して(上記のContainer
クラスの例のように)、変更を通知できます。実際に起こっていることは、変更を加えて新しいアレイが作成されることです。プロパティに割り当てられ、didSet
呼び出されます。
したがって、扱っているオブジェクトが参照型または値型であることがわからない場合は、コードが何をするかについての期待が完全に間違っている可能性があります。
列挙型について話すとき、私たちは一般的に基本的なC列挙型について考えます。これは、下にある整数である関連する定数の単なるリストです。 Swiftでは、列挙型ははるかに強力です。たとえば、各列挙ケースに値を付加できます。列挙型には、メソッドと読み取り専用/計算プロパティもあり、各ケースをより多くの情報と詳細で強化するために使用できます。
公式 列挙型に関するドキュメント 非常に直感的で、 エラー処理ドキュメント Swiftでの列挙型の追加パワーのいくつかの使用例を示します。また、以下の広範なをチェックしてください Swiftでの列挙型の探索 あなたがそれらでできるほとんどすべてを学ぶために。
Swift標準ライブラリは、関数型プログラミングの基本であり、次のような1行のコードで多くのことを実行できる多くのメソッドを提供します。 地図 、 減らす 、および フィルタ 、とりわけ。
いくつかの例を見てみましょう。
たとえば、テーブルビューの高さを計算する必要があります。あなたがUITableViewCell
を持っているとすると次のようなサブクラス:
class CustomCell: UITableViewCell { // Sets up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:) func configureWithModel(model: Model) // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:) class func heightForModel(model: Model) -> CGFloat }
モデルインスタンスの配列があるとしますmodelArray
; 1行のコードでテーブルビューの高さを計算できます。
let tableHeight = modelArray.map { CustomCell.heightForModel($0) }.reduce(0, combine: +)
map
各セルの高さを含むCGFloat
の配列とreduce
を出力します。それらを合計します。
配列から要素を削除したい場合は、次のことを行うことになります。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for s in supercars { if !isSupercar(s), let i = supercars.indexOf(s) { supercars.removeAtIndex(i) } }
この例は、indexOf
と呼んでいるため、エレガントでも効率的でもありません。アイテムごとに。次の例を考えてみましょう。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for (i, s) in supercars.enumerate().reverse() { // reverse to remove from end to beginning if !isSupercar(s) { supercars.removeAtIndex(i) } }
これで、コードはより効率的になりましたが、filter
を使用することでさらに改善できます。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } supercars = supercars.filter(isSupercar)
次の例は、UIView
のすべてのサブビューを削除する方法を示しています。特定の長方形と交差するフレームなど、特定の基準を満たすもの。次のようなものを使用できます。
for v in view.subviews { if CGRectIntersectsRect(v.frame, rect) { v.removeFromSuperview() } } ``` We can do that in one line using `filter` ``` view.subviews.filter { CGRectIntersectsRect($0.frame, rect) }.forEach { $0.removeFromSuperview() }
ただし、これらのメソッドへのいくつかの呼び出しを連鎖させて、凝ったフィルタリングと変換を作成したくなる可能性があるため、注意が必要です。これにより、1行の読み取り不可能なスパゲッティコードが生成される可能性があります。
Swiftが最初であると主張されています プロトコル指向プログラミング言語 、WWDCで言及されているように Swiftでのプロトコル指向プログラミング セッション。基本的に、これは、プロトコルに準拠して拡張するだけで、プロトコルを中心にプログラムをモデル化し、型に動作を追加できることを意味します。たとえば、Shape
があるとします。プロトコル、拡張できますCollectionType
(Array
、Set
、Dictionary
などのタイプに準拠しています)、交差点を考慮した総面積を計算するメソッドを追加します
protocol Shape { var area: Float { get } func intersect(shape: Shape) -> Shape? } extension CollectionType where Generator.Element: Shape { func totalArea() -> Float { let area = self.reduce(0) { (a: Float, e: Shape) -> Float in return a + e.area } return area - intersectionArea() } func intersectionArea() -> Float { /*___*/ } }
ステートメントwhere Generator.Element: Shape
拡張機能のメソッドは、CollectionType
に準拠する型の要素を含むShape
に準拠する型のインスタンスでのみ使用可能になることを示す制約です。たとえば、これらのメソッドはArray
のインスタンスで呼び出すことができますが、Array
のインスタンスでは呼び出すことができません。クラスがある場合Polygon
Shape
に準拠していますプロトコルの場合、これらのメソッドはArray
のインスタンスで使用できます。同じように。
プロトコル拡張を使用すると、プロトコルで宣言されたメソッドにデフォルトの実装を与えることができます。これにより、それらのタイプ(クラス、構造体、または列挙型)に変更を加えることなく、そのプロトコルに準拠するすべてのタイプで使用できるようになります。これは、Swift標準ライブラリ全体で広く行われています。 map
およびreduce
はCollectionType
の拡張で定義され、この同じ実装はArray
などのタイプで共有されます。およびDictionary
余分なコードなし。
この動作はに似ています ミックスイン RubyやPythonなどの他の言語から。デフォルトのメソッド実装を備えたプロトコルに準拠するだけで、タイプに機能を追加できます。
プロトコル指向プログラミングは、一見すると非常に扱いにくく、あまり役に立たないように見えるかもしれません。そのため、それを無視したり、試してみたりすることすらできない可能性があります。 この郵便受け 実際のアプリケーションでのプロトコル指向プログラミングの使用をよく理解できます。
Swiftは当初、多くの懐疑論を持って受け入れられました。人々は、AppleがObjective-Cを子供向けのおもちゃの言語またはプログラマー以外の人向けの言語に置き換えるつもりだと考えているようでした。ただし、Swiftは、プログラミングを非常に快適にする真面目で強力な言語であることが証明されています。強く型付けされているため、間違いを犯しにくく、そのため、言語で犯す可能性のある間違いをリストすることは困難です。
Swiftに慣れてObjective-Cに戻ると、違いに気付くでしょう。 Swiftが提供する優れた機能を見逃し、同じ効果を実現するには、Objective-Cで面倒なコードを作成する必要があります。また、コンパイル中にSwiftがキャッチしたであろうランタイムエラーに直面することもあります。これはAppleプログラマーにとって素晴らしいアップグレードであり、言語が成熟するにつれて、まだまだたくさんのことがあります。
関連: iOS開発者ガイド:Objective-CからLearningSwiftまでmap
各セルの高さを含むCGFloat
の配列とreduce
を出力します。それらを合計します。
配列から要素を削除したい場合は、次のことを行うことになります。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for s in supercars { if !isSupercar(s), let i = supercars.indexOf(s) { supercars.removeAtIndex(i) } }
この例は、indexOf
と呼んでいるため、エレガントでも効率的でもありません。アイテムごとに。次の例を考えてみましょう。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for (i, s) in supercars.enumerate().reverse() { // reverse to remove from end to beginning if !isSupercar(s) { supercars.removeAtIndex(i) } }
これで、コードはより効率的になりましたが、filter
を使用することでさらに改善できます。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } supercars = supercars.filter(isSupercar)
次の例は、UIView
のすべてのサブビューを削除する方法を示しています。特定の長方形と交差するフレームなど、特定の基準を満たすもの。次のようなものを使用できます。
for v in view.subviews { if CGRectIntersectsRect(v.frame, rect) { v.removeFromSuperview() } } ``` We can do that in one line using `filter` ``` view.subviews.filter { CGRectIntersectsRect(ほとんどのSwift開発者が犯していることを知らない間違い
Objective-Cのバックグラウンドから来て、最初は、Swiftが私を抑制しているように感じました。 Swiftは、強く型付けされた性質のため、私が進歩することを許可していませんでした。
Objective-Cとは異なり、Swiftはコンパイル時に多くの要件を適用します。 id
など、Objective-Cでリラックスできるものタイプと暗黙の変換は、Swiftでは重要ではありません。 Int
を持っていてもおよびDouble
であり、それらを合計する場合は、それらを明示的に単一の型に変換する必要があります。
また、オプションは言語の基本的な部分であり、単純な概念ですが、慣れるまでに少し時間がかかります。
最初は、すべてを強制的にアンラップすることをお勧めしますが、最終的にはクラッシュにつながります。言語に精通するにつれて、コンパイル時に多くの間違いが検出されるため、ランタイムエラーがほとんど発生しないことが好きになります。
最も スイフトプログラマー Objective-Cでの重要な経験があります。これにより、他の言語で慣れ親しんでいるのと同じ方法を使用してSwiftコードを作成できる可能性があります。そして、それはいくつかの悪い間違いを引き起こす可能性があります。
この記事では、Swift開発者が犯す最も一般的な間違いとそれを回避する方法について概説します。
間違いありません-Objective-CのベストプラクティスはSwiftのベストプラクティスではありません。 つぶやき 1.オプションの強制アンラップ
オプションの型の変数(例:String?
)は、値を保持する場合と保持しない場合があります。値を持たない場合は、nil
に等しくなります。オプションの値を取得するには、最初に次のことを行う必要があります アンラップ それら、そしてそれは2つの異なる方法で作ることができます。
1つの方法は、if let
を使用したオプションのバインディングです。またはguard let
、つまり:
var optionalString: String? //... if let s = optionalString { // if optionalString is not nil, the test evaluates to // true and s now contains the value of optionalString } else { // otherwise optionalString is nil and the if condition evaluates to false }
2つ目は、!
を使用して強制的にアンラップすることです。演算子、または暗黙的にアンラップされたオプションの型(String!
など)を使用します。オプションがnil
の場合、アンラップを強制するとランタイムエラーが発生し、アプリケーションが終了します。さらに、暗黙的にラップされていないオプションの値にアクセスしようとすると、同じことが発生します。
クラス/構造体初期化子で初期化できない(または初期化したくない)変数がある場合があります。したがって、それらをオプションとして宣言する必要があります。場合によっては、nil
ではないと想定しますコードの特定の部分では、それらを強制的にアンラップするか、暗黙的にアンラップされたオプションとして宣言します。これは、常にオプションのバインドを行うよりも簡単だからです。これは注意して行う必要があります。
これは、ペン先またはストーリーボード内のオブジェクトを参照する変数であるIBOutlet
sの操作に似ています。親オブジェクトの初期化(通常はViewControllerまたはカスタムUIView
)では初期化されませんが、nil
ではないことは確かです。いつviewDidLoad
(ビューコントローラ内)またはawakeFromNib
(ビューで)が呼び出されるので、安全にアクセスできます。
一般に、ベストプラクティスは、アンラップを強制したり、暗黙的にアンラップされたオプションを使用したりしないことです。オプションはnil
である可能性があることを常に考慮してくださいオプションのバインディングを使用するか、そうでないかどうかを確認して、適切に処理しますnil
アンラップを強制する前、または暗黙的にアンラップされたオプションの場合は変数にアクセスする前。
2.強い参照サイクルの落とし穴を知らない
強力な参照サイクルは、オブジェクトのペアが相互に強力な参照を維持する場合に存在します。 Objective-Cにも同じ問題があり、経験豊富なObjective-C開発者がこれを適切に管理することが期待されているため、これはSwiftにとって新しいことではありません。強い参照と何が何を参照しているかに注意を払うことが重要です。 Swiftのドキュメントには このトピック専用のセクション 。
クロージャを使用するときは、参照を管理することが特に重要です。デフォルトでは、クロージャ(またはブロック)は、クロージャ内で参照されるすべてのオブジェクトへの強力な参照を保持します。これらのオブジェクトのいずれかがクロージャー自体を強く参照している場合、強い参照サイクルがあります。利用する必要があります キャプチャリスト 参照のキャプチャ方法を適切に管理します。
ブロックが呼び出される前に、ブロックによってキャプチャされたインスタンスの割り当てが解除される可能性がある場合は、それをとしてキャプチャする必要があります。 弱い参照 、nil
になる可能性があるため、オプションになります。これで、キャプチャされたインスタンスがブロックの存続期間中に割り当て解除されないことが確実な場合は、それを次のようにキャプチャできます。 所有されていない参照 。 unowned
を使用する利点weak
の代わりに参照はオプションではなく、値をアンラップせずに直接使用できるということです。
Xcode Playgroundで実行できる次の例では、Container
クラスには配列と、配列が変更されるたびに呼び出されるオプションのクロージャがあります( プロパティオブザーバー そうするには)。 Whatever
クラスにはContainer
がありますインスタンス、およびその初期化子で、クロージャをarrayDidChange
に割り当てます。このクロージャはself
を参照するため、Whatever
の間に強い関係が作成されます。インスタンスとクロージャ。
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この例を実行すると、deinit whatever
に気付くでしょう。印刷されることはありません。つまり、インスタンスw
メモリから割り当てが解除されません。これを修正するには、キャプチャリストを使用してself
をキャプチャしないようにする必要があります。強く:
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { [unowned self] array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この場合、unowned
を使用できるため、self
を使用できます。 nil
になることはありません閉鎖の存続期間中。
ほとんどの場合、キャプチャリストを使用して参照サイクルを回避することをお勧めします。これにより、メモリリークが減少し、最終的にはより安全なコードになります。
3. self
を使用するどこにでも
Objective-Cとは異なり、Swiftではself
を使用する必要はありません。メソッド内のクラスまたは構造体のプロパティにアクセスします。 self
をキャプチャする必要があるため、クロージャでのみこれを行う必要があります。 self
を使用する必要のない場所は間違いではなく、問題なく機能し、エラーや警告はありません。しかし、なぜあなたがしなければならないより多くのコードを書くのですか?また、コードの一貫性を保つことも重要です。
4.あなたのタイプのタイプを知らない
Swiftは 値型 そして 参照型 。さらに、値型のインスタンスは、参照型のインスタンスの動作がわずかに異なります。各インスタンスがどのカテゴリに当てはまるかわからないと、コードの動作に誤った期待が生じます。
ほとんどのオブジェクト指向言語では、クラスのインスタンスを作成して他のインスタンスに渡し、メソッドの引数として渡すと、このインスタンスはどこでも同じであることが期待されます。つまり、実際には、まったく同じデータへの参照の集まりにすぎないため、変更はどこにでも反映されます。この動作を示すオブジェクトは参照型であり、Swiftではすべての型がclass
として宣言されています。参照型です。
次に、struct
を使用して宣言される値型があります。またはenum
。値型は、変数に割り当てられるか、関数またはメソッドに引数として渡されるときにコピーされます。コピーしたインスタンスで何かを変更しても、元のインスタンスは変更されません。値型は 不変 。 CGPoint
などの値型のインスタンスのプロパティに新しい値を割り当てる場合またはCGSize
の場合、変更を加えて新しいインスタンスが作成されます。そのため、配列のプロパティオブザーバーを使用して(上記のContainer
クラスの例のように)、変更を通知できます。実際に起こっていることは、変更を加えて新しいアレイが作成されることです。プロパティに割り当てられ、didSet
呼び出されます。
したがって、扱っているオブジェクトが参照型または値型であることがわからない場合は、コードが何をするかについての期待が完全に間違っている可能性があります。
5.列挙型の可能性を最大限に活用していない
列挙型について話すとき、私たちは一般的に基本的なC列挙型について考えます。これは、下にある整数である関連する定数の単なるリストです。 Swiftでは、列挙型ははるかに強力です。たとえば、各列挙ケースに値を付加できます。列挙型には、メソッドと読み取り専用/計算プロパティもあり、各ケースをより多くの情報と詳細で強化するために使用できます。
公式 列挙型に関するドキュメント 非常に直感的で、 エラー処理ドキュメント Swiftでの列挙型の追加パワーのいくつかの使用例を示します。また、以下の広範なをチェックしてください Swiftでの列挙型の探索 あなたがそれらでできるほとんどすべてを学ぶために。
6.機能機能を使用しない
Swift標準ライブラリは、関数型プログラミングの基本であり、次のような1行のコードで多くのことを実行できる多くのメソッドを提供します。 地図 、 減らす 、および フィルタ 、とりわけ。
いくつかの例を見てみましょう。
たとえば、テーブルビューの高さを計算する必要があります。あなたがUITableViewCell
を持っているとすると次のようなサブクラス:
class CustomCell: UITableViewCell { // Sets up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:) func configureWithModel(model: Model) // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:) class func heightForModel(model: Model) -> CGFloat }
モデルインスタンスの配列があるとしますmodelArray
; 1行のコードでテーブルビューの高さを計算できます。
let tableHeight = modelArray.map { CustomCell.heightForModel($0) }.reduce(0, combine: +)
map
各セルの高さを含むCGFloat
の配列とreduce
を出力します。それらを合計します。
配列から要素を削除したい場合は、次のことを行うことになります。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for s in supercars { if !isSupercar(s), let i = supercars.indexOf(s) { supercars.removeAtIndex(i) } }
この例は、indexOf
と呼んでいるため、エレガントでも効率的でもありません。アイテムごとに。次の例を考えてみましょう。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for (i, s) in supercars.enumerate().reverse() { // reverse to remove from end to beginning if !isSupercar(s) { supercars.removeAtIndex(i) } }
これで、コードはより効率的になりましたが、filter
を使用することでさらに改善できます。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } supercars = supercars.filter(isSupercar)
次の例は、UIView
のすべてのサブビューを削除する方法を示しています。特定の長方形と交差するフレームなど、特定の基準を満たすもの。次のようなものを使用できます。
for v in view.subviews { if CGRectIntersectsRect(v.frame, rect) { v.removeFromSuperview() } } ``` We can do that in one line using `filter` ``` view.subviews.filter { CGRectIntersectsRect($0.frame, rect) }.forEach { $0.removeFromSuperview() }
ただし、これらのメソッドへのいくつかの呼び出しを連鎖させて、凝ったフィルタリングと変換を作成したくなる可能性があるため、注意が必要です。これにより、1行の読み取り不可能なスパゲッティコードが生成される可能性があります。
7.快適ゾーンにとどまり、プロトコル指向プログラミングを試さない
Swiftが最初であると主張されています プロトコル指向プログラミング言語 、WWDCで言及されているように Swiftでのプロトコル指向プログラミング セッション。基本的に、これは、プロトコルに準拠して拡張するだけで、プロトコルを中心にプログラムをモデル化し、型に動作を追加できることを意味します。たとえば、Shape
があるとします。プロトコル、拡張できますCollectionType
(Array
、Set
、Dictionary
などのタイプに準拠しています)、交差点を考慮した総面積を計算するメソッドを追加します
protocol Shape { var area: Float { get } func intersect(shape: Shape) -> Shape? } extension CollectionType where Generator.Element: Shape { func totalArea() -> Float { let area = self.reduce(0) { (a: Float, e: Shape) -> Float in return a + e.area } return area - intersectionArea() } func intersectionArea() -> Float { /*___*/ } }
ステートメントwhere Generator.Element: Shape
拡張機能のメソッドは、CollectionType
に準拠する型の要素を含むShape
に準拠する型のインスタンスでのみ使用可能になることを示す制約です。たとえば、これらのメソッドはArray
のインスタンスで呼び出すことができますが、Array
のインスタンスでは呼び出すことができません。クラスがある場合Polygon
Shape
に準拠していますプロトコルの場合、これらのメソッドはArray
のインスタンスで使用できます。同じように。
プロトコル拡張を使用すると、プロトコルで宣言されたメソッドにデフォルトの実装を与えることができます。これにより、それらのタイプ(クラス、構造体、または列挙型)に変更を加えることなく、そのプロトコルに準拠するすべてのタイプで使用できるようになります。これは、Swift標準ライブラリ全体で広く行われています。 map
およびreduce
はCollectionType
の拡張で定義され、この同じ実装はArray
などのタイプで共有されます。およびDictionary
余分なコードなし。
この動作はに似ています ミックスイン RubyやPythonなどの他の言語から。デフォルトのメソッド実装を備えたプロトコルに準拠するだけで、タイプに機能を追加できます。
プロトコル指向プログラミングは、一見すると非常に扱いにくく、あまり役に立たないように見えるかもしれません。そのため、それを無視したり、試してみたりすることすらできない可能性があります。 この郵便受け 実際のアプリケーションでのプロトコル指向プログラミングの使用をよく理解できます。
私たちが学んだように、Swiftはおもちゃの言語ではありません
Swiftは当初、多くの懐疑論を持って受け入れられました。人々は、AppleがObjective-Cを子供向けのおもちゃの言語またはプログラマー以外の人向けの言語に置き換えるつもりだと考えているようでした。ただし、Swiftは、プログラミングを非常に快適にする真面目で強力な言語であることが証明されています。強く型付けされているため、間違いを犯しにくく、そのため、言語で犯す可能性のある間違いをリストすることは困難です。
Swiftに慣れてObjective-Cに戻ると、違いに気付くでしょう。 Swiftが提供する優れた機能を見逃し、同じ効果を実現するには、Objective-Cで面倒なコードを作成する必要があります。また、コンパイル中にSwiftがキャッチしたであろうランタイムエラーに直面することもあります。これはAppleプログラマーにとって素晴らしいアップグレードであり、言語が成熟するにつれて、まだまだたくさんのことがあります。
関連: iOS開発者ガイド:Objective-CからLearningSwiftまで
.frame, rect) }.forEach { ほとんどのSwift開発者が犯していることを知らない間違い
Objective-Cのバックグラウンドから来て、最初は、Swiftが私を抑制しているように感じました。 Swiftは、強く型付けされた性質のため、私が進歩することを許可していませんでした。
Objective-Cとは異なり、Swiftはコンパイル時に多くの要件を適用します。 id
など、Objective-Cでリラックスできるものタイプと暗黙の変換は、Swiftでは重要ではありません。 Int
を持っていてもおよびDouble
であり、それらを合計する場合は、それらを明示的に単一の型に変換する必要があります。
また、オプションは言語の基本的な部分であり、単純な概念ですが、慣れるまでに少し時間がかかります。
最初は、すべてを強制的にアンラップすることをお勧めしますが、最終的にはクラッシュにつながります。言語に精通するにつれて、コンパイル時に多くの間違いが検出されるため、ランタイムエラーがほとんど発生しないことが好きになります。
最も スイフトプログラマー Objective-Cでの重要な経験があります。これにより、他の言語で慣れ親しんでいるのと同じ方法を使用してSwiftコードを作成できる可能性があります。そして、それはいくつかの悪い間違いを引き起こす可能性があります。
この記事では、Swift開発者が犯す最も一般的な間違いとそれを回避する方法について概説します。
間違いありません-Objective-CのベストプラクティスはSwiftのベストプラクティスではありません。 つぶやき 1.オプションの強制アンラップ
オプションの型の変数(例:String?
)は、値を保持する場合と保持しない場合があります。値を持たない場合は、nil
に等しくなります。オプションの値を取得するには、最初に次のことを行う必要があります アンラップ それら、そしてそれは2つの異なる方法で作ることができます。
1つの方法は、if let
を使用したオプションのバインディングです。またはguard let
、つまり:
var optionalString: String? //... if let s = optionalString { // if optionalString is not nil, the test evaluates to // true and s now contains the value of optionalString } else { // otherwise optionalString is nil and the if condition evaluates to false }
2つ目は、!
を使用して強制的にアンラップすることです。演算子、または暗黙的にアンラップされたオプションの型(String!
など)を使用します。オプションがnil
の場合、アンラップを強制するとランタイムエラーが発生し、アプリケーションが終了します。さらに、暗黙的にラップされていないオプションの値にアクセスしようとすると、同じことが発生します。
クラス/構造体初期化子で初期化できない(または初期化したくない)変数がある場合があります。したがって、それらをオプションとして宣言する必要があります。場合によっては、nil
ではないと想定しますコードの特定の部分では、それらを強制的にアンラップするか、暗黙的にアンラップされたオプションとして宣言します。これは、常にオプションのバインドを行うよりも簡単だからです。これは注意して行う必要があります。
これは、ペン先またはストーリーボード内のオブジェクトを参照する変数であるIBOutlet
sの操作に似ています。親オブジェクトの初期化(通常はViewControllerまたはカスタムUIView
)では初期化されませんが、nil
ではないことは確かです。いつviewDidLoad
(ビューコントローラ内)またはawakeFromNib
(ビューで)が呼び出されるので、安全にアクセスできます。
一般に、ベストプラクティスは、アンラップを強制したり、暗黙的にアンラップされたオプションを使用したりしないことです。オプションはnil
である可能性があることを常に考慮してくださいオプションのバインディングを使用するか、そうでないかどうかを確認して、適切に処理しますnil
アンラップを強制する前、または暗黙的にアンラップされたオプションの場合は変数にアクセスする前。
2.強い参照サイクルの落とし穴を知らない
強力な参照サイクルは、オブジェクトのペアが相互に強力な参照を維持する場合に存在します。 Objective-Cにも同じ問題があり、経験豊富なObjective-C開発者がこれを適切に管理することが期待されているため、これはSwiftにとって新しいことではありません。強い参照と何が何を参照しているかに注意を払うことが重要です。 Swiftのドキュメントには このトピック専用のセクション 。
クロージャを使用するときは、参照を管理することが特に重要です。デフォルトでは、クロージャ(またはブロック)は、クロージャ内で参照されるすべてのオブジェクトへの強力な参照を保持します。これらのオブジェクトのいずれかがクロージャー自体を強く参照している場合、強い参照サイクルがあります。利用する必要があります キャプチャリスト 参照のキャプチャ方法を適切に管理します。
ブロックが呼び出される前に、ブロックによってキャプチャされたインスタンスの割り当てが解除される可能性がある場合は、それをとしてキャプチャする必要があります。 弱い参照 、nil
になる可能性があるため、オプションになります。これで、キャプチャされたインスタンスがブロックの存続期間中に割り当て解除されないことが確実な場合は、それを次のようにキャプチャできます。 所有されていない参照 。 unowned
を使用する利点weak
の代わりに参照はオプションではなく、値をアンラップせずに直接使用できるということです。
Xcode Playgroundで実行できる次の例では、Container
クラスには配列と、配列が変更されるたびに呼び出されるオプションのクロージャがあります( プロパティオブザーバー そうするには)。 Whatever
クラスにはContainer
がありますインスタンス、およびその初期化子で、クロージャをarrayDidChange
に割り当てます。このクロージャはself
を参照するため、Whatever
の間に強い関係が作成されます。インスタンスとクロージャ。
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この例を実行すると、deinit whatever
に気付くでしょう。印刷されることはありません。つまり、インスタンスw
メモリから割り当てが解除されません。これを修正するには、キャプチャリストを使用してself
をキャプチャしないようにする必要があります。強く:
struct Container { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container init() { container = Container() container.arrayDidChange = { [unowned self] array in self.f(array) } } deinit { print('deinit whatever') } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil
この場合、unowned
を使用できるため、self
を使用できます。 nil
になることはありません閉鎖の存続期間中。
ほとんどの場合、キャプチャリストを使用して参照サイクルを回避することをお勧めします。これにより、メモリリークが減少し、最終的にはより安全なコードになります。
3. self
を使用するどこにでも
Objective-Cとは異なり、Swiftではself
を使用する必要はありません。メソッド内のクラスまたは構造体のプロパティにアクセスします。 self
をキャプチャする必要があるため、クロージャでのみこれを行う必要があります。 self
を使用する必要のない場所は間違いではなく、問題なく機能し、エラーや警告はありません。しかし、なぜあなたがしなければならないより多くのコードを書くのですか?また、コードの一貫性を保つことも重要です。
4.あなたのタイプのタイプを知らない
Swiftは 値型 そして 参照型 。さらに、値型のインスタンスは、参照型のインスタンスの動作がわずかに異なります。各インスタンスがどのカテゴリに当てはまるかわからないと、コードの動作に誤った期待が生じます。
ほとんどのオブジェクト指向言語では、クラスのインスタンスを作成して他のインスタンスに渡し、メソッドの引数として渡すと、このインスタンスはどこでも同じであることが期待されます。つまり、実際には、まったく同じデータへの参照の集まりにすぎないため、変更はどこにでも反映されます。この動作を示すオブジェクトは参照型であり、Swiftではすべての型がclass
として宣言されています。参照型です。
次に、struct
を使用して宣言される値型があります。またはenum
。値型は、変数に割り当てられるか、関数またはメソッドに引数として渡されるときにコピーされます。コピーしたインスタンスで何かを変更しても、元のインスタンスは変更されません。値型は 不変 。 CGPoint
などの値型のインスタンスのプロパティに新しい値を割り当てる場合またはCGSize
の場合、変更を加えて新しいインスタンスが作成されます。そのため、配列のプロパティオブザーバーを使用して(上記のContainer
クラスの例のように)、変更を通知できます。実際に起こっていることは、変更を加えて新しいアレイが作成されることです。プロパティに割り当てられ、didSet
呼び出されます。
したがって、扱っているオブジェクトが参照型または値型であることがわからない場合は、コードが何をするかについての期待が完全に間違っている可能性があります。
5.列挙型の可能性を最大限に活用していない
列挙型について話すとき、私たちは一般的に基本的なC列挙型について考えます。これは、下にある整数である関連する定数の単なるリストです。 Swiftでは、列挙型ははるかに強力です。たとえば、各列挙ケースに値を付加できます。列挙型には、メソッドと読み取り専用/計算プロパティもあり、各ケースをより多くの情報と詳細で強化するために使用できます。
公式 列挙型に関するドキュメント 非常に直感的で、 エラー処理ドキュメント Swiftでの列挙型の追加パワーのいくつかの使用例を示します。また、以下の広範なをチェックしてください Swiftでの列挙型の探索 あなたがそれらでできるほとんどすべてを学ぶために。
6.機能機能を使用しない
Swift標準ライブラリは、関数型プログラミングの基本であり、次のような1行のコードで多くのことを実行できる多くのメソッドを提供します。 地図 、 減らす 、および フィルタ 、とりわけ。
いくつかの例を見てみましょう。
たとえば、テーブルビューの高さを計算する必要があります。あなたがUITableViewCell
を持っているとすると次のようなサブクラス:
class CustomCell: UITableViewCell { // Sets up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:) func configureWithModel(model: Model) // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:) class func heightForModel(model: Model) -> CGFloat }
モデルインスタンスの配列があるとしますmodelArray
; 1行のコードでテーブルビューの高さを計算できます。
let tableHeight = modelArray.map { CustomCell.heightForModel($0) }.reduce(0, combine: +)
map
各セルの高さを含むCGFloat
の配列とreduce
を出力します。それらを合計します。
配列から要素を削除したい場合は、次のことを行うことになります。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for s in supercars { if !isSupercar(s), let i = supercars.indexOf(s) { supercars.removeAtIndex(i) } }
この例は、indexOf
と呼んでいるため、エレガントでも効率的でもありません。アイテムごとに。次の例を考えてみましょう。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for (i, s) in supercars.enumerate().reverse() { // reverse to remove from end to beginning if !isSupercar(s) { supercars.removeAtIndex(i) } }
これで、コードはより効率的になりましたが、filter
を使用することでさらに改善できます。
var supercars = ['Lamborghini', 'Bugatti', 'AMG', 'Alfa Romeo', 'Koenigsegg', 'Porsche', 'Ferrari', 'McLaren', 'Abarth', 'Morgan', 'Caterham', 'Rolls Royce', 'Audi'] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } supercars = supercars.filter(isSupercar)
次の例は、UIView
のすべてのサブビューを削除する方法を示しています。特定の長方形と交差するフレームなど、特定の基準を満たすもの。次のようなものを使用できます。
for v in view.subviews { if CGRectIntersectsRect(v.frame, rect) { v.removeFromSuperview() } } ``` We can do that in one line using `filter` ``` view.subviews.filter { CGRectIntersectsRect($0.frame, rect) }.forEach { $0.removeFromSuperview() }
ただし、これらのメソッドへのいくつかの呼び出しを連鎖させて、凝ったフィルタリングと変換を作成したくなる可能性があるため、注意が必要です。これにより、1行の読み取り不可能なスパゲッティコードが生成される可能性があります。
7.快適ゾーンにとどまり、プロトコル指向プログラミングを試さない
Swiftが最初であると主張されています プロトコル指向プログラミング言語 、WWDCで言及されているように Swiftでのプロトコル指向プログラミング セッション。基本的に、これは、プロトコルに準拠して拡張するだけで、プロトコルを中心にプログラムをモデル化し、型に動作を追加できることを意味します。たとえば、Shape
があるとします。プロトコル、拡張できますCollectionType
(Array
、Set
、Dictionary
などのタイプに準拠しています)、交差点を考慮した総面積を計算するメソッドを追加します
protocol Shape { var area: Float { get } func intersect(shape: Shape) -> Shape? } extension CollectionType where Generator.Element: Shape { func totalArea() -> Float { let area = self.reduce(0) { (a: Float, e: Shape) -> Float in return a + e.area } return area - intersectionArea() } func intersectionArea() -> Float { /*___*/ } }
ステートメントwhere Generator.Element: Shape
拡張機能のメソッドは、CollectionType
に準拠する型の要素を含むShape
に準拠する型のインスタンスでのみ使用可能になることを示す制約です。たとえば、これらのメソッドはArray
のインスタンスで呼び出すことができますが、Array
のインスタンスでは呼び出すことができません。クラスがある場合Polygon
Shape
に準拠していますプロトコルの場合、これらのメソッドはArray
のインスタンスで使用できます。同じように。
プロトコル拡張を使用すると、プロトコルで宣言されたメソッドにデフォルトの実装を与えることができます。これにより、それらのタイプ(クラス、構造体、または列挙型)に変更を加えることなく、そのプロトコルに準拠するすべてのタイプで使用できるようになります。これは、Swift標準ライブラリ全体で広く行われています。 map
およびreduce
はCollectionType
の拡張で定義され、この同じ実装はArray
などのタイプで共有されます。およびDictionary
余分なコードなし。
この動作はに似ています ミックスイン RubyやPythonなどの他の言語から。デフォルトのメソッド実装を備えたプロトコルに準拠するだけで、タイプに機能を追加できます。
プロトコル指向プログラミングは、一見すると非常に扱いにくく、あまり役に立たないように見えるかもしれません。そのため、それを無視したり、試してみたりすることすらできない可能性があります。 この郵便受け 実際のアプリケーションでのプロトコル指向プログラミングの使用をよく理解できます。
私たちが学んだように、Swiftはおもちゃの言語ではありません
Swiftは当初、多くの懐疑論を持って受け入れられました。人々は、AppleがObjective-Cを子供向けのおもちゃの言語またはプログラマー以外の人向けの言語に置き換えるつもりだと考えているようでした。ただし、Swiftは、プログラミングを非常に快適にする真面目で強力な言語であることが証明されています。強く型付けされているため、間違いを犯しにくく、そのため、言語で犯す可能性のある間違いをリストすることは困難です。
Swiftに慣れてObjective-Cに戻ると、違いに気付くでしょう。 Swiftが提供する優れた機能を見逃し、同じ効果を実現するには、Objective-Cで面倒なコードを作成する必要があります。また、コンパイル中にSwiftがキャッチしたであろうランタイムエラーに直面することもあります。これはAppleプログラマーにとって素晴らしいアップグレードであり、言語が成熟するにつれて、まだまだたくさんのことがあります。
関連: iOS開発者ガイド:Objective-CからLearningSwiftまで
.removeFromSuperview() }
ただし、これらのメソッドへのいくつかの呼び出しを連鎖させて、凝ったフィルタリングと変換を作成したくなる可能性があるため、注意が必要です。これにより、1行の読み取り不可能なスパゲッティコードが生成される可能性があります。
Swiftが最初であると主張されています プロトコル指向プログラミング言語 、WWDCで言及されているように Swiftでのプロトコル指向プログラミング セッション。基本的に、これは、プロトコルに準拠して拡張するだけで、プロトコルを中心にプログラムをモデル化し、型に動作を追加できることを意味します。たとえば、Shape
があるとします。プロトコル、拡張できますCollectionType
(Array
、Set
、Dictionary
などのタイプに準拠しています)、交差点を考慮した総面積を計算するメソッドを追加します
protocol Shape { var area: Float { get } func intersect(shape: Shape) -> Shape? } extension CollectionType where Generator.Element: Shape { func totalArea() -> Float { let area = self.reduce(0) { (a: Float, e: Shape) -> Float in return a + e.area } return area - intersectionArea() } func intersectionArea() -> Float { /*___*/ } }
ステートメントwhere Generator.Element: Shape
拡張機能のメソッドは、CollectionType
に準拠する型の要素を含むShape
に準拠する型のインスタンスでのみ使用可能になることを示す制約です。たとえば、これらのメソッドはArray
のインスタンスで呼び出すことができますが、Array
のインスタンスでは呼び出すことができません。クラスがある場合Polygon
Shape
に準拠していますプロトコルの場合、これらのメソッドはArray
のインスタンスで使用できます。同じように。
プロトコル拡張を使用すると、プロトコルで宣言されたメソッドにデフォルトの実装を与えることができます。これにより、それらのタイプ(クラス、構造体、または列挙型)に変更を加えることなく、そのプロトコルに準拠するすべてのタイプで使用できるようになります。これは、Swift標準ライブラリ全体で広く行われています。 map
およびreduce
はCollectionType
の拡張で定義され、この同じ実装はArray
などのタイプで共有されます。およびDictionary
余分なコードなし。
この動作はに似ています ミックスイン RubyやPythonなどの他の言語から。デフォルトのメソッド実装を備えたプロトコルに準拠するだけで、タイプに機能を追加できます。
プロトコル指向プログラミングは、一見すると非常に扱いにくく、あまり役に立たないように見えるかもしれません。そのため、それを無視したり、試してみたりすることすらできない可能性があります。 この郵便受け 実際のアプリケーションでのプロトコル指向プログラミングの使用をよく理解できます。
Swiftは当初、多くの懐疑論を持って受け入れられました。人々は、AppleがObjective-Cを子供向けのおもちゃの言語またはプログラマー以外の人向けの言語に置き換えるつもりだと考えているようでした。ただし、Swiftは、プログラミングを非常に快適にする真面目で強力な言語であることが証明されています。強く型付けされているため、間違いを犯しにくく、そのため、言語で犯す可能性のある間違いをリストすることは困難です。
Swiftに慣れてObjective-Cに戻ると、違いに気付くでしょう。 Swiftが提供する優れた機能を見逃し、同じ効果を実現するには、Objective-Cで面倒なコードを作成する必要があります。また、コンパイル中にSwiftがキャッチしたであろうランタイムエラーに直面することもあります。これはAppleプログラマーにとって素晴らしいアップグレードであり、言語が成熟するにつれて、まだまだたくさんのことがあります。
関連: iOS開発者ガイド:Objective-CからLearningSwiftまで