Objective-CからSwiftまで。 推奨事項

SwiftはAppleの新しいプログラミング言語で、今年WWDCで発表しました。 プログラミング言語に加えて、AppleはSwift言語に関する優れたリファレンスをリリースしました。これを読むか読むことをお勧めします。 しかし、本を読むのは非常に長い時間です! 時間があまりなく、新しいSwift言語について学びたいだけの場合は、この記事が役立ちます。



この記事では、Objective-CからSwiftへの移行についての考えを共有したいと思います。 いくつかのヒントを提供し、両方の言語に対する異なるアプローチの欠点を指摘しようとします。 したがって、不必要な余談なく、記事自体に目を向けます。



単一ファイルとインターフェース実装ファイル



言及する価値のある最初の、最も重要な変更は、interface.h / implementation.m構造の放棄です。



私はこのモデルの支持者であることを認めなければなりません。 インターフェイスファイルを使用してクラス情報を簡単に取得および共有することは、安全で高速です。



Swiftでは、インターフェースと実装は2つのファイルに分割されていません。 クラスを実装するだけです(これを書いている時点では、可視性修飾子を追加することさえできません)。



この変更に対処することが本当に難しい場合は、次を使用できます。常識。



適切なドキュメントを使用して、クラスの読みやすさを簡単に高めることができます。 たとえば、「公開」したい要素を拡張子を使用してファイルの先頭に移動すると、誰でもアクセスできるデータと個人情報を区別できます。

別の非常に一般的なトリックは、プライベートメソッドとプライベート変数にアンダースコア「_」を付けることです。



それらを混合する小さな例を次に示します。



// 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{ // ... } }
      
      







クラスの要素の可視性を変更することはできませんが、「より困難な」ものへのアクセスを試みることはできます。

非標準のソリューションは、個人データを部分的に隠すネストされたクラスを使用することです(少なくともオートコンプリートで)



例:



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







プライベートな実装をプライベートな永続的なケースに入れ、クラス内の「通常の」実装をパブリックインターフェイスとして使用します。 プライベート要素は実際には隠されていませんが、それらにアクセスするには「プライベート」定数を通過する必要があります。



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







疑問が生じます:それは究極の目標、個人的な要素の部分的な隠蔽に値しますか?

私の提案は、可視性修飾子を待つことです(Appleは現在取り組んでいます)が、現時点では、拡張機能の有無にかかわらず適切なドキュメントを使用してください。



定数と変数



Objective-Cでは、一部のデータが変更されないことがわかっていても、定数キーワードを使用することはほとんどありませんでした。 Swiftでは、Apple開発者は変数(var)の代わりに定数(let)を使用することを提案しています。 彼女に注目して、変数の役割を理解してください。 最終的には、予想よりも多くの定数を使用します。



書くのに必要なものだけを書く



2行のコードを見て、違いを見つけます。



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







Swiftを使用した最初の2週間で、コードの各行からセミコロンを削除するように強制しました。 簡単になりました(Objective-Cでどのような感じになるかはもう忘れていました)。



型推論により、変数に型を割り当てて、その定義から直接派生させることができます。 これはもう1つの利点であり、詳細なObjective-C言語を使用することで得られるものであるため、習得するのが少し困難です。



そうしないと、別の開発者(およびあなた)が、失敗した命名の選択によって推測されるタイプを判別することが困難になります。



 let a = something()
      
      







より適切な名前を使用すると、作業が簡単になります。



 let a = anInt()
      
      







次の大きな変更点は、括弧を使用することです。括弧はもう必要ありません。



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







括弧内に記述したものは式として評価され、常にこの方法で記録できるとは限らないことに注意してください。 たとえば、変数をバインドする場合、括弧を使用できません。



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







インターフェイスを選択したり、セミコロンと角かっこを削除したりする必要はありませんが、これらのオプションはSwiftでコードを記述するための1つの方法と考えることができます。 最終的に、読みやすさを向上させ、入力と文字の時間を節約します。



オプショナル



「値」または「無」を返す関数を使用する場合、「無」を定義する最良の方法は何だと思ったことがありますか? NSNotFound、-1、0、カスタム戻り値を使用しました。



