20190522のSwiftに関する記事は11件です。

処理中にNavigationBarのボタン操作を無効にする。

まくら

Navigation Controller を使ったアプリを作っている時に、稀によくある問題に対するアプローチです。

例えばこんな場合

  • とある画面で時間のかかる処理中に読み込み中のインジケータを出したい
  • インジケータが出ている間はナビゲーションバーの操作(戻るボタンとか)を無効にしたい

インジケータを出すのはいいとして、操作を無効にする方法はいくつか思いつきますが、今回は前面に目隠しを出して操作できないようにしてみましょう。

ほんだい

まずはソースをどうぞ。
インジケータを表示する部分です。

    // インジケータを表示する
    private func showIndicator() {

        // 背景になるView
        let backView = UIView()
        backView.backgroundColor = UIColor.init(white: 0.0, alpha: 0.5)
        backView.tag = 12345 // 消す時用にタグを付けておく

        // インジケータ
        let indicator = UIActivityIndicatorView()
        indicator.activityIndicatorViewStyle = .whiteLarge
        indicator.frame = CGRect(x: 0, y: 0, width: 100, height: 100)

        // NavigationControllerのViewを使う
        guard let naviView = self.navigationController?.view else {
            return
        }

        // 背景にインジケータを貼り付け
        backView.addSubview(indicator)

        // 背景をNavigationControllerのViewに貼り付け
        naviView.addSubview(backView)

        // サイズ合わせはAutoLayoutで
        backView.translatesAutoresizingMaskIntoConstraints = false
        backView.topAnchor.constraint(equalTo: naviView.topAnchor).isActive = true
        backView.bottomAnchor.constraint(equalTo: naviView.bottomAnchor).isActive = true
        backView.leftAnchor.constraint(equalTo: naviView.leftAnchor).isActive = true
        backView.rightAnchor.constraint(equalTo: naviView.rightAnchor).isActive = true

        // インジケータもAutoLayout
        indicator.translatesAutoresizingMaskIntoConstraints = false
        indicator.centerXAnchor.constraint(equalTo: backView.centerXAnchor).isActive = true
        indicator.centerYAnchor.constraint(equalTo: backView.centerYAnchor).isActive = true
        indicator.widthAnchor.constraint(equalToConstant: 100).isActive = true
        indicator.heightAnchor.constraint(equalToConstant: 100).isActive = true

        // インジケータ起動
        indicator.startAnimating()
    }

これを適当に実装するとこんな感じになります。

SS 2.png

ポイントはNavigationBarがグレーのViewに覆われていてBarButtonItemが押せない状態になっているところです。

ここですね。

    // NavigationControllerのViewを使う
    guard let naviView = self.navigationController?.view else {
        return
    }
    // 背景をNavigationControllerのViewに貼り付け
    naviView.addSubview(backView)

要は、NavigationController.ViewにもaddSubViewできますよって事ですね。

NavigationControllerの上にViewを貼り付けるので、当然その下のボタン等は押せなくなるわけです。


ちなみに注意点としては、当然ですが、NavigationController.Viewが存在しないと使えません。

prepare for segueの遷移時などNavigationControllerが追いづらい時には正直使いづらいです。



現場からは以上です。

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

AnyとAnyObject

Any

どんな型とも相当する。
関数型でもいい。

一貫性がない型のコレクション
let items: [Any] = [123, "ACB", 4.5, true]

AnyObject

クラスなら、どんな型にでも相当する。構造体とかはダメ。

キャストする

