20200211のSwiftに関する記事は6件です。

Swiftで高階関数(Higher Order Function)を活用してスッキリ&カッコ良いコードを書く

はじめに

Swiftでプログラミングをしていても、UI周り部分の実装がメインとなり、GenericsやHigher Order Functionをあまり活用できていないように思います。
他の言語扱う中でも、必ずと言っていいほど、GenericsやHigher Order Functionは出てきますし、どうせなら、カッコ良いコード書きたいので、
Swift言語を使って復習していきます。

名前リストを挨拶リストに変換

let nameList: [String] = ["Yamada", "Tanaka", "Nakano"]

名前リストから、 敬称挨拶をつけたリストを作り出すには、 map を使ってシンプルに書くと、

let morningMrList = nameList.map { "Mr. \($0) Good morning!" }

のようになります。map を使っているので、十分スッキリ書けていると思います。

ただ、敬称を Ms. に変更したい、 挨拶を、Good evening! に変更したい場合に

let morningMsList = nameList.map { "Ms. \($0) Good morning!" }
let eveningMrList = nameList.map { "Mr. \($0) Good evening!" }

ベタ書きため再利用性がない事に気づきます。

この問題を解決するために、最初に考えられるのは、map処理の中で 敬称挨拶 を分離する。

let morningMrList2 = nameList.map { "Mr. \($0)" }.map {"\($0) Good morning!"}

処理が別れたので再利用性が高くなりそうです。

次に、名前リスト毎に文字列加工処理(敬称+挨拶)をmapでベタ書きするのは再利用できないので、
名前リストと文字列加工処理をパラメータとして受け取る関数を用意します。

関数をパラメータとして受け取る高階関数となります。

func _map<String>(_ list: [String], f: (String) -> String) -> [String] {
    return list.map { f($0) }
}

// 名前リストを挨拶リストに変換
func _mrMorningF (_ list: [String]) -> [String] {
    return _map(list) { "Mr. \($0) Good morning!" }
}
let morningMrList3 = _mrMorningF(nameList)

ただ、このコードはベタ書きであり、スッキリしません。

以下では戻り値が関数の高階関数を使って改善していきます。

戻り値が関数の高階関数を使って改善

  • 敬称、挨拶の文字列追加を分離
  • 文字列加工の関数化

この2つが実現出来れば、名前リストから挨拶リストを生成するのに、毎回ベタ書きしなくてよくなります。

上のmapでは、戻りの型が配列でしたが、戻り値を関数に書き換えます。戻り値の型に束縛されないのでGenericsで書くことができます。

func map<A, B>(_ f: @escaping (A) -> B) -> ([A]) -> [B] {
    return { $0.map(f) }
}

戻り値は、引数に任意の型の配列(A)を受け取り、任意の型の配列(B)を返す関数となります。
Genericsを使っているので、汎用的な関数を用意出来たと思います。

アロー( -> ) が連続すると、とたんに可読性が落ちますが、代わり玄人感はすごく増しますね。

このmapを使うと以下のような、敬称文字列追加、挨拶文字列追加を定義できます。

// 名前に、敬称のMrを追加する関数
let mrF: ([String]) -> [String] = map{ "Mr. \($0)" }

// 名前に、挨拶を追加する関数
let morningF: ([String]) -> [String] = map{ "\($0) Good morning!" }

nameList, nameList2のように別のリストでも、引数を変えるだけで済みます。

let morningMrList4 = morningF(mrF(nameList))
let morningMrList5 = morningF(mrF(name2List))

他の挨拶の関数も同様に書けます。

let msF: ([String]) -> [String] = map{ "Ms. \($0)" }
let afternoonF: ([String]) -> [String] = map{ "\($0) Good afternoon!" }
let eveningF: ([String]) -> [String] = map{ "\($0) Good evening!" }

これらを組み合わせれば名前から挨拶リストを作り出せるので、再利用可能です。

関数ネストを改善したい

関数がネストしているので少し見にくい気がします。

morningF(mrF(nameList))

これを解決するには、関数を合成する関数を用意すれば良いでしょう。本格的な高階関数ですね。