Optionalsのおかげで、完全に定義された "nothing-value"があります。データ型の後に疑問符を追加するだけです。



私たちは書くことができます:



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







この例では、「男が車を所有している」という関係が「オプション」として定義されています。 これは、「car」プロパティがゼロになる可能性があり、人が車を所有できないことを意味します。



次に、追加のバインディング(carを(let)にする場合)または詳細なフレーズ(car?)を使用して、この値を使用します。 プロパティをオプションとして定義しない場合、このプロパティの値を設定する必要があります。そうしないと、初期化関数がエラーをスローします。

追加のプロパティ値を定義する最後の機会は、初期化関数内です。



したがって、クラスのプロパティがクラスの残りの部分とどのように相互作用するか、およびクラスのインスタンスが存在するときにそれらがどのように動作するかを決定する必要があります。



これらの機能強化により、クラスの表示方法が完全に変わります。



追加の開梱



オプションを使用するのが難しいと感じる場合は、コンパイラが使用する前に拡張値を指定するように求める理由を理解できないため...



 Mark.car?
      
      







...オプションを構造としてさらに考えることをお勧めします(構造なので、それほど難しくはないはずです)。このオプションは、値を直接含まず、その周りにレイヤーを追加します(エンベロープ、フレームラップ)。 内部値が決定されると、レイヤーを削除し(unwrap-unwrap)、目的の値を取得します。それ以外の場合はゼロになります。



「!」記号による強制的な展開は、内部サイズを気にせずにレイヤーを削除する方法にすぎません。 レイヤーの背後にある値にアクセスしようとするリスクがあります。 この値がゼロの場合、アプリケーションはクラッシュします。



委任テンプレート



Objective-CとCocoaでのプログラミングの数年後、委任パターンに依存しています。 ただし、このスキームは引き続き使用します。 以下は、非常に単純なデリゲートの使用例です。



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







デリゲートチェックを置き換え、respondToSelectorを追加のチェーンで使用します。



 delegate?.DataWillRead?()
      
      







@optionalを使用したため、@ objキーワードを使用してプロトコルを割り当てる必要があることに注意してください。 ちなみに、コンパイラーはこれを行うのを忘れた場合にメッセージで警告します。



このデリゲートを実装するには、Protocolを別のクラスに実装し、Objective-Cと同様に割り当てます。



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







プログラミングテンプレート-ターゲットアクション



Swiftで使用するもう1つの一般的な方法は、インタラクティブ要素(target-action)です。この場合、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){...} }
      
      







わずかな違いは、セグメントアドレス(セレクター)の決定方法です。 次のように自動的に変更される行を使用して、プロトタイプメソッドを記述するだけです。



 Selector("buttonPressed:")
      
      







シングルトンプログラミングテンプレート



好きでも嫌いでも、シングルトンは依然として最も受け入れられているプログラミングモデルの1つです。



気に入っても気に入らなくても、Singletonパターンは最も適切なパターンの1つです。 GDCとdispatch_onceを使用して実装するか、letキーワードのスレッドセーフな性質に依存します。



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







このコードを見てみましょう:

1. SharedReaderは静的コンポーネントです(関数で置き換えることができます)。

2.静的(非コンポーネント)プロパティは、クラス実装ではまだ許可されていません。 したがって、ネストされた型のおかげで、ネストされた構造をクラスに追加します。 この構造は静的プロパティをサポートしているため、ここに静的プロパティを追加するだけです。

3. _instanceプロパティは定数です。 別の値に置き換えることはできず、スレッドセーフです。



DataReaderの単一のインスタンスにアクセスするには、次のようにします。



DataReader.sharedReader



構造とコンピューティング



Swiftでは、構造と計算には他の言語ではほとんど適用できない多くの特性があります。



彼らはサポートしています:



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







ご覧のとおり、構造は初期化関数を使用します。この場合、Swiftによって自動的に作成されます(クライアントに他の入力パラメーターを追加できます)。



enum構文は、使用したものとは少し異なります。 キーワードcaseで定義されます:



 enum Fruit { case orange case apple }
      
      







列挙型はそのプロパティに限定されません:



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







より複雑な特性を持つ列挙型を作成することもできます。



 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
      
      