Any型はキャストして使う
if let firstItem = items.first as? Int {
    print(firstItem + 77)    // 200
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ionic+Cordovaでuniversal linkの実装(ios版)

はじめに

Ionic + Cordovaでiosアプリのuniversal linkが実装できるか試してみました。universal linkは、URLを使ってアプリを起動するものです。

Ionic + Cordova環境でuniversal linkのプラグインは、検索するといくつか出てきて、どれを使うか迷いました。cordova-universal-links-plugincordova-universal-links-plugin-deluxeが検索されたのですが、廃止予定(DEPRECATED)のようです。

今回は、Ionicから出されているDeeplinksを使ってみました。Deeplinksの導入手順を実行しましたが、引数等のパラメータがわからないところがあり、試行錯誤しました。以下では、うまくいった方法を書いていきます。

環境

  • Ionic 4.12.0
  • Cordova 9.0.0
  • Xcode 10.2.1
  • Ionic Deeplink Plugin 1.0.19

手順

  • universal linkを実装するアプリフォルダに移動します。アプリを新規に作成するときはionic startで作成します。
  $ cd myApp
  • Deeplinkプラグインをインストールします。オプション引数の順番が違うとエラーが起きたので、順番もドキュメントに書かれている通りに入れる必要があります。
    • URL_SCHEME : カスタムURLスキーム。今回は使いませんが、これを指定しないとインストールできませんでした。
    • DEEPLINK_SCHEME : universal linkのスキーム。通常はhttps
    • DEEPLINK_HOST : universal linkのホスト名。ここでは、example.comを指定
$ ionic cordova plugin add ionic-plugin-deeplinks --variable URL_SCHEME='example' --variable DEEPLINK_SCHEME=https --variable DEEPLINK_HOST='example.com'

package.jsonに以下の設定が入るようです。

"plugins": {
  "ionic-plugin-deeplinks": {
    "URL_SCHEME": "example",
    "DEEPLINK_SCHEME": "https",
    "DEEPLINK_HOST": "example.com"
  }
}
  • ionic nativeをインストールします。
$ npm install @ionic-native/deeplinks
  • アプリのパッケージ名を指定します。iosの場合は、Bundle Identifierにこの値が入ります。 config.xmlのidフィールドを書き換えます。最初は、io.ionic.starterとなっているので、 例えば、com.exampleに書き換えます。
<widget id="io.ionic.starter" version="0.0.1">
<widget id="com.example" version="0.0.1">
  • iosプラットフォームを追加します。
$ ionic cordova platform add ios
  • src/app/app.module.tsに以下の2つを追加します。

import及びprovidersにDeeplinksを追加します。

 import { Deeplinks } from '@ionic-native/deeplinks/ngx';

   providers: [
     StatusBar,
     Deeplinks,
   ]
  • src/app/app.component.tsに以下を追加します。
    • import文
    • constructorにDeeplinks
    • initializeAppにDeeplinksの処理を追加します。この例は、https://example.com/home で起動された場合に、 HomePageを表示します。HomePageの部分は、
import { Deeplinks } from '@ionic-native/deeplinks/ngx';
import { HomePage } from './home/home.page';

constructor(
   private statusBar: StatusBar,
   private deeplinks: Deeplinks
)

  initializeApp() {
    this.platform.ready().then(() => {
      // 他のコード
      this.deeplinks.route({
         '/home': HomePage
      }).subscribe(match => {
         console.log('Success', match);
      }, nomatch => {
         console.error('Fail', nomatch);
      });
    });
  }
  • Xcodeを立ち上げのプロジェクト設定を変更します。  プロジェクトファイルは、platform/ios/MyApp.xcodeprojです。TARGETのCapabilitiesを選び、Associated DomainsをONにします。このときに署名を選択する画面が出たときには、署名を選びます。+ ボタンを押して applinks:example.comを追加します。

20190522-ionic-cordova-universal-link-ios-project1.png

20190522-ionic-cordova-universal-link-ios-project2.png

  • アプリを端末/シミュレータに配置します。 これでuniversal linkによっって起動されるアプリができました。別のアプリからuniversal linkを使ってこのアプリを起動することができます。
$ ionic cordova run ios

参考文献

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

Swiftの型キャストPART1

型をダウンキャストする方法

Swift における型変換について調べました。
as? 演算子の基本です。

こんな継承したAnimal型があって

Animal型とその継承クラス
class Animal {
    // 体重とか
}

class Dog: Animal {
    var name: String
}

class Cat: Animal {
    var boxSize: Int
}

class Bird: Animal {
    let featherColor: Color
}

こんな関数があって

Animal型を返す関数
func getPet() -> Animal {
    // return the pet
}

let pet = getPet()    //petはAnimal型

この時点で、pet の具体的な型は不明。
Dog かも知れないし、Cat かも知れないし、Bird かも知れない。

ペットの種類に合わせたタスク
func walk(dog: Dog) {
    // お散歩させる
    print("Walking with \(dog.name)")    
}

func cleanLitterBox(cat: Cat) {
    // ネコ用トイレを掃除する
    print("Cleaning the \(cat.boxSize) litter box")    
}

func cleanCage(bird: Bird) {
    // 鳥かごをキレイにする
    print("Removing the \(bird.featherColor) feathers")
}

ペットに合わせたタスクを行う関数があるけど、定数 pet を引数にするとエラーになる可能性がある。

ダウンキャストして、適切にタスクを実行する

as?if-let ステートメントを組み合わせて、型キャストしつつオプショナルをアンラップする。

ダウンキャストの例
let pets = allPetAnimals()    //あらゆる種のペットを含んだ配列

for pet in pets {
    if let dog = pet as? Dog {
        walk(dog: Dog)
    } else if let cat = pet as? Cat {
        cleanLitterBox(cat: Cat)
    } else if let bird = pet as? Bird {
        cleanCage(bird: Bird)
    } else {
        print("The type of this pet is unknown.")
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSの自動更新購読 Auto-Renewing subscriptionでハマるたった1つの罠

出落ちです

共有シークレットを開発用だけ作成して本番用を忘れがち

開発環境では上手く動くが、本番環境で急に動かなくなったら共有シークレットを疑いましょう。
あと、TestFlightビルド時のレシートはSandBox環境に送りつけないとエラーになります。

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

SwiftのProtocol ExtensionはExistentialをopenする

この記事ではSwiftのProtocol ExtensionがExistentialをopenする事を説明します。

Existentialと型変数の違い

SwiftのExistentialは、プロトコルに対して自動定義される型で、そのプロトコルを満たす型を代入できます。

protocol P {}

extension Int : P {}

var p: P = 3

ここでpの型はPのexistentialです。Pのexistentialに代入できるのは、Pを満たす型なので、つまり、ジェネリクスの<T: P> Tで表される型変数です。Int<T: P> Tにマッチしますね。

ここで、Pのexistentialと<T: P> Tの違いを説明します。

Pのexistentialは、条件を満たす型がなんでも代入できる型です。この型になった時点で、元の値の型の情報は消去されています。そのため、例えば下記のように異なる型を再代入する事ができます。

protocol P {}
extension Int : P {}
extension String : P {}

var p: P = 3
p = "str"

一方、<T: P> Tは、条件を満たす特定のある型を表しています。実行時の実際の値の型が
何になっているかはわかりませんが、コンパイル時の型検査ではTという表現で特定の型を表しています。そのため、下記のように特定の型を代入する事はできません。

func g<T: P>(_ x: T) {
    var x = x
    x = "" // Cannot assign value of type 'String' to type 'T'
}

このように、existentialと型変数は、実行時に代入できる型の条件は同じでも、コンパイル時の型検査においては、型の同一性が失われている、維持しているのかという違いがあります。

SwiftのCore TeamメンバーのJoe Groffは、この違いをvalue-level abstraction(Existential)とtype-level abstraction(型変数)と説明しています。

Existentialのopen

ここで、Existentialから型変数を取り出す事を考えることができます。つまり、コードにおいて、Existentialに入っている型の同一性を復旧させます。

例1

この概念は、Generics Manifestoで紹介されています。

let e1: Equatable = ...
let e2: Equatable = ...

if let storedInE1 = e1 openas T { // T is the type of storedInE1, a copy of the value stored in e1
  if let storedInE2 = e2 as? T {  // Does e2 have type T? If so, copy its value to storedInE2
    if storedInE1 == storedInE2 { ... } // Okay: storedInT1 and storedInE2 are both of type T, which we know is Equatable
  }
}

この例は、Equatableのexistentialであるe1e2==で比較する架空のコードです。実際の現在のSwiftにおいては、Equatableのexistentialはありませんが、それが仮にあった場合の話です。

このコードでは、openasという架空の言語機能を使って、e1から真の型Tを取り出し、e2を、そのTにキャストした後で、e1e2==比較をしています。この、真の型を取り出す操作をExistentialのopenと言います。

なぜこのような手間を踏む必要があるかというと、Equatableプロトコルは自身と同じ型を比較するためのプロトコルだからです。その機能を提供する==メソッドの型はfunc ==(lhs: Self, rhs: Self) -> Boolであり、両辺が同じ型でなければ呼び出せません。Equatableのexistentialであるe1e2にはそれぞれ異なる型が入っているかもしれないので、そのままでは比較できないのです。

ちなみに、本題とは関係ありませんが、標準ライブラリにはAnyHashableというtype erasureが提供されていて、これを使うと異なる型の等値比較を行うことができます。

var e1 = AnyHashable(Int(3))
var e2 = AnyHashable(Double(3.0))
print(e1 == e2) // true

例2

Joe Groffがフォーラムに投稿した書き込みにもう一つの例があります。

func bar(x: Collection) { 
    let <X: Collection> openedX = x // X is now bound to the dynamic type of x
    let start = openedX.startIndex
    let first = openedX[start] // OK, indexing X with a value of type X.Index, to get a result of type X.Element
}

この例では、また別の架空の構文を使って、Collectionのexistentialであるxから、真の型をXとして取り出しています。これにより、openedXのsubscriptにopenedXstartIndexを渡しています。

なぜこのような処理が必要かというと、Collectionは自身のIndex型を持っているが、existentialのままではその型情報が消えてしまうために、型安全なコードが書けないからです。

例えば標準ライブラリにはAnyCollectionというtype erasureがありますが、これを使うと下記のようなコードを書くことができます。このコードはコンパイルすることはできますが、実行時にクラッシュしてしまいます。それは、真の型Array<Int>のsubscriptに対して、String.Indexを渡しているからです。

var c1 = AnyCollection([3])
var c2 = AnyCollection("str")
print(c1[c2.startIndex])

仮にCollectionのexistentialがあったとしても、これと同様に、真の型がわからないので、真の型に対応したIndex型もわからず、型安全にできないのです。

Protocol Extensionによるopen

ここまで見たように、Existentialのopenという概念は公式に説明されていますが、実際の言語仕様としては導入されていません。しかし実は、Protocol Extensionにおいては、すでにExistentialのopenが実現されているのです。

protocol P {
    func f()
}

extension Int : P {}

extension P {
    func f() {
    }
}

このように、プロトコルPのメソッドfが、Protocol Extensionによって実装を与えられている状況を考えます。

このとき、Pのexistential型に対しても、fを呼び出すことができます。

var x: P = 3
x.f()

実はここでExistentialがopenされています。これは、P.fにおけるselfの型を考える事でわかります。

P.fの型は何になっているのでしょうか。もし、Pのexistentialであったとしたら、前述の例と同様にして、selfの型の変数に対して、何かPを満たす型を代入できるはずです。

extension P {
    func f() {
        var x = self
        x = Int(3) // Cannot assign value of type 'Int' to type 'Self'
    }
}

しかし、代入することはできません。一方で、selfPを満たしているとして扱えます。下記のように他にもメソッドgがあったとき、それを呼び出すことができます。

protocol P {
    func f()
    func g()
}

extension P {
    func f() {
        g()
    }
}

selfはこのような特性をもっているので、<T: P> Tという型になっていると考えられます。

もう一つの根拠として、このコードのSILコードがあります。

// P.f()
sil hidden @$s1a1PPAAE1fyyF : $@convention(method) <Self where Self : P> (@in_guaranteed Self) -> ()

P.fはこのように、<Self where Self : P>というジェネリックシグネチャを持っていて、引数の型がSelfになっています。

そして、このextensionによって自動実装されたInt.fのSILは下記のようになります。

// protocol witness for P.f() in conformance Int
sil private [transparent] [thunk] @$sSi1a1PA2aBP1fyyFTW : $@convention(witness_method: P) (@in_guaranteed Int) -> () {
// %0                                             // user: %2
bb0(%0 : $*Int):
  // function_ref P.f()
  %1 = function_ref @$s1a1PPAAE1fyyF : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> () // user: %2
  %2 = apply %1<Int>(%0) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> ()
  %3 = tuple ()                                   // user: %4
  return %3 : $()                                 // id: %4
} // end sil function '$sSi1a1PA2aBP1fyyFTW'

これを見ると、P.fである$s1a1PPAAE1fyyFに対して、型変数<Self>Intを与えて呼び出している事が確認できます。これは通常のジェネリックメソッドと同じ機構になっています。

このように、selfの型が<Self : P> Selfという型変数になっているのは、実はextensionに限らずプロトコルのメソッドの共通の特徴です。

Self conformanceとopen

protocolのexistentialは自分自身のprotocolに準拠しません。詳細は以前書いた記事を参照してください。

これは記述でいうと、P<T: P> Tにマッチしないということです。一方で、ここまでで見たように、protocol extensionのメソッドが呼び出される時、P型であるself<Self: P> Self型としてopenされています。この違いを説明します。

まず、なぜexistentialをopenできるのかというと、P型の変数には、実際にPを満たす真の型を持った値が入っているからです。<Self: P> Selfとしてopenされているのは、この真の値が対象なのです。この値の型がPを満たしているという事は、Pの存在型に値を代入する際の条件でした。よって、それがopenできるのは当然です。

一方、protocolのself conformanceとして議論しているのは、Pのexistential型が、プロトコルPに準拠できるか、ということです。これは特定の値とは関係の無い、型の話です。

ここで逆に、P型がself-conformanceを満たす条件を考えてみます。それは@objcがついていて、スタティックメンバを持たないことです。スタティックメンバを持たないということは、そのP型に対して何か関わるときは、かならずインスタンスが絡んでいるということです。そして、インスタンスが絡んでいるということは、内部にselfを保持したexistential型の値が対象になっているということです。これはexistentialがopenできる理由と同じ事を考えているとわかります。

protocol extensionによるself conformanceの制約の回避

これまでの話で、protocol extensionを使うとexistentialがopenしていることがわかりました。これを利用すると、self-conformanceがなくて困った場合に、問題を回避できる場合があります。

例えば、下記のように、P型がEncodableであるときに、Pのexistentialを持つ型Sがあったとします。

protocol P : Encodable {}
extension Int : P {}
struct S {
    var p: P
    init() { p = 3 }
}

Sの値があるとき、pencodeしようと思っても、JSONEncoder<X: Encodable> Xは受け取れますが、Encodableのexistentialはサポートしていないため、できません。

func main(_ s: S) throws {
    let encoder = JSONEncoder()
    try encoder.encode(s.p) // Protocol type 'P' cannot conform to 'Encodable' because only concrete types can conform to protocols
}

そこで、Pに下記のようなextensionを与えて、それ経由の呼び出しに書き換えると、encodeができます。

extension P {
    func encode(jsonEncoder: JSONEncoder) throws -> Data {
        return try jsonEncoder.encode(self)
    }
}

func main(_ s: S) throws {
    let encoder = JSONEncoder()
    try s.p.encode(jsonEncoder: encoder)
}

合わせて読みたい

型システムの理論からみるSwiftの存在型(Existential Type)
Swiftでprotocol型の値がそのprotocol自身にconformしていない理由

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

【Server-side-Swift】【Vapor】iOSアプリエンジニアが挑戦する初めてのサーバーサイド【勉強会レポ】

はじめにの前に(概要/対象読者)

概要

  • SwiftでサーバーサイドAPIの開発ができる
      -> サーバーサイドの知識があまりなくても開発できちゃう
  • Swiftすごい、Vaoprすごい

対象読者

  • Swiftの基本的なコーディングを経験した人
  • サーバーサイド知識はあまりないが(Swiftを用いた)サーバーサイドの構築に興味がある人

※今回の勉強会で使用したコードは以下に置いています。Vaporで生成されたコードをちょっといじっただけですが。
https://github.com/s-kamada/Swift-server

あくまでレポなので勉強会の内容に合わせて記述します。

はじめに

今年の3月に大学を卒業し、4月からアプリを作る会社に入社した者です。
まだ研修期間中なのですが、研修では所定の要件に沿ったiOSアプリとAndroidアプリを製作しています。

先日研修のiOSアプリの製作が終了し、「Swiftの知見をさらに深めたい!」ということでこちらの勉強会に参加してきました。
iOSアプリエンジニアが挑戦する初めてのサーバーサイド(connpassリンク)
スライド(SlideShare)
スライド

大学時代にエンジニア志望の仲間として緒に勉強していた友人が講師役の勉強会です。
生粋のiOS/Swiftエンジニアですが普段は睡眠とAIを掛け合わせた研究をしているんだとか。

Swiftでサーバーサイドを作れるらしい

Swiftで書くと何がいいの?

  • 早い

Swiftは4秒で終了しました。 Javaは4.3秒と、ほぼ同じぐらいです。このテストにおけるSwiftとJavaの性能はほぼ同じですね。Node.jsを見てみましょう。15.8秒かかっています。SwiftやJavaの約4倍ですね。Rubyはどうでしょう。とても遅いです。1

  • 省メモリ

RSS(ベンチマークを実行するのに必要な物理メモリ領域)のベンチマークを見てみると、Swiftでは15MBで、Javaでは32MBです。必要なメモリはJavaの半分なのに、同じ性能です。Node.jsはJavaよりは若干少ないですが、Swiftよりずっと多いです。Rubyはたくさん必要ですね。1

なんでVaporなの?

Server-side-swift のフレームワークは数多あるけれど

  • perfect
  • Swifton
  • Slimane
  • Express
  • HTTPSwiftServor
  • Kitura
  • Vapor etc......

主流はKitura、Vapor。
海外ではVaporの方がファンが多く、サーバーサイドの知識があまりない人はVaporの方がいい、ただしVaporの方がSwiftのアップデートへの対応が早いが、開発者もそれについていくためのコストがかかる、など長短あるようです。2

Vaporを使う

※ Homebrew、Swift/Xcode(4.1.0以上)がインストールされている前提です

構築

インストール

$ brew install vapor/tap/vapor

プロジェクト作成

$ cd [プロジェクトを置きたいディレクトリ]
$ vapor new [プロジェクト名]

イカしたロゴとともにプロジェクトが生成されます
スクリーンショット 2019-05-22 14.09.39.png

プロジェクトを開く(Xcode)

すこし時間がかかります

$ vapor xcode
Generating Xcode Project...
Open Xcode project?
y/n> y
Opening Xcode project...

プロジェクト構成

スクリーンショット 2019-05-22 14.51.37.png
基本的に編集するのはAppフォルダ内のファイルです
Controllersはコントローラ、Modelsはモデルを入れるフォルダ、など見た通りの命名がされています。

コーディング(抜粋)

自動生成されたものだけも十分動きますが、ちょっといじります。
今回は「Todo管理アプリ」のサーバーサイドAPIを作成する前提で編集します。

モデル

TodoModel.swift
final class TodoModel: SQLiteModel {
    /// The unique identifier for this `Todo`.
    var id: Int?
    /// A title describing what this `Todo` entails.
    var title: String
    var isFinished: Bool

    /// Creates a new `Todo`.
    init(id: Int? = nil, title: String, isFinished: Bool) {
        self.id = id
        self.title = title
        self.isFinished = isFinished
    }
}

データモデルです。マイグレーション(自動的にデータベースの更新・管理を行う)などのextensionも実装できます。

コントローラ

TodoController.swift
final class TodoController{

    /*中略*/

    //新しいTodoを生成する
    func create(_ req: Request) throws -> Future<Todo> {
        return try req.content.decode(Todo.self).flatMap { todo in
            return todo.save(on: req)
        }
    }

    //すでに登録されているデータを更新する
    func update(_ req: Request) throws -> Future<Todo> {
        return try flatMap(to: Todo.self, req.parameters.next(Todo.self), req.content.decode(Todo.self)) { todo, updateTodo in
    todo.title = updateTodo.title
    todo.isFinished = updateTodo.isFinished
    return todo.save(on: req)
        }
    }
}

データの読み込み、生成、削除の操作は自動で生成されますが、不足しているので更新処理を追記しました。

ルーティング

routes.swift
public func routes(_ router: Router) throws {
    // そのエンドポイントのルートにアクセスした場合
    // Basic "It works" example
    router.get { req in
        return "It works!"
    }

    // http://(エンドポイント)/hello/world にアクセスした場合
    // Basic "Hello, world!" example
    router.get("hello","world") { req in
        return "Hello, world!"
    }

    // http://(エンドポイント)/todos に対してAPIを叩く場合
    // Example of configuring a controller
    let todoController = TodoController()
    router.get("todos", use: todoController.index)
}

ルーティングを定義するファイルです。

ビルドしてサーバーを立てる

左上のバーをRun > My Macと設定してビルドします。この場合はlocalhost:8080にサーバーが立ちます。
ターミナル上でもビルドが可能ですがXcode上でビルドするよりも時間がかかるようです。

$ vapor build
$ vapor run

動いたか確認してみる

ブラウザでlocalhost:8080に移動してみると...
スクリーンショット 2019-05-22 15.45.02.png
動いてるみたいですね :dancer:
他のAPIも叩いてみます。ここからはAPIツールのPostmanを使用します

  • 追加
    スクリーンショット 2019-05-22 15.50.07.png

  • 一覧取得
    スクリーンショット 2019-05-22 15.51.53.png
    レスポンスが帰ってきているのがわかりますね :dancer:

まとめ

  • サーバーサイドAPIをSwiftだけで簡単に作れる。やったね! :dancer:
  • 課題(今回の勉強会では触れなかったこと)
    • DBとの連携が必要(今回はしていません。連携の設定をしないとXcodeを落とした際にデータがリセットされます)
    • マイグレーションの方法
    • デプロイ方法

  1. Try! Swift NYCの講演より。現在は少し記録が更新されているようですが、やはりSwiftは早いかつ省メモリなようです(The Benchmarks Game) 

  2. ここHacking with Swiftなど、KituraとVaporを比較考察している記事が多いです。 

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

【学習記録31】2019/5/22(水)

学習時間

1.0H

使用教材

本気ではじめるiPhoneアプリ作り Xcode 10.x対応 (Informatics&IDEA)

学習分野

chapter4

コメント

学習開始からの期間:32日目
今日までの合計時間:90.0H

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

swift バーション確認について

バージョン調査(xcode)

xcode場合は、xcodeを開くと書いてあります

スクリーンショット 2019-05-22 11.21.48.png

バージョン調査

ターミナルにswift -vと打ち込みます。

$ swift -v

たくさん出るが最後の方にswiftのバージョンが出ます

スクリーンショット 2019-05-22 11.31.23.png

参考サイト
バージョン確認

https://pippi-pro.com/xcode-version-confirmation#Xcode-2
https://qiita.com/t_punch/items/21b745054356e7828c35

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

円弧でパーセントグラフを作る

スクリーンショット 2019-05-21 20.48.51.png

描く順序としては
➀円の半径とパーセントを引数にする円弧のパスを作る関数を定義し、それを指定の座標に描く。
➁ここでパスを平行移動する
➂さらに62.3%のようにテキストをコンテキストに描画して円弧のグラフを合成。
です。

パーセント円弧を作る関数

func arcPercent(_ radius:CGFloat, _ percent:Double) -> UIBezierPath {

        //終わりの角度を計算します
        let endAngle = 2*Double.pi*percent/100-Double.pi/2

        //StartAngleで初めの角度指定します。
        let path = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: radius, startAngle: CGFloat(-Double.pi/2), endAngle: CGFloat(endAngle), clockwise: percent > 0
        )
        return path
    }

パスを平行移動する

     let tf = CGAffineTransform(translationX: view.center.x, y: view.center.y)
     //ビューの中心に平行移動します
     arcpath.apply(tf)

文字を合成する

        let font = UIFont.boldSystemFont(ofSize: 28)
     //フォント属性を指定します
        let textFontAttributes = [NSAttributedString.Key.font:font,NSAttributedString.Key.foregroundColor:UIColor.gray]
     //表示する数値
        let drawString = String(percent) + "%"
        let posX = view.center.x-45
        let posY = view.center.y-15
        let rect = CGRect(x: posX, y: posY, width: 90, height: 30)
        //テキストを描く
        drawString.draw(in:rect,withAttributes:textFontAttributes)

以上をまとめたクラス

import UIKit

class ViewController: UIViewController {

    //パーセントの円弧のパスを作る
    func arcPercent(_ radius:CGFloat, _ percent:Double) -> UIBezierPath {
        //パーセントの最終角度に換算します
        let endAngle = 2*Double.pi*percent/100-Double.pi/2

        let path = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: radius, startAngle: CGFloat(-Double.pi/2), endAngle: CGFloat(endAngle), clockwise: percent > 0
        )
        return path
    }
    func drawLine() -> UIImage {
        //イメージ処理の開始
        let size = view.bounds.size
        UIGraphicsBeginImageContextWithOptions(size, false, 1.0)

        let percent = 62.3
        //円弧のパスを作る
        UIColor.red.setStroke()
        let arcpath = arcPercent(80, Double(percent))
        arcpath.lineWidth = 60
        arcpath.lineCapStyle = .butt
        //パスを平行移動する
        let tf = CGAffineTransform(translationX: view.center.x, y: view.center.y)
        arcpath.apply(tf)
        //円弧を描画
        arcpath.stroke()

        //『何パーセント』の数字を書く
        let font = UIFont.boldSystemFont(ofSize: 28)
        let textFontAttributes = [NSAttributedString.Key.font:font,NSAttributedString.Key.foregroundColor:UIColor.gray]
        let drawString = String(percent) + "%"
        let posX = view.center.x-45
        let posY = view.center.y-15
        let rect = CGRect(x: posX, y: posY, width: 90, height: 30)
        //テキストを描く
        drawString.draw(in:rect,withAttributes:textFontAttributes)

        //イメージコンテキストからUIImageを作る
        let image = UIGraphicsGetImageFromCurrentImageContext()
        //イメージ処理の終了
        UIGraphicsEndImageContext()

        return image!


    }

    override func viewDidLoad() {
        super.viewDidLoad()
        //図形のイメージを作る
        let drawImage = drawLine()
        //イメージビューに設定する
        let drawView = UIImageView(image: drawImage)
        //画面に表示する
        view.addSubview(drawView)

    }


}


https://github.com/matushinn/BezierPath_arc_percent

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

[Swift]ポインタ使用パターンをテストする

はじめに

UnsafeMutablePointer の allocate/deallocate/initialize/move などのメソッドを一通り使用する。
また、ポインタの扱いでメモリリークが発生しないようにする。

テストで共通使用する関数・クラス

import Foundation
import CoreAudio

// オブジェクトのアドレスを保存するマップ
var addressMap = [String: Bool]()

// オブジェクトのアドレス(文字列)を得る
func address(_ object: AnyObject) -> String {
  return "\(Unmanaged.passUnretained(object).toOpaque())"
}

// テストの終了処理
func testFinally() {
  assert(addressMap.count == 0)  // アドレスマップの要素数を確認する(0でなければメモリリークをしている
  print("- OK\n")
}


// モッククラス
class Mock {

  // インスタンス生成時にアドレス表示&アドレスをマップに保存する
  init () {
    print("init   : \(address(self))")
    addressMap[address(self)] = true  // Store address.
  }

  init(_ value: Int) {
    print("init   : \(address(self))")
    addressMap[address(self)] = true  //  Store address.

    v1 = value
  }

  // deinit() でアドレスマップからアドレスを削除する
  deinit {
    print("deinit : \(address(self))")
    addressMap.removeValue(forKey: address(self))  //  Remove address.
  }


  // 自由に設定できる変数
  var v1 = Int(0)
}

テストケース

allocate / deallocate パターン

func testAllocateAndDeallocate() {
  print("# \(#function)")

  do {
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)

    // 未初期化のメモリはアクセスする前に、初期化する必要がある
    // initialize せずに以下のコードを実行するとエラーになる
    //
    // p.pointee.v1 = ...

    p.deallocate()  // deallocate をコールしないとメモリリークが発生する
  }

  testFinally() // メモリリークが発生していないことを確認する
}