2つの関数を引数を受け取り、これらの関数を合成して関数を返却しています。

func compose<A, B, C>(_ f: @escaping((A) -> B), _ g: @escaping((B) -> C)) -> (A) -> C {
    return { a in g(f(a)) }
}

型に縛られない関数なので、Genericsで書くことが出来ます。

これを使うと、

let msMorningF = compose(msF, morningF)

使う側も関数がネストしていないので、スッキリ読みやすいです!

let morningMsList2 = msMorningF(name2List)

まとめ

毎回、同じ処理を書く場合は、高階関数(特に、戻り値を関数)にすることを検討すると再利用性が高いコードにすることが
出来るケースがあることが理解できました。

Swiftの高階関数の記事は、 map flatMap reduceなどの使い方を説明に閉じた記事が多いですが、
自身で高階関数を用意して使うことが本当の活用な気がします。

そして、Generics +高階関数の組み合わせは非常に強力ですね。

この記事にあたり、Point Free で勉強させてもらいましたが、海外サイトは良質な記事が多いので、英語頑張りたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftの記号あれこれ

背景

Swiftの公式ドキュメント、ヘルプガイド、ブログのコードサンプルなどによく出てくる実装のなかで、どんな意味があるのかすぐに判断できない記述がありました。
メソッドの引数が顔文字化していたり(例えば、max(:_:_)とか)、変数定義の型の最後にに?!が付いていたりと、あれ?これはどういう意味だったかなというときに自分自身がすぐに思い出すとき用の記事があるといいなぁと思い記事にしました。

対象とする読者

Swift初心者

環境

私がこの記事を書いている際に利用しているのは次の環境です。
Xcode 11.3.1
Swift 5.0

Swiftの記号あれこれ

関数の引数に出現する_ (アンダースコア)

Swiftでは関数の引数にラベルをあらかじめ付けておき、利用者にそれを明確に指定させることができるが、名前をわざわざ指定しなくても明らかにわかる場合は、その仕組みだとコードが読みにくくなってしまいます。
なので、関数呼び出しするときの引数が、名前なしで渡せますよということを明示するもの。

// 引数に名前付きで呼び出してもらう関数の書き方
func add(x: Int, y: Int) -> Int {
    return x + y
}
var r1 = add(x: 3, y: 2)  // 5: OK
var r2 = add(3, 2)        // NG(引数に名前がないのでコンパイルエラー)


// 引数に名前なしで呼び出してもらう関数の書き方
func addByNoLabel(_ x: Int, _ y: Int) -> Int { 
    return x + y
}
var r3 = add(4, 7)        // 11: 引数に名前なしでOK

関数のシグネチャーで、
func(_:)とかfunc(_:_:)とかの表現を見かけたときは、_:のところに、名前なしで引数を入れれば利用できるんだな、ぐらいで見ておけばよいです。

型のうしろに?(はてな)

?
通称:はてなマーク
正式名:クエスチョンマーク

値がオプショナル型であることを示す場合に利用します。オプショナル型は、指定した型の値もしくは値そのものがないnilのいずれかを表す型です。
Optional<Wrapped> (WrappedにはInt,Stringなど実際の型が入る)が入りますが、コードが冗長となるのを避けるためのシュガーシンタックスとして用意されています。

// 正式な構文だと
var age: Optional<Int>

// 簡潔に書くと
var age: Int? = 21

// 利用時には、アンラップが必要(強制アンラップした場合)
var nextAge :Int = age! + 1   // 22: オプショナルの値に!を付けて強制アンラップ

変数等の後ろに?が出現した場合は、この変数は空っぽの状態(nil)もあり得るということと、Optionalを解除(Wrap包み紙から出してあげるイメージ)しないと、元の型としては利用できないという認識を持っておけばよいです。

型のうしろに!(びっくり)

!
通称:びっくりマーク
正式名:エクスクラメーションマーク

