default eye-catch image.

Swift超入門_関数とクロージャ的な何か

仮引数と省略 swift初心者(おっさん)が一見して理解できなかったのが関数の仮引数の前にあるアンダースコア(_)。 swift全般的にアンダースコアが言語仕様に含まれているので、いろいろ調べてみた。 単に、関数を呼び出す際にキーワード引数の名称を省略するための記法だった。 仮引数の前にアンダースコア(_)を置くと、呼び出し時にキーワード無しで引数を呼べる。 func hoge(_ int: Int) { print(int) } hoge(100) func hoge2(int: Int) { print(int) } hoge2(int: 200) Swiftは、仮引数と実引数を違う名前で定義できる。 以下、param2というキーワードを使ってhoge3を呼び出しているが、 hoge3の中では仮引数param2をparam3として使っている。 一般に、param3をparam1と関係がある名称にすることで冗長にさせない工夫。 func hoge3(param1: Int, param2 param3: Int) { print(\"(param1),(param3)\") } hoge3(param1: 100, param2: 200) デフォルト引数は一般的。 func hoge4(param1: Int, param2 param3: Int = 200) { print(\"(param1),(param3)\") } hoge4(param1: 100) 関数の定義名に特徴がある。hoge4の定義名は以下のようになる。 ObjectiveCっぽい。 hoge4(param1:param2) インアウト引数 いわゆる参照渡し。 仮引数にinout予約語をつけておくと、仮引数の操作が実引数に反映される。 呼び出す際に、引数に&をつける。 func hoge5(param1: Int, param2: inout Int) { param2 = 300 print(param1, param2) } var val1 = 100 var val2 = 200 hoge5(param1: val1, param2: &val2) // 100 200 print(val2) // 300 可変長引数 可変長引数はスリードット(...)を指定し、配列(Array)で受ける。 func hoge6(params: String...) { for param in params { print (\"element is (param)\") } } hoge6(params: \"hoge1\",\"hoge2\",\"hoge3\") クロージャ さて、Swiftのクロージャ。まぁ何回か使ったら慣れるだろうぐらい分かりづらくはない。 前置詞のinがクロージャブロックの開始になってるのは意図があるんだろうか。 let double = { (x:Int) -> Int in return x * 2 } let v = double(2) クロージャの引数の型と戻り値の型は型推論が効く。 var closure: (Int) -> Int closure = { (x:Int)-> Int in return x*x } closuer(2) // 4 var closure2 = { x in return x*2 } closure2(3) // 6 クロージャの簡易引数名 クロージャの引数名すら書きなくない場合もある。 前置詞inではなく、一般的なブラケットでクロージャブロックを書く。 クロージャブロックからは、n番目のパラメタを$nとして受け取る。 let add: (Int,Int)->Int = { return $0+$1 } add(2,3) // 5 クロージャによる変数束縛 クロージャの実行時にクロージャ呼び出し元の環境を利用できる。 文脈によって呼び出し元の環境は異なるので、実行時に環境のスナップショットを取るイメージ。 Swiftでは\"キャプチャ\"という名前で呼んでいるらしい。 例えば以下。クロージャ add:(Int,Int)-> Int 内で呼び出し元の変数pを使っている。 クロージャ呼び出し時に呼び出し元の環境に依存してpの値が決まる。 なお、pが初期化済みでない状態でクロージャを定義するとエラーになる。 クロージャの実行前にキャプチャする変数は初期化されている必要があるらしい。 var p : Int = 200 let add: (Int,Int)->Int = { return $0+$1 + p } p = 200 add(2,3) // 205 p = 300 add(2,3) // 305 クロージャ内でキャプチャした変数を書き換えた場合、キャプチャ元になった変数自体が変わる。 autoclosure属性 引数の遅延評価を行う指令。 論理和を計算する関数を定義しようとしたとき、Bool型の引数を2つ取ってBool型の戻り値を返す 関数を定義するとする。 論理和なので、どちらかが真であれば一方の引数を見なくても結果がわかるのだが、 下記のようにすると、どちらかが真であっても、もう一方の引数を評価して返すことになる。 func or(_ lhs:Bool, _ rhs:Bool) -> Bool { if lhs { return true } else { return rhs } } or(true,false) 引数をクロージャとし、クロージャ内で引数の評価の必要性を判断するようにして、 クロージャを遅延評価(関数内での評価)することで不要な引数の評価を回避するというアイデア。 func or2(_ lhs:Bool, _ rhs:()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or2(lhs(),{return rhs()}) or2の第2引数が複雑になってしまうところを、文法解決で簡単に書けるようにするのが@autoclosure。 引数をクロージャで包む書き方を省略できる。 下記のように書ける。 func or3(_ lhs:Bool, _ rhs: @autoclosure ()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or3(lhs(),rhs()) trailing closure 引数の最後がクロージャである場合、その引数の前で引数のブラケットを閉じて、 その後にクロージャを書くことができる。 例えば以下のような感じ。 1つ目は真面目に引数の最後にクロージャを与えている。 2つ目は関数呼び出しの後にクロージャを書いている。 func execute(parameter:Int, closure: (String)->Void) { closure(\"parameter is (parameter)\") } execute(parameter:1, closure:{ string in print(string) }) execute(parameter:2) { string in print(string) } 関数をクロージャとして扱う 関数をクロージャとして扱うことができる。関数double(_:)は(Int)->Int型。 それをfunctionという定数の初期値に設定している。 functionには型アノテーションがないけれども、 double(_:)が(Int)->Intなので型推論されて(Int)->Intになる。 func double(_ x:Int)->Int { return x*2 } let function = double function(2) 関数の引数としてクロージャを与えるシーンで、 クロージャを関数として用意しておくことで、それぞれの呼び出しでクロージャを定義する必要がなくなる。 下記について、上はmapの引数としてそれぞれクロージャを定義して呼び出している。 同じ処理をするのに2回定義しないといけない。 下は、関数を定義し、mapの引数として関数をクロージャとして与えている。 関数(クロージャ)の定義は1回で良い。 let array1 = [1,2,3] let toArray1b = array1.map{$0*2} let array2 = [4,5,6] let toArray2b = array2.map{$0*2} func double(_ x:Int)->Int { return x*2 } let toArray1a = array1.map(double) let toArray2a = array2.map(double)

default eye-catch image.

Swift4基本構文_制御文的な何か

オプショナルバインディング</h3 Optional型の存在の有無を冗長な書き方にしないためのif-let構文。 if letで指定したOptional型の変数がnilでなければ、構文の中でアンラップ済みの変数を使えるようにする。 ダウンキャストもif letで対応できる。 let oa = Optional(\"a\") if let a = oa { print(\"(a)\") } else { print(\"nil\") } let ob = Optional(\"b\") if let a = oa, let b = ob { print(\"(a),(b)\") } else { print(\"nil\") } let any : Any = 1 if let iany = any as? Int { print(\"(iany)\") } guard,guard-let ある関数のスコープ内で、guardで指定した条件が満たされることを保証する。 関数の先頭で引数に対してguardを定義することで引数の前提条件を書いたりする。 if-letのguard版(guard-let)もある。 func someFunc() { let value = 1 guard value >= 0 else { return } // ここまで来たならvalueが0以上であることを保証する print(\"0以上です。\") } func someFunc2() { let any : Any = 1 guard let iany = any as? Int else { return } print(\"(iany)\") } nil可能な引数を取る加算関数をif,guard両方で書くと以下。 guardで書いた方が見通しがよくなる。 func add(_optionalA:Int?, _optionalB:Int?) -> Int? { let a: Int let b: Int if let unwrappedA = _optionalA { a = unwrappedA } else { return nil } if let unwrappedB = _optionalB { b = unwrappedB } else { return nil } return a + b } func add2(_optionalA:Int?, _optionalB:Int?) -> Int? { guard let a = _optionalA else { return nil } guard let b = _optionalB else { return nil } return a + b } for-in,for-case for-inで要素の全てにアクセスする。for-caseで条件を満たす要素にアクセスする。 foreachで回す先頭で条件でスキップする奴はfor-caseで書ける。 let dict:Dictionary = [\"a\":1,\"b\":2] for (key,value) in dict { print(\"(key):(value)\") } let ary:Array = [1,2,3,4] for case 2...3 in ary { print(\"2以上3以下\") } fallthrough switch文の各caseの終わりでfallthroughを書いたときに初めて後ろのcaseが実行される。 fallthroughを書かないと条件にマッチしたcaseした実行されない。 なので、switch-caseの各caseの末尾にbreakを書く必要はない。 let a = 1 switch a { case 1: print(\"case1\") fallthrough case 2: print(\"case2\") default: print(\"other\") } 遅延実行 関数の評価時ではなく、関数のスコープから抜けたときに実行する文を記述する。 以下、関数評価時にはgは0だが、関数がスコープから抜けたときにdeferが評価されgが1になる。 var g = 0 func someFunc2() -> Int { defer { g += 1 } print(\"(g)\") return g } someFunc2() print(\"(g)\")

default eye-catch image.

Swift4基本構文_型的な何か

Kotlinに超入門してみたので続いてSwiftに超入門してみる。 超入門に使用する書籍は以下。 [改定新版]Swift実践入門-直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus Playgroundsを使って確認しながら各単元の何かを書いてみる。 変数と定数 変数はvar、定数はletで宣言する。Kotlinと同様に宣言時に型情報が決まる必要がある。 つまり、変数(定数)宣言時に型アノテーションを書くか、初期値の型から型推論させるか。 型推論が出来るので、右辺から型が決まる場合には左辺の型定義(型アノテーション)は省略可。 let i: Int = 123 let j = 123 print(type(of:i)) // Int print(type(of:j)) // Int nilは代入不可。使用時までに値を入れている必要あり。 let i: Int = nil // Nil cannot initialize specified type \'Int\' let j: Int j = nil // Nil cannot be assigned to type \'Int\' let v: Int print(v) // Constant \'v\' used before being initialized スコープ 外側の宣言は内側の宣言で使える。逆はできない。特別な予約語は不要。普通。 let globalint: Int = 100 func hoge() { lef localint: Int = 200 print(globalint) // 100 print(localint) // 200 } Bool型,リテラル Bool型のリテラルはtrue,false。 演算子は論理和、論理積、否定のみ。普通。 let a: Bool = true let b: Bool = false print (a && b )// false print (a || b )// true print (!a) // false Int型,リテラル IntはInt8,Int16,Int32,Int64。 32bitプラットフォームでIntはInt32、64bitだとInt64。普通。 Float型,Double型,リテラル 32bitがFloat、64bitがDouble。 Float,Doubleは無限(isInfinite)と非数値(isNaN)という状態をStaticプロパティとして持つ。 let a: Double = 10.0 / 0.0 a.isInfinite // true let b: Double = 0.0 / 0.0 b.isNaN // true 異型間の代入 Swiftでは型が異なる変数同士の代入を暗黙的にできない。イニシャライザをかます。 縮小方向の代入は端数処理が行われる。 let a: Int32 = 100 let b: Int64 = a // Cannot convert value of type \'Int32\' to specified type \'Int64\' let c: Int64 = Int64(a) let x: Float = 1.0 let y: Int = Int(x) // 1 異型間の比較 Swiftでは型が異なる変数同士の比較ができない。イニシャライザをかます。 結構、型の違いにシビアなのか...。 Int32とInt64は比較できるみたい。FloatとDoubleはできない。 let a: Float = 100.0 let b: Double = 100.0 let c = (a == b) // Binary operator \'==\' cannot be applied to operands of type \'Float\' and \'Double\' let d = (a == Float(b)) // true String型,リテラル 文字列リテラルはダブルクォーテーション。エスケープは一般的なやつが使える。 リテラル内での変数展開は()演算子。${}の方がいいのに...。 let x: Int = 100 let v: String = \"hoge(x)fuga\" // hoge100fuga kotlinでも見かけたスリーダブルクォート。これの発祥はPythonらしい。 面白いのがスリーダブルクォート内において変数自体のインデント分が無視されること。 kotlinのTrimよりも強烈。以下、sとrの出力は同じになる。 let s: String = \"\"\" Swift入門 Swift入門 Swift入門 \"\"\" print(s) let r: String = \"\"\" Swift入門 Swift入門 Swift入門 \"\"\" print(r) String.Index StringとCharacterの関係も一般的。String内のCharacterを指す方法が用意されている。 String.startIndex(=0),String.endIndex(=文字数)を使ってCharacterを指す例。 let a: String = \"abcde\" let b: Character = a[a.startIndex] // a let lastindex = a.index(a.endIndex,offsetBy: -1) let c: Character = a[lastindex] // e Int型とString型の変換 イニシャライザを通す。変換できない場合はnilが入る。 let i: Int = 100 let s: String = String(i) let s2: String = \"hoge\" let i2 = Int(s2) // nil 文字列の結合 文字列の結合は+演算子。append(_:)も使える。 let s1: String = \"hoge\" let s2: String = \"fuga\" let s3: String = s1 + s2 let s4: String = s1.append(s2) 配列,配列リテラル 配列はArray。SyntaxSugarとして[Element]という書き方もできる。 配列リテラルは[]。同一型リテラルの配列から型推論可能。 順序数で要素にアクセス可能。 append(_:)で追加、insert(_at:)で挿入、remote(at:)で削除。 let ary1: Array = [1,2,3,4,5] let ary2: [Int] = [1,2,3,4,5] let ary3 = [1,2,3,4,5] // Intの配列 let ary4 = [\"hoge\",\"fuga\"] // Stringの配列 let a = ary1[0] // 1 ary1.append(6) // [1,2,3,4,5,6] ary1.insert(10,at: 1) // [ 1,10,2,3,4,5,6] ary1.remove(at: 0) // [10,2,3,4,5,6] ary1.removeLast() // [10,2,3,4,5] ary1.removeAll() // [] Dictionary型,リテラル Key->Value辞書はDictionary、SyntaxSugerとして[Key:Value]という書き方もできる。 辞書リテラルは[\"key1\":\"value1\",\"key2\":\"value2\"]のように書く。 同一型の辞書から型推論可能。 let dic1 : Dictionary = [\"a\":1,\"b\":2] let dic2 : [String:Int] = [\"a\":1,\"b\":2] let dic3 = [\"a\":1, \"b\":2] Keyは一意でないといけないので、Keyとして使える型には制限がつく。 DictionaryはDictionary。Key型はHashableプロトコル(インターフェース?) に準拠したもの(Keyからハッシュ値を計算できる)のみ使える。 DictionaryへのアクセスはOptional(Value)を返す。つまり無いキーの値はnil。 let dic3 = [\"a\":1, \"b\":2] let a = dic3[\"a\"] // 1 let b = dic3[\"c\"] // nil 更新,追加は同じ。存在するkeyを指定して値を代入すれば更新、存在しないkeyなら追加。 削除はnilを設定する。 var dic4 = [\"a\":1,\"b\":2] dic4[\"b\"] = 3 // [\"a:1\",\"b\":3] dic4[\"c\"] = 4 // [\"a:1\",\"b\":3,\"c\":4] dic4[\"a\"] = nil // [\"b\":3,\"c\":4] 範囲型 Swiftの範囲型は数学的なモデリングになってる。 1つ目は開区間、半開区間、閉区間。2つ目は順序(計数可能)。 数値以外の範囲についてもforループを回して順番に要素にアクセスできたりする。 範囲型が範囲を定義すると、範囲に収まる値が得られるのではなく、 区間と上限下限をもったオブジェクトのまま保持される。 let range1 = 1..<4 // CountableRange(1..<4) : lower bound=1,uppper bound=4 for value in range1 { print(value) // 1 2 3 } let range2 = 1...4 // CountableCloseRange(1..4) : lower bound=1,uppper bound=4 for value in range2 { print(value) // 1 2 3 4 } let range3 : Range = 1..<4 // Range<1..<4) :lower bound=1,upper bound=4 let range4 : Rnage = 1...4 // Range<1..4) : lower bound=1,upper bound=4 Optional型 型がnilを許容するか否か。Wrapped型がnilを許容する場合はOptional、許容しない場合はWrapped。 Suger Syntaxとして、OptionalをWrapped?と書くこともできる。 let ival1: Int = 32 let ival2: Int = nil // Nil cannot initialize specified type \'Int\' let ival3: Optional = 32 let ival4: Optional = nil let ival5: Int? = nil ぬるぽ防止(Unwrap) デフォルトではOptional型同士の四則演算はできない。 明示的なアンラップ(Optional->Wrapped)が必要。 ??演算子を使うと、左辺のOptional(Wrapped)がnilの場合に右辺、 nilでない場合にWrappedをアンラップして返す。 !演算子を使うと強制アンラップ。当然nilならぬるぽ。ぬるぽ時のエラーがドキッとする。 当然、強制アンラップは非推奨。 let ival6: Int? = 100 let ival7: Int? = 200 let ival8 = ival6 + ival7 let ival6: Int? = 100 let value1 = ival6 ?? 3 // 100 let ival7: Int? = nil let value2 = ival7 ?? 3 // 3 let ival8: Int? = 100 let ival9: Int? = 200 let ival10 = ival8! + ival9! // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). ぬるぽ防止(Optional chain) Optionalの値を使うために必ずアンラップしないといけないとなると面倒。 ?.演算子を使うと、nil、非nilの場合を1行に含めることができる。 左辺がnilである場合はnil、nilでない場合はOptional(Wrapped)を返す。 ?.を数珠つなぎに書いていくと、最初のnilで評価が止まってそれ以降のnil参照を行わない。(なのでchain) let optionalDouble1 = Optional(1.0) // Optional(1.0) let optionalInfinite1 = optionalDouble1?.isInfinite // Optional(false) let optionalDouble2 :Double? = nil let optionalInfinite2 = optionalDouble2?.isInfinite // nil 強制アンラップ構文 ぬるぽを恐れるあまり、nil可能なケースが面倒になっているのを和らげるためか、 何か迷いのようなものを感じるけれども、OptionalをWrapped?ではなくWrapped!と書くと、 型としてOptionalを維持したまま、暗黙的に強制アンラップしてから計算が行われる。 当然、nilが入っていればぬるぽで止まる。 let intval: Int? = 100 print(intval + 200) // Value of optional type \'Int?\' must be unwrapped to a value of type \'Int\' let intval: Int! = 100 print(intval + 200) // 300 let intval2: Int! = nil print(intval2 + 200) // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). アップキャスト 継承関係やプロトコル(インターフェース?)準拠により上位型の存在が確実な場合、 アップキャスト可能。アップキャストはas演算子。 確実な上位関係でない型へのアップキャストは実行時エラー。 アップキャストは暗黙的に実行可能。 let str : String = \"hogehoge\" let any : Any = str as Any let int : Int = str as Int // Cannot convert value of type \'String\' to type \'Int\' in coercion let any2 : Any = str ダウンキャスト 上位型から下位型へのキャスト。失敗のリスクがある。 失敗のリスクをOptionalで解決するのがas?演算子、リスクをケアしないのがas!演算子。 as?によるダウンキャストはOptionalを返す。as!はWrappedを返す。 as!が失敗した場合、実行時エラーが発生する。 let any = 1 as Any let int = any as? Int // Optional(1) let any2 = 1 as Any let int2 = any2 as! Int // 1