initialize / deinitialize パターン

func testInitializeAndDeinitialize() {
  print("# \(#function)")

  do {
    // モックインスタンスを作成する
    var mock = Mock()
    mock.v1 = 10

    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    // インスタンスから UnsafeMutablePointer を initialize する    
    p.initialize(from: &mock, count: 1)
    p.pointee.v1 = 20  // initialize した UnsafeMutablePoitner はアクセス可能になる

    assert(address(mock) == address(p.pointee)) // インスタンスと UnsafeMutablePointer は同一アドレスである
    assert(mock.v1 == 20)

    p.deinitialize(count: 1)  // initialize した分、 deinitialize をコールしないとメモリリークが発生する
  }

  testFinally()
}

initialize / move パターン

func testInitializeAndMove() {
  print("# \(#function)")

  do {
    // UnsafeMutablePointer を allocate する
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    // インスタンス値から UnsafeMutablePointer を初期化する
    p.initialize(to: Mock())
    p.pointee.v1 = 10

    // p を move してインスタンスを取得する
    // move したことにより、 deinitialize しなくても p は deinit される
    let mock = p.move()

    // p.deinitialize() は不要

    assert(address(mock) == address(p.pointee))
    assert(mock.v1 == 10)
  }

  testFinally()
}

moveInitialize パターン