こちらもオプショナル型ですが、?とは異なり!で定義した値にアクセスする際には自動的に強制アンラップして元の型にしてくれます。なので「暗黙的にアンラップされたOptional<Wrapped>型」と呼ばれています。
型としては、Optional<Wrapped>型だけど、利用するときには通常のWrapped型として利用可能。
注意点としては、値にアクセスしたときにnilだった場合は、実行時エラーとなってしまいます。

var age: Int! = 21

//アクセス時に自動的に強制アンラップしてくれる
var nextAge: Int = age + 1   // 22

範囲演算子(終了を含まない)

a..<b
aから始まり、終了bを含まない範囲。

var range 1..<4   // CountableRange(1..<4) 

for value in range {
    print(value)
}

実行結果
1
2
3

範囲演算子(終了を含む)

a...b
aから始まり、終了bを含む範囲。

var range 1...4   // CountableClosedRange(1...4) 

for value in range {
    print(value)
}

実行結果
1
2
3
4

さいごに

慣れない言語でのコーディングは不安です。
でも、知ってしまえば、慣れてしまえばそれほど難しくないことは多々あります。
私も今回のこの記事を自分で整理してアウトプットすることで、頭の中を整理できました。
私のようなSwift初心者の方に、この記事が少しでも役立てば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】SequenceとIteratorProtocolでfor-inですべてのデータを取り出すサンプル

SequenceとIteratorProtocolを使ってfor-inですべてのデータを取り出す書き方のサンプルです。

SequenceとIteratorProtocolのサンプル
class T {
    var n = 1;
    init(_ n: Int) {
        self.n = n
    }
}

class C: Sequence, IteratorProtocol {
    typealias Element = T
    var ta = [T](repeating: T(1), count: 100)
    var i = 0

    func next() -> T? {
        guard i < ta.count else {
            i = 0
            return nil
        }
        let r = ta[i]
        i += 1
        return r
    }
}

var c = C()
for e in c {
    print(e.n)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swifterを使って画像を投稿する

はじめに

SwiftでTwitterAPIを使って画像を投稿したかったのですがあまり記事がなかったので投稿します。
Swifterというライブラリを使っています。ドキュメントには画像を投稿することについては書いていなかったのですがコード読んでたらそれらしきものがあったのでそれを使いました。
https://github.com/mattdonnelly/Swifter

今回やること

  1. SwifterでaAuth認証する。
  2. 画像をツイッターに投稿する。

OAuth認証する。

これに関しては記事を別に書いているのでそちらを見てください。

https://qiita.com/daichi77/items/88fcffaf11585033fdd4

画像を投稿する。

let image = UIImage(hogehoge)//投稿する画像を入れる。
let data = image.pngData()//data型に変換
//aouth認証で入手したやつを入れる。
let swifter = Swifter(consumerKey: 取得したkey,
                      consumerSecret: 取得したkey,
                      oauthToken: 取得したkey,
                      oauthTokenSecret: 取得したkey)
//ツイート。statusには文をmediaには画像をdata型に変換したものを入れる
swifter.postTweet(status: "test", media: data!,success: { json in
  print(json)//成功時
}, failure: {error in
  print(error)//失敗時
})

こんな感じでツイートできました。
文章のみ投稿するpostTweetはドキュメントに載っていましたが画像投稿も同じ感じでツイートできました。
スクリーンショット 2020-02-11 11.36.34.png

補足(413 payload too large errorが帰ってくる場合 )

自分の場合iPhoneのフォルダーから画像を読み取ってそれをアプリに保存した後に
再度アプリから保存した画像を読み取りツイッターに投稿という手順だったのでこの手順のどこかで画像が大きくなってしまったのかもしれません。
もしかしたら同じエラーが帰ってくる人がいるかもしれないので解決方法を載せておきます。

let image = UIImage(hogehoge)//投稿する画像
//大きさをresizeする
let width = image.size.width * 0.1
let height = image.size.height * 0.1
UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 1.0)
image.draw(in: CGRect(x: 0, y: 0, width: width, height: height))
let resizeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
//大きさをresizeした後にdata型に変換
image.pngData()

こんな感じで画像の大きさをresizeしてあげるとうまくtweetできました。0.1倍じゃなくていいかもしれませんがいい感じの大きさにしてツイートしてあげてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでAtCoder攻略するノウハウを色々書いていく記事

