- 投稿日:2019-05-03T22:31:34+09:00
Swiftで幽霊型を実装する
概要
Swiftで幽霊型を実装する方法を説明します。
※幽霊型は英語でPhantom Type(ファントムタイプ)と呼ばれますが、本記事内では幽霊型で統一します。
幽霊型とは
幽霊型はSwiftのclass、struct、enumのような言語機能として提供される型ではありません。
幽霊型は実装パターンの一種です。実装パターンのため、Swiftに限定されるものではなく、Scala、Haskellなどでも同様の表現があります。(参考: Phantom Typeの紹介)
コンパイル時にしか存在しないという特徴を持つため、幽霊型と呼ばれるようです。
幽霊型の特徴
幽霊型は以下のような特徴を持ちます。
- 型に型パラメータとして付加情報を持たせることにより、コンパイラでの静的チェックを強化する
- 型による制約がコードに表現されるため、コードが自己文書化され、可読性も向上する
- 幽霊型の実装はジェネリクスの型パラメータにすぎないため、実行時には影響しない
もっとも簡単な実装例
幽霊型のミニマムな実装例はこれだけです。ここでenumで表現している理由は、幽霊型はインスタンス化する必要がないからです。caseを持たないenumはインスタンス化することができないという一般的なテクニックを利用しています。
enum ATag {} enum BTag {} struct Hoge<T> { }型引数により、Aという情報を付与できています。
例えば以下のような実装により、ATag情報を持つHogeインスタンスのみを受け入れるような関数を作成できます。func print(hoge: Hoge<ATag>) { print("ATagのみ出力") } let hoge = Hoge<BTag>() print(hoge: hoge) // Extraneous argument label 'hoge:' in callもう少し面白い実装例
以下のような
Cat
型を考えます。struct Cat {}幽霊型によってねこが空腹かどうかの付加情報を表現してみます。
まず、
Cat
型に型パラメータT
を持たせ、ねこの状態をenumで表現します。// ジェネリック型に変更する struct Cat<CatState> {} // ねこの状態を表す幽霊型 protocol CatState {} protocol Runnable {} protocol Eatable {} enum HungryCat: CatState, Eatable {} // 空腹 enum NotHungryCat: CatState, Runnable {} // 満腹これでねこの状態を型情報に持たせることができるようになりました。
catA
インスタンスは空腹で、catB
インスタンスは満腹であることがコンパイラにも理解できるようになります。let catA = Cat<HungryCat>() let catB = Cat<NotHungryCat>()これを利用し、
Cat
型を型制約付きのエクステンションを実装することで幽霊型に応じた実装が可能です。extension Cat where T: Eatable { func eat() { print("食べる") } } extension Cat where T: Runnable { func run() { print("走る") } } let catA = Cat<HungryCat>() catA.eat() catA.run() // Type 'HungryCat' does not conform to protocol 'Runnable' let catB = Cat<NotHungryCat>() catB.eat() // Type 'NotHungryCat' does not conform to protocol 'Eatable' catB.run()より実践的な実装例
【Swift】型を使うという意味を考える (Phantom Typeを通して) - Qiita
こちらの記事では幽霊型をショッピングアプリケーションでの状態管理に利用したり、UserのidやDogのidなどを同じString型で表現せず、複数のIDを幽霊型を利用して識別する方法が示されています。
幽霊型を利用しない場合
逆に幽霊型を利用しないパターンも容易に想像がつきます。例えば、先述のHungryCat、NotHungryCatを個別のstructで定義する場合です。
struct HungryCat: Eatable {} struct NotHungryCat: Runnable {}それぞれの型の固有の振る舞いはそれぞれの型に定義すれば良いですが、共通の振る舞いを安直に実装すると、冗長なコードになります。(
name
プロパティやsleep()
メソッド)struct HungryCat: Eatable { let name: String func sleep() { print("ねる") } } struct NotHungryCat: Runnable { let name: String func sleep() { print("ねる") } }メソッドであればプロトコルエクステンションでの処理の共通化が可能ですが、プロパティはスーパークラスを定義しないと共通化はできません。(スーパークラスの設計の難しさや構造体からクラスに変更する必要が出てくる)
extension Sleepable { func sleep() { print("ねる") } } struct HungryCat: Eatable, Sleepable { let name: String } struct NotHungryCat: Runnable, Sleepable { let name: String }上記のように、独自型を用いるのか、幽霊型を用いて既存の型を拡張していくのかどちらが良いのかは修正による影響範囲なども関係するため、ケースバイケースかと思います。独自型と幽霊型の使い分けなどあればコメントに書いていただけるとうれしいです。
参考
- Swift で Phantom Type (幽霊型) - Qiita
- iOSDC 2017 でさらっと出てきた Phantom Type さらっとやった話 - Qiita
- Phantom Typeでコンパイル時に状態チェックする: shibuya.swift #4 - speakerdeck
- FlowtypeでPhantom Typeを楽しむ - Qiita
- で、出たー!幽霊型だー!(Phantom Type) - Qiita
- F(by) 2017. Denis Shevchenko - The Way to Simplicity: How Haskell Simplifies Code Maintenance.
- Phantom type - HaskellWiki
- 投稿日:2019-05-03T19:25:26+09:00
【Swift】loadViewの注意点
loadViewを使用した際に困ったことを、備忘録として記載しておきます。
困ったこと
StoryBoardを使用することになった時に、loadViewを使ってレイアウトしていたせいで、作業が詰んでしまいました。
loadViewの呼ばれるタイミング
loadViewするタイミングで、controllerが制御するviewの作成を行います。
loadViewの使用目的
- xibを利用せず、コードでViewを作成する場合はオーバーライドする
- コードでViewを作成する場合はloadViewにコーディングをする※ただし、UI部品のセットはこのメソッドでなくても問題ない
loadViewの注意点
- StoryBoard(Interface Builder)やxibを使用する場合は、loadViewはオーバーライドしてはいけない
- 当たり前といえば当たり前ですが、loadViewの中身を書かなくても、オーバーライドしているのでアウトです。
// UI 部品を View へセットする場合はこちらをオーバーライドします。 // ただし、UI 部品のセットはこのメソッドでなくても問題ありません。 override func loadView() { super.loadView() print("loadView") } // 初期表示時に必要な処理を設定します。 override func viewDidLoad() { super.viewDidLoad() print("viewDidLoad") }引用:【Swift】UIViewController ライフサイクル 簡易説明書 | ポケットリファレンス サンプル付き
loadViewを使うときは、StoryBoardやxibを使用しないか、きちんと確認しましょう。
参考リンク
- 投稿日:2019-05-03T10:12:51+09:00
【学習記録13】2019/5/3(金)
学習時間
1.0H
使用教材
・絶対に挫折しないiPhoneアプリ開発「超」入門 第7版 【Xcode 10 & iOS 12】 完全対応 (Informatics&IDEA)
Progate リンク[https://prog-8.com/]
学習分野
Progate
Swift学習コースⅠ
3.アプリを作ってみようコメント
学習開始からの期間:13日目
今日までの合計時間:34.0H
- 投稿日:2019-05-03T09:22:39+09:00
UIAlertController をRxSwiftを使って拡張してみました。
UIAlertController RxSwiftでUIViewController拡張
RxSwiftを使って、UIViewControllerを拡張してみました。
引用:
https://qiita.com/katafuchix/items/50266e0eb52a032c9629cocoapod
pod 'RxUIAlert'Carthage
github "RxSwiftCommunity/RxAlert"これで動きます。
Sample code
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) alert(title: "WOW", actions: [AlertAction(title: "OK", type: 0, style: .default)], vc: self).subscribe(onNext: { (num) in }).disposed(by: disposeBag) }
- 投稿日:2019-05-03T06:22:28+09:00
Swiftでサウンドプログラミング 基本的な波形の音を鳴らす
はじめに
本記事では Swift による実装でサイン波をはじめとした基本的な波形の音を鳴らす方法を紹介します。 記事中のソースコードは Xcode の Playground に貼り付けて実行すると実際に音を聞くことができます。
基本的な音を鳴らすだけのため、ソースコードは音データの作成部分が虫食いになっている雛形を最初に提示し、各波形では虫食い部分のコードのみを記述していきます。
コード雛形
import AVFoundation let audioEngine = AVAudioEngine() let player = AVAudioPlayerNode() let audioFormat = player.outputFormat(forBus: 0) let fs = Float(audioFormat.sampleRate) // 標本化周波数: 44.1K Hz let length = fs * 1.0 // 音データの長さ // 音データ let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity:UInt32(length))! buffer.frameLength = UInt32(length) let f0: Float = 500.0 // 周波数 /*======================== ここに色々当て込んでいきます ==========================*/ // オーディオエンジンにプレイヤーをアタッチ audioEngine.attach(player) // プレイヤーノードとミキサーノードを接続 audioEngine.connect(player, to: audioEngine.mainMixerNode, format: audioFormat) // 再生の開始を設定 player.scheduleBuffer(buffer) // エンジンを開始 try! audioEngine.start() // 再生 player.play()音を鳴らす
サイン波
「ピー」という笛のような音です。
ソースコード
let a: Float = 0.1 // 振幅 // サイン波 for ch in (0..<Int(audioFormat.channelCount)) { // オーディオのチャンネル数だけ繰り返す let samples = buffer.floatChannelData![ch] for n in 0..<Int(buffer.frameLength) { samples[n] = a * sinf(Float(2.0 * .pi) * f0 * Float(n) / fs) } }波形
ノコギリ波
サイン波と比べて音色が明るく聞こえる特徴のある音です。
ソースコード
for ch in (0..<Int(audioFormat.channelCount)) { let samples = buffer.floatChannelData![ch] for i in 1..<45 { for n in 0..<Int(buffer.frameLength) { samples[n] += Float(1) / Float(i) * sinf(Float(2.0 * .pi) * Float(i) * f0 * Float(n) / fs) } } for n in 0..<Int(buffer.frameLength) { samples[n] *= 0.1 // ゲイン } }波形
矩形波
うつろな音色に聞こえる特徴のある音です。
ソースコード
// 矩形波 for ch in (0..<Int(audioFormat.channelCount)) { let samples = buffer.floatChannelData![ch] for i in 1..<45 where i % 2 == 1 { for n in 0..<Int(buffer.frameLength) { samples[n] += Float(1) / Float(i) * sinf(Float(2.0 * .pi) * Float(i) * f0 * Float(n) / fs) } } for n in 0..<Int(buffer.frameLength) { samples[n] *= 0.1 // ゲイン } }波形
三角波
矩形波と比べて音色がおとなしく聞こえる特徴のある音です。
ソースコード
for ch in (0..<Int(audioFormat.channelCount)) { let samples = buffer.floatChannelData![ch] for i in 1..<45 where i % 2 == 1 { for n in 0..<Int(buffer.frameLength) { samples[n] += Float(1) / Float(i) / Float(i) * sinf(.pi * Float(i) / Float(2)) * sinf(Float(2.0 * .pi) * Float(i) * f0 * Float(n) / fs) } } for n in 0..<Int(buffer.frameLength) { samples[n] *= 0.1 // ゲイン } }波形
コサイン波の重ね合わせによるノコギリ波
通常のノコギリ波と全く同じ音色に聞こえます。
ソースコード
// ノコギリ波 for ch in (0..<Int(audioFormat.channelCount)) { let samples = buffer.floatChannelData![ch] for i in 1..<45 { for n in 0..<Int(buffer.frameLength) { samples[n] += Float(1) / Float(i) * cosf(Float(2.0 * .pi) * Float(i) * f0 * Float(n) / fs) } } for n in 0..<Int(buffer.frameLength) { samples[n] *= 0.1 // ゲイン } }波形
白色雑音
音の高さを定義できない音です。アナログテレビの砂嵐のような音がしました。
ソースコード
// 白色雑音 for ch in (0..<Int(audioFormat.channelCount)) { let samples = buffer.floatChannelData![ch] for i in 1..<22050 { let theta = Float.random(in: 0 ..< (2 * Float.pi)) for n in 0..<Int(buffer.frameLength) { samples[n] += sinf(Float(2.0 * .pi) * Float(i) * f0 * Float(n) / fs + theta) } } for n in 0..<Int(buffer.frameLength) { samples[n] *= 0.001 // ゲイン } }波形
おわりに
今回の記事を書こうと思ったのはたまたま手にとった『サウンドプログラミング入門』という書籍を読んだのがきっかけです。書籍でのサンプルコードはC言語で書かれていましたが、自分にとって一番馴染みのある Swift で書くとどうなるだろうと思い実装してみました。
まだ読み途中なので、読み進めてまた記事にまとめられそうであれば、別記事として書こうと思います。
Appendix: 12平均律音階
サイン波でドレミファソラシドの音を鳴らします。
ソースコード
import AVFoundation let audioEngine = AVAudioEngine() let player = AVAudioPlayerNode() let audioFormat = player.outputFormat(forBus: 0) func sineWave(_ buffer: AVAudioPCMBuffer, f0: Float, a: Float, offset: Int, duration: Int) { // サイン波 for ch in (0..<Int(audioFormat.channelCount)) { // オーディオのチャンネル数だけ繰り返す let samples = buffer.floatChannelData![ch] for n in 0..<duration { samples[offset + n] = a * sinf(Float(2.0 * .pi) * f0 * Float(n) / fs) } } } let fs = Float(audioFormat.sampleRate) // 標本化周波数: 44.1K Hz let length = fs * 2.0 // 音データの長さ // 音データ let buffer = AVAudioPCMBuffer(pcmFormat: audioFormat, frameCapacity:UInt32(length))! buffer.frameLength = UInt32(length) let a: Float = 0.1 // 振幅 let f0: Float = 500.0 // 周波数 sineWave(buffer, f0: 261.63, a: 0.1, offset: Int(fs * 0.00), duration: Int(fs * 0.25)) // C4 sineWave(buffer, f0: 293.66, a: 0.1, offset: Int(fs * 0.25), duration: Int(fs * 0.25)) // D4 sineWave(buffer, f0: 329.63, a: 0.1, offset: Int(fs * 0.50), duration: Int(fs * 0.25)) // E4 sineWave(buffer, f0: 349.23, a: 0.1, offset: Int(fs * 0.75), duration: Int(fs * 0.25)) // F4 sineWave(buffer, f0: 392.00, a: 0.1, offset: Int(fs * 1.00), duration: Int(fs * 0.25)) // G4 sineWave(buffer, f0: 440.00, a: 0.1, offset: Int(fs * 1.25), duration: Int(fs * 0.25)) // A4 sineWave(buffer, f0: 493.88, a: 0.1, offset: Int(fs * 1.50), duration: Int(fs * 0.25)) // B4 sineWave(buffer, f0: 523.25, a: 0.1, offset: Int(fs * 1.75), duration: Int(fs * 0.25)) // C5 // オーディオエンジンにプレイヤーをアタッチ audioEngine.attach(player) // プレイヤーノードとミキサーノードを接続 audioEngine.connect(player, to: audioEngine.mainMixerNode, format: audioFormat) // 再生の開始を設定 player.scheduleBuffer(buffer) // エンジンを開始 try! audioEngine.start() // 再生 player.play()参考
- 投稿日:2019-05-03T06:16:16+09:00
SwiftでTesseract-OCR-iOSを使ってみる
画像認識のアプリを作成しようと思い、Tesseractを使ってわかったことをまとめておきたいと思います。
開発環境
iOS 12
Swift 5
Xcode 10.2.1
TesseractOCRiOS 5.0.0Tesseractの導入で参考になったサイト
この人の記事がよくまとまっているので、参考にさせてもらいました。
【Swift】文字認識ライブラリ、TesseractOCR for iOSを試してみたただ、こちらの記事は2015年の記事なので少し古く「4.BridgingHeaderを用意する」のHeaderの作成は必要内容です。
- gali8/Tesseract-OCR-iOS
- Installationチュートリアル
海外のチュートリアルでアプリのベースも無料で配布されています。
Tesseract OCR Tutorial for iOSこのチュートリアルでTesseractの制限が書いてあります。
- Unlike some OCR engines (like those used by the U.S. Postal Service to sort mail), Tesseract is unable to recognize handwriting. In fact, it’s limited to about 64 fonts in total.
- Tesseract’s performance can improve with image pre-processing. You may need to scale images, increase color contrast, and horizontally-align the text for optimal results.
- Finally, Tesseract OCR only works on Linux, Windows, and Mac OS X.
この方法でやられている方がこちら
Tesseract-OCR を iPhone で使ってみるこちらの方の内容も結構充実しています。
Xcode 7.0 + Swift2 でTesseract-OCR-iOSを使う(追記あり)この人の動画がわかりやすいです。
https://www.youtube.com/watch?v=DTQ1z_8KXZo文字データのバージョンがポイント
文字の解析に使う文字情報のバージョンによってエラーが発生してしまい、なかなかうまくいかない様です。
こちらのデータリストからダウンロードしたものは使えました。
https://github.com/tesseract-ocr/tessdata.まとめ
単純にカメラで撮影した画像を認識させようと思うと文字が化けるので、画像の加工が必要になるっぽい。ただ、どの様にTesseractが認識しているのかを調べる必要があるかもしれない。
Tesseract OCR iOSで認識させた文字の情報を取得する補足
日本語の文字認識の精度があまりよろしくないので、他のOCRも試してみたいと思う。
https://www.isoroot.jp/blog/1025/