func testMoveInitialize() {
  print("# \(#function)")

  do {
    var mock = Mock()
    mock.v1 = 10

    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    p.moveInitialize(from: &mock, count: 1)
    p.pointee.v1 = 20

    // moveInitialize() したので p.deinitialize() は不要

    assert(address(mock) == address(p.pointee))  // Check same address.
    assert(p.pointee.v1 == 20)
  }

  testFinally()
}

initialize/ assign パターン

func testInitializeAndAssign() {
  print("# \(#function)")

  do {
    var mock = Mock()
    mock.v1 = 10

    var mock2 = Mock()
    mock2.v1 = 20

    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    p.initialize(from: &mock, count: 1) // mock1 で初期化する
    p.assign(from: &mock2, count: 1) // mock2 をアサインする
    p.pointee.v1 = 30

    // UnsafeMutablePointer は mock2 を指している
    assert(address(mock2) == address(p.pointee))  // Check same address.
    assert(mock2.v1 == 30)

    p.deinitialize(count: 1)
  }

  testFinally()
}

moveAssign パターン

func testMoveAssign() {
  print("# \(#function)")

  do {
    var mock = Mock()
    mock.v1 = 10

    var mock2 = Mock()
    mock2.v1 = 20

    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    p.initialize(from: &mock, count: 1) // mock1 で初期化する
    p.moveAssign(from: &mock2, count: 1) // mock2 を move する
    p.pointee.v1 = 30

    // UnsafeMutablePointer は mock2 を指している
    assert(address(mock2) == address(p.pointee))  // Check same address.
    assert(mock2.v1 == 30)

    // move したので p.deinitialize() は不要
  }

  testFinally()
}