半年ぐらい離れていたんですが、最近またAtCoderをやっています。
Swift使いとしては、AtCoderのSwift2縛りがキツいんですが、ぼちぼちコンパイラアップデートしてくれそうです。

競プロ真剣にやるんなら、C++が圧倒的有利なんですが、
今の僕の状況的にC++まで手出せないので、Swiftで戦います。

この前気付いたんですが、AtCoderにSwiftで参戦している人口はめちゃくちゃ少ないです。

この記事はアップデートしていく自分用ノートみたいにしたいかなと思っています。
Swift5->swift2への逆引き(?)みたいな記事ってないので、案外困るんですよね。。。

やっていく中で詰まったら順次追記していきます。
後AtCoder側がSwift5にアップデートされたらこの記事自体要らなくなると思います。

テンプレート

swift2です。

標準入力
// 1
let N = Int(readLine()!)!

// 1 2
let split = readLine()!.componentsSeparatedByString(" ")
let N = Int(split[0])!
let S = Int(split[1])!

// 1 2 3 4 5
let array = readLine()!.componentsSeparatedByString(" ").map { Int($0)! }
便利extension
extension Array where Element: Equatable {
    mutating func remove(value: Element) {
        if let i = self.indexOf(value) {
            self.removeAtIndex(i)
        }
    }
}

パクリ元:swiftで配列から値を指定して削除する

sort
var array = [1, 2, 3, 4, 5]
array = array.sort { $0 > $1 } // 降順
文字列のcount
let S = "aaaaa"
print(S.characters.count) //5
配列の最大値/最小値
array.maxElement()!
array.minElement()!

エディタ問題

playgroundだと標準入力が受け取れなくて、
readline部分を書き換えて、変数で受け取っちゃうとか、やりようはあるんですが、まあちょっとめんどくさいですよね。

最近気付いたんですが、コマンドラインツールとしてプロジェクト作成すれば、
標準入力を受け取るプログラムとしてSwiftが書けて、入力補完が効くので嬉しいので、これでやるのが一番いいですね。
AtCoder側がSwift5に対応してくれた後なら。

一昨日これで参戦したら、やっぱいちいちSwift2向けに書き換えるオーバーヘッドがしんどくて、
結局いつの間にかAtCoder上のコードテストでデバッグしている僕がいました。

現状ブラウザで書いちゃうのが一番いいかなあという感じです。

関連記事

AtCoderにSwiftで挑戦するときの標準入出力

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIのTextのfontとfontWeight一覧

概要

文字の大きさや太さを一覧で視覚的に残しておきたかったので書きました。
当たり前っちゃ当たり前の内容ですが、パッと見比べる時に有用であればいいなと思います。

font

文字の大きさを指定します。
ドキュメントなど参照しましたが、デフォルト値を見つけることができなかったのですが、
見た感じは.bodyかと思います。

Text("largeTitle").font(.largeTitle)
Text("title").font(.title)
Text("headline").font(.headline)
Text("subheadline").font(.subheadline)
Text("body").font(.body)
Text("callout").font(.callout)
Text("caption").font(.caption)
Text("footnote").font(.footnote)

スクリーンショット 2020-02-11 8.02.57.png

fontWeight

文字の太さを指定します。
こちらもデフォルトは見つからなかったですが、見た感じは``。

Text("black").fontWeight(.black)
Text("bold").fontWeight(.bold)
Text("heavy").fontWeight(.heavy)
Text("light").fontWeight(.light)
Text("medium").fontWeight(.medium)
Text("regular").fontWeight(.regular)
Text("semibold").fontWeight(.semibold)
Text("thin").fontWeight(.thin)
Text("ultraLight").fontWeight(.ultraLight)

スクリーンショット 2020-02-11 8.01.03.png

組み合わせ

組み合わせるとこのような感じに。

Text("largeTitle & black").font(.largeTitle).fontWeight(.black)
Text("footnote & ultraLight").font(.footnote).fontWeight(.ultraLight)

スクリーンショット 2020-02-11 8.06.34.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む