前のコードでは、ネストされた型(ビタミン)と追加のプロパティ(mainVitamin)を追加しました。これは、enumの値に応じてこの構造の要素の初期値を割り当てます。



可変および不変



Objective-Cでは、あらゆるクラスの不変および可変バージョンに慣れています。 NSArrayおよびNSDictionaryの例があります。



Swiftでは、さまざまなタイプのデータは必要ありません。新しい方法で定数または変数値を使用するだけです。



変数配列は可変ですが、配列定数では保存された値を変更できません。 そのため、「let = immutable、var = variable」というルールを念頭に置いてください(バグ修正:Beta 3より前は、不変配列を変更できます)。



ブロックvsクロージャー



私はブロックの構文が好きです、それはとても明確で覚えやすいです!



 </IronicMode>
      
      







ところで、Cocoaの数年間の開発の後、私たちはこの構文に慣れました。時々、私は軽い委任タスクをブロックに置き換えることを好みます。 これらは意味があり、高速で、適切に適用されます。



Swiftでは、クロージャー要素は同様のブロックです。 それらには多くの特性があり、Appleはそれらの記述方法を単純化しようとして素晴らしい仕事をしました。 Swiftの公式ドキュメントの例は言葉になりません。 この定義で始まります:



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







再設計:



 reversed = sort(names, >)
      
      







したがって、型推論、略語($ 0、$ 1)、および直接関数(>)により、クロージャーを実装するさまざまな方法があります。



この記事では、閉じた式の構文については説明しませんが、閉じた式内のデータコレクションの値についていくつか説明します。



Objective-Cでは、ブロックを介して値を変更する場合、変数を__blockとして定義します。 この場合、クロージャーの使用は不要になります。



周辺地域のあらゆる価値にアクセスして変更できます。 実際、閉じた式は、外部要素をキャプチャするのに十分インテリジェントです。 アイテムはコピーまたはリンクとして入力されます。 クロージャが要素の値を変更する場合、リンクを作成し、変更しない場合、コピーを作成します。



クロージャがそれを含むまたは使用するエントリを参照する場合、循環のサイクルが発生する場合があります。



例を見てみましょう:



 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
      
      







現在のインスタンスへの参照を維持しながら、閉じたagePotion式が使用されます。 同時に、このインスタンスには閉じるリンクが含まれています-そして、ここで循環のサイクルがあります。



この問題を回避するには、キャプチャリストを使用します。 このリストは、クロージングで使用するインスタンスに弱いリンクを関連付けます。 構文は非常に単純です-クロージング定義の前に弱いリンクを追加すると、インスタンスは強いリンクではなく弱いリンクを取得します。



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







非所有リンクと弱いリンク



Objective-Cで弱参照がどのように機能するかはすでにわかっています。 また、Swiftでも機能し、変更はありません。



このキーワードの導入は、クラス間の関係を判断するための良いヒントであるため、本当に感謝しています。



人と彼の銀行口座との間の単純な関係について説明します。



1.個人は銀行口座を持っている場合があります(オプション)

2.銀行口座は個人に属している必要があります(必須)



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







これらの関係は、サイクルを作成しようとしています。 最初の解決策は、「Account.owner Bank」プロパティに弱い参照を追加することです。 ただし、弱参照の助けを借りて、もう1つの有用な制限を定義します。プロパティには常に値が必要です。ゼロに等しくすることはできません(したがって、前のリストの項目2を満たします)。



実際、弱いリンクについて言うことはこれ以上ありません。 指すケースを増やすことなく、弱いリンクのように機能し、ゼロ以外の値を提供します。



おわりに



私は認めなければなりません:時々私はまだコンパイラの間違いに取り組んでいます。 Swiftを使用すればするほど、実験と勉強に時間を費やしていないことが明確になります。 Objective-Cに比べて、以前には存在しなかった、多くの興味深い変更や事柄があります。それらは、私がもっと練習するように動機付けます。



これは、IOS / OSXの開発における待望のノベルティであり、きっとあなたはそれを愛することでしょう!



ps翻訳は、Habréで最も正確で最高の翻訳であるふりをしません。コメントがあれば、個人で書いて、編集します。 ご理解いただきありがとうございます。



All Articles