スウィフトの多くの顔

Natasha Murashevによる非常に有用な記事のロシア語翻訳The Swift Functionsの多くの顔



Objective-Cの構文は他のプログラミング言語と比べてやや奇妙ですが、 メソッドの構文は単純で単純です。 ここに過去への短い余談があります:



+ (void)mySimpleMethod { //  "" //   //    } - (NSString *)myMethodNameWithParameter1:(NSString *)param1 parameter2:(NSNumber *)param2 { //  "" //   -  NSString , //   -  NSNumber  //     NSString  return @"hello, world!"; }
      
      







対照的に、 Swift構文はほとんどの場合、他のプログラミング言語と同じように見えますが、 Objective-Cよりも複雑でわかりにくい場合があります。



続行する前に、この記事全体でこれらの用語の両方を使用するため、 Swiftの 「メソッド」「関数」の違いを明確にします。 AppleのSwiftプログラミング言語で指定されている「メソッド」の定義は次のとおりです。







メソッドは、特定の「タイプ」に関連付けられている関数です。 クラス、構造、および列挙は、特定のタイプのインスタンスを操作するための特定の作業と機能をカプセル化するインスタンスメソッドを定義できます。 クラス、構造、および列挙は、「タイプ」に関連付けられた「タイプ」のメソッドを定義することもできます。 「タイプ」メソッドは、Objective-Cの「クラス」メソッドに似ています。



関数は自律的ですがメソッドclass, struct



またはenum



組み込まれた関数です。



Swift関数の構造





シンプルな「Hello、World!」 Swift関数から始めましょう。



 func mySimpleFunction() { println("hello, world!") }
      
      







Objective-C以外の言語でプログラミングしたことがあれば、この機能はおなじみのように思えます。



キーワードfunc



は、それが関数であることを意味します。

この関数の名前はmySimpleFunction



です。

パラメータはこの関数に渡されません—括弧()内は空のためです。

値は返されません。

実行可能な関数コードは、中括弧{}の間にあります。

それでは、より複雑な関数に移りましょう。



 func myFunctionName(param1: String, param2: Int) -> String { return "hello, world!" }
      
      







この関数はparam1



String



型のparam1



という名前のパラメーターとparam2



Int



型のparam2



という名前のパラメーターを取り、 String



型の値を返します。



すべての関数を呼び出す





スウィフト関数と呼ば際スウィフトObjective-Cとの間に有意な違いの1つは方法の設定作業です。 私が好むように、 Objective-Cのおしゃべりが好きなら、 Swift関数を呼び出すときにパラメーター名がデフォルトで含まれないことに注意してください:



 func hello(name: String) { println("hello \(name)") } hello("Mr. Roboto")
      
      







関数にさらにいくつかのパラメーターを追加するまで、それほど悪くはありません。



 func hello(name: String, age: Int, location: String) { println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?") } hello("Mr. Roboto", 5, "San Francisco")
      
      







hello("Mr. Roboto", 5, "San Francisco")



読むには、どのパラメーターが何を意味するかを知る必要があります。

Swiftには、この混乱を明確にするための外部パラメーター名の概念があります。



 func hello(name name: String) { println("hello \(name)") } hello(name: "Robot")
      
      







代わりに、略語のパラメーター名の前に#を追加します。



 func hello(#name: String) { println("hello \(name)") } hello(name: "Robot")
      
      







そして、もちろん、 関数のパラメーターが機能するルールは、 メソッドのルールとは少し異なります ...



メソッド呼び出し





関数がclass



(またはstruct



、またはenum



)に組み込まれている場合、メソッドの最初のパラメーターの名前はexternalとして含まれませんが、メソッドが呼び出されると、後続のパラメーター名はすべて外部として含まれます。



 class MyFunClass { func hello(name: String, age: Int, location: String) { println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?") } } let myFunClass = MyFunClass() myFunClass.hello("Mr. Roboto", age: 5, location: "San Francisco")
      
      







したがって、 Objective-Cのように、メソッドの名前に最初のパラメーターの名前を含めることをお勧めします。



 class MyFunClass { func helloWithName(name: String, age: Int, location: String) { println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?") } } let myFunClass = MyFunClass() myFunClass.helloWithName("Mr. Roboto", age: 5, location: "San Francisco")
      
      







“hello”



関数を呼び出す代わりに、 helloWithName



に名前を変更して、メソッドの最初のパラメーターの名前をより明確にしました。



何らかの理由で関数内の外部パラメーター名をスキップする場合(非常に重要な理由がある場合にのみこれを行うことをお勧めします)、外部パラメーター名に_記号を使用します。



 class MyFunClass { func helloWithName(name: String, _ age: Int, _ location: String) { println("Hello \(name). I live in \(location) too. When is your \(age + 1)th birthday?") } } let myFunClass = MyFunClass() myFunClass.helloWithName("Mr. Roboto", 5, "San Francisco")
      
      







インスタンスメソッドはカリー化された関数です





注目すべき非常に注目すべきことは、インスタンスメソッドが実際Swiftのカリー化された関数であることです。



カリー化の主な考え方は、関数を部分的に適用できることです。つまり、関数が呼び出される前にパラメーター値の一部を決定できるということです。 部分関数アプリケーションは、新しい関数を生成します。


だから私はクラスを持っています:



 class MyHelloWorldClass { func helloWithName(name: String) -> String { return "hello, \(name)" } }
      
      







helloWithName



クラスの関数を指す変数を作成できます。



 let helloWithNameFunc = MyHelloWorldClass.helloWithName // MyHelloWorldClass -> (String) -> String
      
      







私の新しい機能helloWithNameFunc



のタイプの関数であるMyHelloWorldClass -> (String) -> Sting



、私のクラスの「インスタンス」をとり、そして今度は、文字列の値をとり、文字列値を返す、という別の関数を返します。

これで、次のように関数を呼び出すことができます。



 let myHelloWorldClassInstance = MyHelloWorldClass() helloWithNameFunc(myHelloWorldClassInstance)("Mr. Roboto") // hello, Mr. Roboto
      
      







Init



:特記事項





class, struct



、またはenum



が初期化されると、特別なinit



メソッドが呼び出されます。 Swiftでは、他のメソッドと同様に初期化パラメーターを定義できます。



 class Person { init(name: String) { //   init } } Person(name: "Mr. Roboto")
      
      







他のメソッドとは対照的に、 init



メソッドの最初のパラメーターの名前には、クラスのインスタンスを受け取るときに常に外部名が必要です。

クラスインスタンスを読みやすくするために、内部名と外部名(この場合はfromName



以外のパラメーターを追加することをおfromName



ます。



 class Person { init(fromName name: String) { // your init implementation } } Person(fromName: "Mr. Roboto")
      
      







そして、もちろん、他のメソッドと同様に、 init



メソッドでパラメーターの外部名をスキップする場合は、_文字を追加できます。 Swift Programming Language bookの次の初期化例の読みやすさとパワーが気に入っています。



 struct Celsius { var temperatureInCelsius: Double init(fromFahrenheit fahrenheit: Double) { temperatureInCelsius = (fahrenheit - 32.0) / 1.8 } init(fromKelvin kelvin: Double) { temperatureInCelsius = kelvin - 273.15 } init(_ celsius: Double) { temperatureInCelsius = celsius } } let boilingPointOfWater = Celsius(fromFahrenheit: 212.0) // boilingPointOfWater.temperatureInCelsius is 100.0 let freezingPointOfWater = Celsius(fromKelvin: 273.15) // freezingPointOfWater.temperatureInCelsius is 0.0 let bodyTemperature = Celsius(37.0) // bodyTemperature.temperatureInCelsius is 37.0
      
      







外部パラメータ名をスキップすることはclass / enum / struct



初期化される方法を無視したい場合にも役立ちます。 David Owen json-swiftライブラリでこれを使うのが本当に好きです:



 public struct JSValue : Equatable { // ...   ///   `JSValue`  `JSArrayType` . public init(_ value: JSArrayType) { self.value = JSBackingValue.JSArray(value) } ///   `JSValue`  `JSObjectType` . public init(_ value: JSObjectType) { self.value = JSBackingValue.JSObject(value) } ///   `JSValue`  `JSStringType` . public init(_ value: JSStringType) { self.value = JSBackingValue.JSString(value) } ///   `JSValue`  `JSNumberType` . public init(_ value: JSNumberType) { self.value = JSBackingValue.JSNumber(value) } ///  `JSValue`  `JSBoolType` . public init(_ value: JSBoolType) { self.value = JSBackingValue.JSBool(value) } ///  `JSValue`  `Error` . init(_ error: Error) { self.value = JSBackingValue.Invalid(error) } ///  `JSValue`  `JSBackingValue` . init(_ value: JSBackingValue) { self.value = value } }
      
      







「特別な」パラメーター





Objective-Cと比較して、 Swiftには関数/メソッドに渡すことができるパラメーターに関する追加オプションがあります。 以下に例を示します。



オプションのパラメーター





Swiftは、 Optional



型の新しい概念を導入します。



オプションは、「この値とxに等しい」または「値がまったくない」と言います。オプションは、Objective-Cのポインターでnilを使用するのと似ていますが、クラスだけでなくすべてのタイプで機能します。 オプションは、Objective-Cのnilポインターよりも安全で表現力があり、Swiftの多くの強力な機能の中心にあります。


このパラメータがあることを示すためにOptional



(すなわち、かもしれnil



)、単に疑問符を追加しますか? 型指定後:



 func myFuncWithOptionalType(parameter: String?) { // function execution } myFuncWithOptionalType("someString") myFuncWithOptionalType(nil)
      
      







Optionals



するときは、忘れずに「デプロイ」してください!



 func myFuncWithOptionalType(optionalParameter: String?) { if let unwrappedOptional = optionalParameter { println("The optional has a value! It's \(unwrappedOptional)") } else { println("The optional is nil!") } } myFuncWithOptionalType("someString") // The optional has a value! It's someString myFuncWithOptionalType(nil) // The optional is nil
      
      







Objective-Cを使用している場合は、 Optionals



を使用するときに適応するのに時間がかかります。



Default



値を持つパラメーター





 func hello(name: String = "you") { println("hello, \(name)") } hello(name: "Mr. Roboto") // hello, Mr. Roboto hello() // hello, you
      
      







デフォルト値( default



)を持つパラメーターには、自動的に外部名が付けられることに注意してください。



関数を呼び出すときにdefault



値を持つパラメーターをスキップできるため、 default



値を持つすべてのパラメーターをパラメーターリストの最後に配置することをお勧めします。 このトピックに関するSwiftプログラミング言語の本から引用です:

関数パラメーターリストの最後に、デフォルト値を持つパラメーターを配置します。 これにより、関数のすべての呼び出しで、デフォルト以外の引数に同じ順序のパラメーターが使用されるようになります。 したがって、すべての場合で同じ関数が呼び出されます。


主にdefault



パラメーターを使用するのが大好きです。これは主に、これによりコードの変更と下位互換性の提供が容易になるためです。 たとえば、現時点で必要な2つのパラメーターから開始できます。たとえば、カスタムの "テーブルセル" UITableViewCell



構成したり、別のパラメーターが必要なときに追加の構成が必要な場合(たとえば、ラベルテキストの異なる色セル)、デフォルト値で新しいパラメーターを追加するだけです-この関数が既に呼び出されている他のすべての場所は変更せずに残し、新しいパラメーターを必要とするコードの新しい部分にカップルを転送できます デフォルト値以外の値を持つEMPL!



可変引数(可変数のパラメーター)





Variadic



は、要素の配列を渡すより読みやすいバージョンです。 実際、以下の例で内部パラメーターname



のタイプを見ると、それがタイプ[String]であることがわかります。



 func helloWithNames(names: String...) { for name in names { println("Hello, \(name)") } } // 2 names helloWithNames("Mr. Robot", "Mr. Potato") // Hello, Mr. Robot // Hello, Mr. Potato // 4 names helloWithNames("Batman", "Superman", "Wonder Woman", "Catwoman") // Hello, Batman // Hello, Superman // Hello, Wonder Woman // Hello, Catwoman
      
      







空の配列に対応する0の値を渡すことができるので、必要に応じて空の配列が渡されるかどうかを確認することを忘れないでください。



 func helloWithNames(names: String...) { if names.count > 0 { for name in names { println("Hello, \(name)") } } else { println("Nobody here!") } } helloWithNames() // Nobody here!
      
      







variadic



パラメータに関する別の注意: variadic



パラメータは、パラメータリストの最後のパラメータでなければなりません!



入力パラメータ





inout



パラメーターを使用すると、参照渡しの外部変数を操作できます。



 var name1 = "Mr. Potato" var name2 = "Mr. Roboto" func nameSwap(inout name1: String, inout name2: String) { let oldName1 = name1 name1 = name2 name2 = oldName1 } nameSwap(&name1, &name2) name1 // Mr. Roboto name2 // Mr. Potato
      
      







これは、 Objective-Cのエラー管理スクリプトの非常に一般的なパターンです。



NSJSONSerialization



はそのような例の1つです。



 - (void)parseJSONData:(NSData *)jsonData { NSError *error = nil; id jsonResult = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (!jsonResult) { NSLog(@"ERROR: %@", error.description); } }
      
      







Swiftはまだ非常に若いプログラミング言語であるため、エラー処理の明確な規則はありませんが、入力パラメーター以外にも多くの方法があります。 最近のSwift David Owenエラー処理に関するブログ投稿をご覧ください 。 本「Functional Programming in Swift」のこのトピックに関する多くの資料。



汎用オプション





このジェネリック投稿ではあまり注意を払いませんが、異なるタイプのパラメーターを受け入れる関数の非常に簡単な例を示しますが、両方のパラメーターは同じタイプです。



 func valueSwap<T>(inout value1: T, inout value2: T) { let oldValue1 = value1 value1 = value2 value2 = oldValue1 } var name1 = "Mr. Potato" var name2 = "Mr. Roboto" valueSwap(&name1, &name2) name1 // Mr. Roboto name2 // Mr. Potato var number1 = 2 var number2 = 5 valueSwap(&number1, &number2) number1 // 5 number2 // 2
      
      







generics



詳細については、 AppleのSwift Programming Languageのジェネリックセクションをご覧になることをお勧めします。



パラメーター-変数





デフォルトでは、関数に渡されるパラメーターは定数であるため、関数のスコープ内で操作することはできません。 この動作を変更する場合は、 varキーワードを使用します。



 var name = "Mr. Roboto" func appendNumbersToName(var name: String, #maxNumber: Int) -> String { for i in 0..<maxNumber { name += String(i + 1) } return name } appendNumbersToName(name, maxNumber:5) // Mr. Robot12345 name // Mr. Roboto
      
      







これはinout



パラメーターとは完全に異なることに注意してください-変数パラメーターは外部から渡された変数を変更しません!



パラメータとして機能





スウィフトの機能は通常の変数として渡すことができます。 たとえば、関数には渡されたパラメーターとして別の関数がある場合があります。



 func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String { let luckyNumber = Int(arc4random() % 100) return lotteryHandler(name, luckyNumber) } func defaultLotteryHandler(name: String, luckyNumber: Int) -> String { return "\(name), your lucky number is \(luckyNumber)" } luckyNumberForName("Mr. Roboto", lotteryHandler: defaultLotteryHandler) // Mr. Roboto, your lucky number is 38
      
      







関数参照のみがパラメーターとして渡されることに注意してください。この例では、 defaultLotteryHandler



関数です。 関数は、このパラメーターが渡される関数を決定するときに、後で実行されます。



インスタンスメソッドは同じ方法で渡すことができます。



 func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String { let luckyNumber = Int(arc4random() % 100) return lotteryHandler(name, luckyNumber) } class FunLottery { func defaultLotteryHandler(name: String, luckyNumber: Int) -> String { return "\(name), your lucky number is \(luckyNumber)" } } let funLottery = FunLottery() luckyNumberForName("Mr. Roboto", lotteryHandler: funLottery.defaultLotteryHandler) // Mr. Roboto, your lucky number is 38
      
      







関数定義をもう少し読みやすくするために、関数の型エイリアスを作成することを検討してください( Objective-Cの typedef



に似ていtypedef



):



 typealias lotteryOutputHandler = (String, Int) -> String func luckyNumberForName(name: String, #lotteryHandler: lotteryOutputHandler) -> String { let luckyNumber = Int(arc4random() % 100) return lotteryHandler(name, luckyNumber) }
      
      







パラメータータイプとして名前のない関数( Objective-Cのブロックなど)もあります。



 func luckyNumberForName(name: String, #lotteryHandler: (String, Int) -> String) -> String { let luckyNumber = Int(arc4random() % 100) return lotteryHandler(name, luckyNumber) } luckyNumberForName("Mr. Roboto", lotteryHandler: {name, number in return "\(name)'s' lucky number is \(number)" }) // Mr. Roboto's lucky number is 74
      
      







Objective-Cでは、ブロックをパラメーターとして使用することは、非同期操作のあるメソッドでcompletion handler



error handler



を制御するために非常に一般的です。 Swiftでも人気があります。



アクセス制御





Swiftには3つのレベルのアクセス制御があります。



パブリックアクセスにより、これらのエンティティが定義されているモジュールのソースコードファイル内のエンティティ、およびこれらのエンティティが定義されているモジュールをインポートする別のモジュールのソースコードファイル内のエンティティを使用できます。 通常、フレームワークのパブリックインターフェイスを指定するときは、 パブリックアクセスを使用します。

内部アクセスにより、これらのエンティティが定義されているモジュールのソースファイル内のエンティティを使用できますが、それらが定義されているモジュール外のソースコードファイルでは使用できません。 通常、アプリケーションまたはフレームワークの内部構造を定義するときに内部アクセスを使用します。

プライベートアクセスは、エンティティの使用を、そのエンティティが定義されているソースファイルに制限します。 プライベートアクセスを使用すると、特定の機能の実装の詳細が隠されます。

デフォルトでは、各関数と変数は内部です。これを変更する場合は、個々のメソッドまたは変数の前にprivateまたはpublicキーワードを使用する必要があります。



 public func myPublicFunc() { } func myInternalFunc() { } private func myPrivateFunc() { } private func myOtherPrivateFunc() { }
      
      







Rubyを使用する場合、 プライベート関数をクラスの一番下に配置し、マーカーで区切ります:



 class MyFunClass { func myInternalFunc() { } // MARK: Private Helper Methods private func myPrivateFunc() { } private func myOtherPrivateFunc() { } }
      
      







Swiftの将来のリリースには、他のプログラミング言語でのアクセス制御の動作と同様に、1つのプライベートキーワードを使用して、以下のすべてのメソッドをprivateとして示すオプションが含まれることを期待しています。



「特別な」戻り型





Swiftでは、関数によって返される型と値は、 Objective-Cで使用するよりも少し複雑になる可能性があります。特に、 Optionals



と複数の戻り値型が導入されています。



オプションの戻り型





関数がnil



返す可能性がある場合、戻り値の型をOptional



として定義する必要があります。



 func myFuncWithOptonalReturnType() -> String? { let someNumber = arc4random() % 100 if someNumber > 50 { return "someString" } else { return nil } } myFuncWithOptonalReturnType()
      
      







もちろん、 Optional



戻り値を使用する場合は、忘れずに「展開」してください。



 let optionalString = myFuncWithOptonalReturnType() if let someString = optionalString { println("The function returned a value: \(someString)") } else { println("The function returned nil") }
      
      







私が見た最高のオプションの説明は、 @ Kronusdark twitterから取ったものです。

シュレディンガー猫に似た@SwiftLangオプションがついに手に入りました! 猫を使用する前に、生きているかどうかを確認する必要があります。




多くの戻り値





Swiftの最も印象的な機能の1つは、多くの戻り値を持つ機能です。



 func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int) { var min = numbers[0] var max = numbers[0] for number in numbers { if number > max { max = number } if number < min { min = number } } return (min, max) } findRangeFromNumbers(1, 234, 555, 345, 423) // (1, 555)
      
      







ご覧のとおり、戻り値のセットは、グループ化された値の非常に単純な構造であるタプルとして返されます。 タプルで複数の戻り値を使用するには、2つの方法があります。



 let range = findRangeFromNumbers(1, 234, 555, 345, 423) println("From numbers: 1, 234, 555, 345, 423. The min is \(range.min). The max is \(range.max).") // From numbers: 1, 234, 555, 345, 423. The min is 1. The max is 555. let (min, max) = findRangeFromNumbers(236, 8, 38, 937, 328) println("From numbers: 236, 8, 38, 937, 328. The min is \(min). The max is \(max)") // From numbers: 236, 8, 38, 937, 328. The min is 8. The max is 937
      
      







多くの戻り値とオプション





返される値がOptional



場合、複数の値を返すときに混乱が発生しますが、この状況を管理するには2つの方法があります。



上記の機能に関して、私のロジックは失火します。入力に何も送信されない場合、プログラムは異常終了します。

値が関数の入力に渡されない場合、戻り値を完全にオプションにすることができます:



  func findRangeFromNumbers(numbers: Int...) -> (min: Int, max: Int)? { if numbers.count > 0 { var min = numbers[0] var max = numbers[0] for number in numbers { if number > max { max = number } if number < min { min = number } } return (min, max) } else { return nil } } if let range = findRangeFromNumbers() { println("Max: \(range.max). Min: \(range.min)") } else { println("No numbers!") } // No numbers!
      
      







他の場合では、タプル全体をOptional



にするのではなく、タプル内の個々の値をOptional



にすることが理にかなっています。



 func componentsFromUrlString(urlString: String) -> (host: String?, path: String?) { let url = NSURL(string: urlString) return (url.host, url.path) }
      
      







タプルの値の一部がであると判断した場合、値のOptional



各組み合わせを個別に考慮する必要があるため、それらを「拡張」するのが少し難しくなりOptional



ます。



 let urlComponents = componentsFromUrlString("http://name.com/12345;param?foo=1&baa=2#fragment") switch (urlComponents.host, urlComponents.path) { case let (.Some(host), .Some(path)): println("This url consists of host \(host) and path \(path)") case let (.Some(host), .None): println("This url only has a host \(host)") case let (.None, .Some(path)): println("This url only has path \(path). Make sure to add a host!") case let (.None, .None): println("This is not a url!") } // This url consists of host name.com and path /12345
      
      







ほらObjective-Cほど簡単ではありません



関数の戻り





スウィフト任意の関数は、関数を返すことができます。



 func myFuncThatReturnsAFunc() -> (Int) -> String { return { number in return "The lucky number is \(number)" } } let returnedFunction = myFuncThatReturnsAFunc() returnedFunction(5) // The lucky number is 5
      
      







読みやすくするために、返す関数の型エイリアスを使用できます。



 typealias returnedFunctionType = (Int) -> String func myFuncThatReturnsAFunc() -> returnedFunctionType { return { number in return "The lucky number is \(number)" } } let returnedFunction = myFuncThatReturnsAFunc() returnedFunction(5) // The lucky number is 5
      
      







入れ子関数





この投稿ではこのトピックを作成しませんが、Swiftでは関数内に関数がある可能性があることを知っておくと便利です



 func myFunctionWithNumber(someNumber: Int) { func increment(var someNumber: Int) -> Int { return someNumber + 10 } let incrementedNumber = increment(someNumber) println("The incremented number is \(incrementedNumber)") } myFunctionWithNumber(5) // The incremented number is 15
      
      







終わり

Swift . Swift , : . , !



Swift , , . , , Swif t, Swift .



Swift !



All Articles