Array パターン

func testArray() {
  print("# \(#function)")

  do {
    var array: [Mock] = [Mock(10), Mock(20), Mock(30)]

    let p = UnsafeMutablePointer<[Mock]>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    // Array から UnsafeMutablePointer を初期化する
    p.moveInitialize(from: &array, count: 1)
    p.pointee[0] = Mock(40)
    p.pointee[1] = Mock(50)
    p.pointee[2] = Mock(60)

    assert(array.count == p.pointee.count)
    assert(address(array as AnyObject) == address(p.pointee as AnyObject))
    assert(array[0].v1 == 40)
    assert(array[1].v1 == 50)
    assert(array[2].v1 == 60)
  }

  testFinally()
}

AudioBufferList パターン

func testAudioBufferList() {
  print("# \(#function)")

  do {
    let abl = AudioBufferList.allocate(maximumBuffers: 1)
    free(abl.unsafeMutablePointer) // AudioBufferList を allocate() したあとは free() が必要
  }
}

各テストケースを実行する

上記のテストをまとめて繰り返し実行し、エラーとメモリリークがないことを確認する

while true {
  autoreleasepool {
    testAllocateAndDeallocate()
    testInitializeAndDeinitialize()
    testInitializeAndMove()
    testMoveInitialize()
    testInitializeAndAssign()
    testMoveAssign()
    testArray()
    testAudioBufferList()

    Thread.sleep(forTimeInterval: 1)
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む