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

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
}

上記のように、独自型を用いるのか、幽霊型を用いて既存の型を拡張していくのかどちらが良いのかは修正による影響範囲なども関係するため、ケースバイケースかと思います。独自型と幽霊型の使い分けなどあればコメントに書いていただけるとうれしいです。

参考

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

【Swift】loadViewの注意点

loadViewを使用した際に困ったことを、備忘録として記載しておきます。

困ったこと

StoryBoardを使用することになった時に、loadViewを使ってレイアウトしていたせいで、作業が詰んでしまいました。

loadViewの呼ばれるタイミング

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f34353532352f36373064303033382d366630332d303935662d636132322d3930633531306638626162662e706e67.png
引用:UIViewControllerのライフサイクル

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を使用しないか、きちんと確認しましょう。

参考リンク

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

【学習記録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

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

UIAlertController をRxSwiftを使って拡張してみました。

UIAlertController RxSwiftでUIViewController拡張

RxSwiftを使って、UIViewControllerを拡張してみました。

引用:
https://qiita.com/katafuchix/items/50266e0eb52a032c9629

cocoapod

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

RxCommunity

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

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

波形

screencapture 2019-05-03 5.14.58.png

ノコギリ波

サイン波と比べて音色が明るく聞こえる特徴のある音です。

ソースコード

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 // ゲイン
    }
}

波形

screencapture 2019-05-03 5.15.59.png

矩形波

うつろな音色に聞こえる特徴のある音です。

ソースコード

// 矩形波
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 // ゲイン
    }
}

波形

screencapture 2019-05-03 5.16.30.png

三角波

矩形波と比べて音色がおとなしく聞こえる特徴のある音です。

ソースコード

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 // ゲイン
    }
}

波形

screencapture 2019-05-03 5.16.45.png

コサイン波の重ね合わせによるノコギリ波

通常のノコギリ波と全く同じ音色に聞こえます。

ソースコード

// ノコギリ波
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 // ゲイン
    }
}

波形

screencapture 2019-05-03 5.17.06.png

白色雑音

音の高さを定義できない音です。アナログテレビの砂嵐のような音がしました。

ソースコード

// 白色雑音
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 // ゲイン
    }
}

波形

screencapture 2019-05-03 5.17.31.png

おわりに

今回の記事を書こうと思ったのはたまたま手にとった『サウンドプログラミング入門』という書籍を読んだのがきっかけです。書籍でのサンプルコードは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()

参考

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

SwiftでTesseract-OCR-iOSを使ってみる

画像認識のアプリを作成しようと思い、Tesseractを使ってわかったことをまとめておきたいと思います。

開発環境

iOS 12
Swift 5
Xcode 10.2.1
TesseractOCRiOS 5.0.0

Tesseractの導入で参考になったサイト

この人の記事がよくまとまっているので、参考にさせてもらいました。
【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で認識させた文字の情報を取得する

補足

TesseractとKrakenのレビュー(英語)

日本語の文字認識の精度があまりよろしくないので、他のOCRも試してみたいと思う。
https://www.isoroot.jp/blog/1025/

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