20200120のSwiftに関する記事は12件です。

Github ActionsでiOSアプリのテストの結果をSlackに通知したい!

Github ActionsとSlackを連携したい!

以前投稿したGithub ActionsでiOSのプロジェクトをビルドしたい!でとりあえずGithubにPushして、ビルドとテストが実行されるところまではできるようになりました。
そうすると、今度はその結果の通知がほしくなります。そこで今回はActionの実行結果をSlackに通知できるようにしてみたいと思います。

すでにSlackに通知できるActionがあるじゃあないか、、と思ったら、、

早速GithunのMarketPlaceで「Slack」と検索すると、よさげにSlackへ通知できそうなActionがいっぱい出てきました。
スクリーンショット 2020-01-20 22.22.29.png

そこの先頭にある「Slack Notify」を使ってみることに。
瞬殺じゃん、と思い、実行してみると、エラーが表示された。
スクリーンショット 2020-01-20 22.26.08.png
エラーをみると、このActionはLinuxのOSでしか動かせないようです(理由ははっきりわかっておりません、、、)。
検索結果の上位にある他のActionもいくつか試しましたが、同じエラーが出ました。iOSアプリをビルドする以上、MacOSの指定は必須なので、仕方なくWebhookのURLを直叩きすることにしました。

WebhookのURLをcurlで実行して、Slackに通知を送る。

WebhookのURLの取得はこちらを参考にしました。
あとはそれを使ったActionを実行するだけです。

# Notify to slack when build and test succeeded
- name: Slack success notification
 - run: |
   curl -X POST --data-urlencode 'payload={
     "text": ":tada::tada::tada:${{github.repository}}: Build and Test succeeded!:tada::tada::tada:"
    }' ${{secrets.SLACK_WEBHOOK_URL}}

github.repositoryは「{ユーザー名}/{リポジトリ名}」の形式で文字列を取得できる環境変数です。
先ほど取得したSlackのWebhookのURLはGithubのリポジトリのSecretsに保存しています。
ここに登録することで、PublicリポジトリがForkされても、そのSecretsの値はコピーされません。WebhookURLはそれを知っていれば、誰でもそれを設定したチャネルに投稿できてしまうので、ハードコーディングしないようにしましょう。登録した値は、secrets.{登録したkey名}で取得できます。
実行して、ビルドやテストが成功すると、以下のようにちゃんとSlackに通知が届きました。
スクリーンショット 2020-01-20 22.45.15.png
ちなみにエラーの時は以下のように設定しています。

##### Run build action #####
# Notify to slack when build failed
- name: Slack build failure notification
- if: failure()
run: |
   curl -X POST --data-urlencode 'payload={
      "text": ":fire::fire::fire:${{github.repository}}: Build failed..:fire::fire::fire:"
   }' ${{secrets.SLACK_WEBHOOK_URL}}
##### Run unit test action #####
# Notify to slack when test failed
- name: Slack test failure notification
if: failure()
run: |
   curl -X POST --data-urlencode 'payload={
      "text": ":fire::fire::fire:${{github.repository}}: Test failed..:fire::fire::fire:"
   }' ${{secrets.SLACK_WEBHOOK_URL}}

if: failure()を入れることで、直前のActionが失敗した時に実行されるようになるになります。参考
ビルド失敗時とテスト失敗時の結果は以下の通りです。
スクリーンショット 2020-01-20 22.53.34.png
本当はビルドに失敗した場合は、「Build failed..」のメッセージだけが表示されるようにしたいのですが、今のところやり方がわからず、、(知っている方はぜひおしえていただけると!)

最後に

Slackに通知できるようになったので、これである程度業務でも使えるようになったかなと思います。
あとはSlackに通知されるメッセージをもう少し見やすくしたり、Github Actionsへのリンクを貼ったりと、カスタムできればと思います。
このサイトが参考になりそう!
これから自動デプロイなどより充実させていきたいです。

テストで使用しているリポジトリ

https://github.com/u5-03/Swift-github-actions
Publicリポジトリであれば、Github Actionsは無料で使用できるので、検証で使用する場合はPublicリポジトリで!

参考資料

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

UITableViewでセルを選択不可にする方法

環境

swift 5
xcode 11.3

2020年1月20日現在

動機

簡単なのですが、すぐに忘れてしまうので。

メモメモ

方法

selectionがNoneになっていれば選択することができなくなります。

スクリーンショット 2020-01-20 21.35.37.png

スクリーンショット 2020-01-20 21.35.45.png

コードだと

コードだと以下のように書くようです。

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

signin with apple 実装するときの注意点まとめ

対応バージョン

iOS13以上、iOS13以下は対応しないため、別々のデザイン準備する必要があります。

取得できるユーザーデータ

  • email
    • ユーザーの選択でランダム生成されたメール渡せる可能性があります
  • full name
    • ユーザーの編集可能で、apple id登録された名前とは限らない

https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential?language=objc

sign in 初回と2回目の違い

sign in 初回はメールと名前のユーザーデータ取れますが、その以後sign inする場合は取得できなくなります。
初回取得成功後key chainなどに保存し、登録成功後削除したほうが一番無難。

 let request = ASAuthorizationAppleIDProvider().createRequest()
 request.requestedScopes = [.fullName, .email]
 let controller = ASAuthorizationController(authorizationRequests: [request])
 controller.delegate = self
 controller.presentationContextProvider = self
 controller.performRequests()

extension AppleSigninService: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let credentials = authorization.credential as? ASAuthorizationAppleIDCredential else {
            let error = NSError(domain: "jp.huiping192.signInWithApple", code: -1000, userInfo: nil)
            signInSubject?.onError(error)
            return
        }

        // アプリ起動時ログイン状態チェックのためcredentials.user keychainに保持する
          saveUserId(credentials.user)
        // mailとnameある場合一時保持、登録成功後に削除
          saveUserInfo(nickname: credentials.fullName?.nickname, email: credentials.email)
    }

    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // error handing
    }
}

AppleID使用停止検知

「設定」 -> 「{Apple ID}」->「パスワードとセキュリティ」-> 「Apple IDを使用中のApp」 -> 「{app name}」-> 「Apple IDの使用を停止する」

ユーザーは上の手順でSign in with Apple使用停止することが可能なので、アプリ側にチェックする必要があります。

  • アプリ起動時
ASAuthorizationAppleIDProvider().getCredentialState(forUserID: appleUserID) { credentialState, _ in
            if credentialState != .authorized {
                // logout
                User.current.logout()
            }
 }
  • アプリ起動中
NotificationCenter.default.rx.notification(ASAuthorizationAppleIDProvider.credentialRevokedNotification).subscribe(onNext: { _ in
                // logoutしとく
                User.current.logout()
            }).disposed(by: disposeBag)

AppleID使用停止後再度登録

新しいユーザーを扱い、token、ユーザー情報新しく取得できる。

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

【Swift】Must translate autoresizing mask into constraints to have _setHostsLayoutEngine:YES.でクラッシュする

概要

iPhone6s (iOS9.3) で実行すると

掲題のエラー

Must translate autoresizing mask into constraints to have _setHostsLayoutEngine:YES.

と出てクラッシュする問題。

解決方法

スクリーンショット 2020-01-20 17.16.11.png

どうやらいろんな原因でこのクラッシュが起きるみたいですが僕の場合は
クラッシュ対象となるクラスのxib上で赤枠で囲っているプルダウンを

AutomaticからTranslates Mask Into Constraintsに変更

するだけ、で直りました。
なんで、これでなおるかは要調査ですが。

他の事例。
https://qiita.com/matsuyoro/items/9219c23a70c012d6e3e5
https://qiita.com/kaktaam/items/1c07f6bf003cee77cc31

誰かのお役に立てば。

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

【Swift】dyld: Library not loaded: /usr/lib/libauto.dylib で古いSimulatorで実行出来ない

概要

dyld: Library not loaded: /usr/lib/libauto.dylib
Referenced from: /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation 
Reason: no suitable image found. Did find: /usr/lib/libauto.dylib: mach-o, but not built for iOS simulator

と言うエラーが出てきて古いSimulatorで実行出来ないと言うものだ。

環境

Xcode: 11.3
対象Simulator: iOS Simulator6S
対象OS: 9.3

解決方法

https://stackoverflow.com/questions/55389080/xcode-10-2-failed-to-run-app-on-simulator-with-ios-10

sudo mkdir '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift'

このコマンドを叩くだけ。

Simulatorはruntimeと言うルートを元に動作しているが、
そもそものruntimeがないため起こっているみたい。
だから、強制的に作ってやろうよみたいな感じですかね。

誰かのお役に立てば。

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

Swift で 画像 (UIImage ) の リサイズ と トリミング (切り抜き) を一度の処理で行う。

0.はじめに

Swift で画像の切り抜きとリサイズを一度の処理で行いたかったので、やってみました。

以下の記事を参考にさせて頂きました。

感謝 ♪♪♪

?‍♂️?‍♂️?‍♂️

1.コード

今回は、画像を 0.5 倍にリサイズしつつ、中心部分を正方形に切り抜いてみました。

    var _image: UIImage = image
    var compress: CGFloat = 0.5
    var oneside: CGFloat = _image.size.width < _image.size.height ? _image.size.width : _image.size.height
    let origin: CGPoint = _image.size.width < _image.size.height
        ? CGPoint(x: 0.0, y: (_image.size.width - _image.size.height) * 0.5 * compress)
        : CGPoint(x: (_image.size.height - _image.size.width) * 0.5 * compress, y: 0.0)
    UIGraphicsBeginImageContextWithOptions(CGSize(width: oneside * compress, height: oneside * compress), false, 0.0)
    _image.draw(in: CGRect(origin: origin, size: CGSize(width: _image.size.width * compress, height: _image.size.height * compress)))
    _image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
■ リサイズ

まず、以下の処理で、出力先のサイズを指定します。

    var oneside: CGFloat = _image.size.width < _image.size.height ? _image.size.width : _image.size.height
    UIGraphicsBeginImageContextWithOptions(CGSize(width: oneside * compress, height: oneside * compress), false, 0.0)
■ トリミング (切り抜き)

そして、以下の処理で、画像をトリミング (切り抜き) します。

    let origin: CGPoint = _image.size.width < _image.size.height
        ? CGPoint(x: 0.0, y: (_image.size.width - _image.size.height) * 0.5 * compress)
        : CGPoint(x: (_image.size.height - _image.size.width) * 0.5 * compress, y: 0.0)
    _image.draw(in: CGRect(origin: origin, size: CGSize(width: _image.size.width * compress, height: _image.size.height * compress)))

重要なポイントは、

draw 関数の引数に指定する CGRect の値 (origin も、size も) を出力後の値で設定する

こと。

99.ハマりポイント

  • 上述しましたが、draw 関数の引数に指定に結構手間取りました…。

???

XX.まとめ

以上、ご参考になれば ♪♪♪

???

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

CustomCellに置いたButtonの選択状態によってTableViewの処理を変える

タップしたButtonが乗ってるCellの情報をFirestoreに保存したい。
その想いから、こんなややこしい(?)記述方法になってしまいました。ご了承下さい。
Firestoreの部分は他記事にDelegate!

開発環境

・Xcode 11.3
・Swift 5.0

UIButtonのextensionを作る

CustomCell.swift
extension FaveButton {
    func switchAction(onAction: @escaping ()->Void, offAction: @escaping ()->Void) {
        switch self.isSelected {
        case true:
            //ONにする時に走らせたい処理
            onAction()
        case false:
            //OFFにする時に走らせたい処理
            offAction()
        }
    }
}

ButtonActionにDelegateを設定する

CustomCell.swift
import UIKit

protocol CellDelegate: AnyObject {
    func didTapButton(cell: CustomCell) //ON
    func didUnTapButton(cell: CustomCell) //OFF
}
class CustomCell: UITableViewCell {
    weak var delegate: CellDelegate?

@IBAction func didButtonTapped(_ sender: UIButton) {
        sender.switchAction(onAction: {
//TableViewController.swiftのdidTapButton()に委任
            self.delegate?.didTapButton(cell: self)
        },offAction: {
//TableViewController.swiftのdidUnTapButton()に委任
            self.delegate?.didUnTapButton(cell: self)
        })
    }
}
TableViewController.swift
import UIKit

class TableViewController: UITableViewController, CellDelegate /*追記*/ {

func didTapButton(cell: CustomCell) {
     print("ON")
    }

func didUnTapButton(cell: CustomCell) {
        print("OFF")
    }
}

参考

【iOS】UIButtonにON/OFFのスイッチ処理を1行で書く【Swift】

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

swift サーバへPOST送信のimage無しとimage有りの処理

image無しのpost送信

    func myMessageUploadRequest(){
        var url = URL(string:  "http://受取処理.php");
        print(url)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        var req = NSMutableURLRequest(url: url!)
        var token = "lastName=\(commentText.text!)&TypeName=Message&groupid="+textgid+"&userId="+userid!
        req.httpMethod = "POST"
        req.httpBody = token.data(using: String.Encoding.utf8)
        var task = session.dataTask(with: req as URLRequest, completionHandler: {
            (data, resp, err) in
            print(resp!.url!)
            print(NSString(data: data!, encoding: String.Encoding.utf8.rawValue))
        })
        task.resume()
    }

image有りの場合

    func myImageUploadRequest(){
        print(globalmainurl)
        let myUrl = URL(string: globalmainurl! + "/swift_receive_app.php");
        let request = NSMutableURLRequest(url:myUrl!);
        request.httpMethod = "POST";
        print("ok2xxxx"+globalauthid!+globalmainurl! + "/swift_receive_app.php")

        let param = [
            "groupid"  : textgid,
            "firstName"  : "Message",
            "lastName"    : commentText.text!,
            "userId"    : globalauthid!
        ]

        let boundary = generateBoundaryString()

        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")


        let imageData = UIImageJPEGRepresentation(ImageView.image!, 0.5)

        if(imageData==nil)  {
            print("error")
            return;
        }
        print("ok3")

        request.httpBody = createBodyWithParameters(param, filePathKey: "file", imageDataKey: imageData!, boundary: boundary)

        let task = URLSession.shared.dataTask(with: request as URLRequest) {
            data, response, error in
            if error != nil {
                print("error=\(error)")
                return
            }
            // レスポンスを出力
            print("******* response = \(response)")
            let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
            print("****** response data = \(responseString!)")

            DispatchQueue.main.async(execute: {
                //アップロード完了
                self.ImageView.image = nil;
            });
        }
        task.resume()     
    }

    func createBodyWithParameters(_ parameters: [String: String]?, filePathKey: String?, imageDataKey: Data, boundary: String) -> Data {
        let body = NSMutableData();
        print("ok4")

        if parameters != nil {
            for (key, value) in parameters! {
                body.appendString("--\(boundary)\r\n")
                body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
                body.appendString("\(value)\r\n")
            }
        }
        let filename = "user-profile.jpg"

        let mimetype = "image/jpg"

        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
        body.appendString("Content-Type: \(mimetype)\r\n\r\n")
        body.append(imageDataKey)
        body.appendString("\r\n")
        body.appendString("--\(boundary)--\r\n")

        return body as Data
    }

    func generateBoundaryString() -> String {
        return "Boundary-\(UUID().uuidString)"
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

玩玩 @propertyWrapper

看了這兩篇文章動手做之後簡單來做個筆記

規格 提案

SE-0258 Property Wrappers

小聲說:寫這篇的時候我還沒詳細讀完所有的內容

玩過之後看到的優點

  • 過去以來需要寫在 didSet 裡面的邏輯有辦法再封裝,讓宣告的地方能夠更加乾淨
  • 似乎可以解決在設定初始值無法觸發 didSet 的問題

語法

對我來說,直覺是有 Java 中 annotation 的 fu
詳細說明可以參照

定義

@propertyWrapper
struct Trimmed {
    private(set) var value: String = ""

    // 根據文件指出,「必須」定義這個 property 
    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

wrappedValue 通常會是一個 computed value ,用這樣的寫法的話,在 set 裡面可以拿到在 init 中被設定的值,而 get 中回傳的值則是放處理完成的值。

使用方式

先定義一個簡單的 struct 並把剛剛定義的 property wrapper 放在一個屬性前面

struct Post {
    @Trimmed var title: String
}

使用之後如下:

let post = Post(title: " B A A ")
print(post.title) // "B A A"

因為有透過 property wrapper 的處理,這時候在 console 輸出的內容會是 B A A ,而不是前後有空白的 B A A

目前還沒摸清楚可以在實際專案中怎麼用,不過看來是個可以拿來做適度封裝的好工具。
接下來還要繼續去看 Projected Value 到底是什麼東西和可以怎麼用,看懂之後再來多寫一篇。

環境

  • Xcode 11.3.1
  • Swift 5.1
  • Playground

參考

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

Swift for TensorFlowでSinGAN

SinGAN

QiitaでもSinGANの論文が話題になって久しいですが、Swift for TensorFlowでSinGANを実装してみました。
https://github.com/t-ae/singan-s4tf

公式の資料は以下

公式実装との違い

だいたいReadmeに書いたんですが、工夫した点は大事なのでここにも書いておきます。

BatchNormをやめてInstanceNorm導入

SinGANは画像一枚を入力とするので、訓練時にはその画像自体の統計量で正規化する=InstanceNormと同等の振る舞いになります。
しかし推論時には、BatchNormは入力ではなく訓練時に得た統計量を用いて正規化します。バッチサイズがある程度ある場合は訓練時にもバッチ内統計量になるので問題ないのですが、上述の通りバッチサイズが1なので、訓練時と推論時で正規化方法が大きく異なるということになってしまいます。

これを避けるため、私の実装ではInstanceNormを用いることにしました。これだと訓練時・推論時で同様の正規化を行うことになります。

WGAN-GPを使わない

オリジナル実装ではWGAN-GPを用いています。

issueに書いたのですが、公式の実装はWGAN-GPの論文に従ったgradient penaltyになっていないのではないかと考えています(レスがなくて結論まで至れてませんが)。

またSwift for TensorFlowはまだ高階微分を実装していないため、そもそもgradient penaltyを用いるような方法は使用不可能です。

そういうわけで、ここはオリジナル実装から離れてWGAN-GPをやめることにしました。

Spectral Normalizationの導入

GANの訓練でよく使われているのがWGAN-GPとSpectral Normalizationです。最近は両者の併用で更に安定化できるという論文も発表されていました。

https://tech.preferred.jp/ja/blog/smoothness-and-stability-in-gans/

Spectral Normalizaitonには高階微分が必要なく、Swift for TensorFlowでも使用することができるので、これを用いることにしました。

この記事とは関係ないですが、Spectral Normalizationを使った一般的(?)なGANも書いてます。

結果

学習・生成した結果のうちいくつかを掲載します。ここに載せていない結果は以下に置いてあります。
https://github.com/t-ae/singan-s4tf/tree/master/Results

上に書いた別のGANのほうでUbuntu機が埋まっていたのでMacBookProで学習させたのですが、GPUなしでも数時間〜半日くらいで学習できます。
乱数のシードは固定しているので、ここに掲載した結果は手元で実行すれば再現できるはずです。

複数サイズは4つのアスペクト比を、高解像度化は4/3倍の拡大を5回分(約4倍まで)の内容を掲載しています。

balloons

オリジナルの入力画像と最構成結果
オリジナル 最構成
balloons.png reconstruction.png

小さいバルーンが見逃されています。SinGANは小さなスケールから徐々にアップスケールしてくるため、直前の解像度では何もなかったところに小さなオブジェクトが出てくるということがあります。

複数サイズ

multisize_181x181.png
multisize_181x369.png
multisize_293x181.png
multisize_592x181.png

ランダム生成においてはバルーンの形がかなり崩れてしまっています。大域的な歪みは低解像度からの生成の歪みが積み重なっていると考えられるので、各段階のトレーニングステップ数を増やす等で改善できそうな気がします。

33039_LR

オリジナルの入力画像と最構成結果
オリジナル 最構成
33039_LR.png reconstruction.png

元が小さいので割ときれいにできています。

高解像度化

super_resolution1.png
super_resolution2.png
super_resolution3.png
super_resolution4.png
super_resolution5.png

これは実験した中では最も綺麗にできた結果だと思います。
SinGANの高解像度化は最大の解像度で学習したモデルを用い、拡大→モデルに通す→拡大→モデルに通す……と繰り返して行きます。この方法ではフラクタル的に細部が生成されるのではないかと考えられます。そのため岩の表面のような自然物はSinGANでの生成に向いているのでしょう。

birds.png

オリジナルの入力画像と最構成結果
オリジナル 最構成
birds.png reconstruction.png

バルーンと同じく小さな鳥が消えてしまっています。色合いもちょっと違っています。

複数サイズ

multisize_181x181.png
multisize_181x369.png
multisize_293x181.png
multisize_592x181.png

オリジナルは縦方向の変動が大きく、横方向の変動が小さいため、横に長い画像のほうがよくできている印象です。

lightning1
オリジナル 最構成
lightning1.png reconstruction.png
複数サイズ

multisize_181x181.png
multisize_181x369.png
multisize_293x181.png
multisize_592x181.png

地面まで届かずに途中で切れてる稲妻が複数あります。

高解像度化

super_resolution1.png
super_resolution2.png
super_resolution3.png
super_resolution4.png
super_resolution5.png

これは33039_LRで触れたフラクタル的に細部が生成されるというのがよく分かる結果になっています。解像度が上がるに従って細い稲妻が枝分かれして増えています。

まとめ

公式実装がかなり読みにくくて苦労しましたが、なんとかそれらしい結果を出すところまで行けました。
ハイパラ調整はあんまりやっていないのですが、もっと詰めればバルーンの崩れていたのあたりは綺麗に出せるようになるんじゃないかと思っています。

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

[Swift] IntやDoubleなどの数字オブジェクトを2倍にする関数でジェネリクスを使ってみた

概要

業務でInt型やDouble型を単純に2倍する関数を作りたい時に戸惑いましたのでググってみました。

    /// これを型ごとに作ると、関数が増えてしまって冗長なので書きたくない
    func multipleValue(_ value: Int) -> Int {
        return value * 2
    }

    func multipleValue(_ value: Double) -> Double {
        return value * 2
    }

    func multipleValue(_ value: CGFloat) -> CGFloat {
        return value * 2
    }

    multipleValue(2) // 4
    multipleValue(3.14) // 6.28
}

これをクールに一つの関数にまとめたいと思いました。
さてどうすればいいのかを考えるのが今回のテーマです。

ジェネリクスを使う

すぐに思いつくやり方は、ジェネリスクでパラメータを一つにまとめる方法です。

ジェネリスクはSwift のドキュメントでこう記されています。

英語版Swiftドキュメント

ジェネリクスの用法としては
[Swift]ジェネリクスについて

ジェネリック関数 | ジェネリクス | Swift

が参考になります。

それはいいとして、ジェネリクスを使ってこんな風に書きたいです。

    /// Tがジェネリックパラメータ
    func multipleValue<T>(_ value: T) -> T {
        return value * 2
    }

    multipleValue(2) // 4
    multipleValue(3.14) // 6.28
}

これでいけるかと思ったのですがビルドエラーになってしまいました。

image.png

エラーの内容は

Binary operator '*' cannot be applied to operands of type 'T' and 'Int'

どうやらジェネリクスに二項演算子「*」などのoperatorが使えないみたいなエラーでした。

そのため、てっきりジェネリクスで数値を2倍にできないのかと諦めかけましたが、
エラーの文章からググってみましたら解決策が見つかりました。

Binary operator '+' cannot be applied to two 'T' operands

ということで Numeric protocol に準拠させたら使えるそうです。

    /// Tがジェネリックパラメータ
    func multipleValue<T: Numeric>(_ value: T) -> T {
        return value * 2
    }

    multipleValue(2) // 4
    multipleValue(3.14) // 6.28
}

これで目標のあらゆる数値を単純に2倍にする関数が作れました。

Numeric とは何なのか

Numeric

乗算をサポートする値を持つ型だそうです。

Numericプロトコルを汎用制約として使用すれば、標準ライブラリ内の任意の数値型を操作する関数が作れるようになるとの事でした。

これで数値を操作する汎用メソッドが作れます。

と調べた後にこれについて詳しい資料がありましたので最後に貼って終わります。

Protocol-Oriented Integers に想うジェネリックプログラミングの未来

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

Github ActionsでiOSのプロジェクトをビルドしたい!

CI環境として、Github Actionsを使ってみたい!

アプリを開発する時に、以下のようなことを自動化してみたいと思いました。そこで調べたことを備忘録として、まとめてみました。

  • プッシュした時に、ビルドやテストを実行する。
  • プルリクを出した時に、ビルドやテストに失敗している時はマージできないようにする。
  • developブランチにマージされたタイミングで、開発用のipaをビルドする。
  • releaseブランチにマージされたタイミングで、デプロイする。

そこでCI環境を使ってみようと思い、Betaが外れたばかりのGithub Actionsを使ってみようと思い、調べてみました。

使い方

1.Githubのプロジェクトを開き、「Actions」タブを開く。そうすると、以下のような画面が表示される。
右側の「Set up a workflow yourself」を選択する。
スクリーンショット 2020-01-18 23.53.01.png

2.そうすると、Github actionsで使用するworkflowのymlファイルのテンプレートが表示されます。

スクリーンショット 2020-01-18 23.53.36.png
ちなみにこのままテストをすると、失敗します。(swiftコマンドが実行されるので、当然ですが、、、)
スクリーンショット 2020-01-18 23.57.36.png
スクリーンショット 2020-01-18 23.57.50.png

3.なのでテンプレートのymlファイルを修正する。とりあえず最低限動くように、以下のように修正しました。
scheme名を自分のプロジェクトのものに変更してもらえれば、そのまま使えると思います。

swift.yml
name: Swift

on: [push]

jobs:
  build:

    runs-on: macOS-latest

    steps:
    - uses: actions/checkout@v1
    # Select Xcode version
    - name: Select Xcode version
      run: sudo xcode-select -s '/Applications/Xcode_11.3.app/Contents/Developer'
    - name: Show Xcode version
      run: xcodebuild -version
      # Run build
    - name: Build
      run: xcodebuild
            -scheme Swift-github-actions
            -sdk iphonesimulator
            -configuration Debug
            build
      # Run unit test
    - name: Run tests
      run: xcodebuild
            -scheme Swift-github-actions
            -sdk iphonesimulator
            -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max'
            clean test

4.修正が終わったら、コミットする。「Start commit」からコミットします。
スクリーンショット 2020-01-18 23.54.06.png

5.「Actions」タブを開き、workflowの実行結果を確認する。きっと成功しているはずです!
ちなみにデフォルトではリポジトリへのPushがworkflow実行のトリガーになっています。
スクリーンショット 2020-01-20 0.28.30.png

Swift Package Manager(SPM)を使ってみる。

上記のworkflowであれば、SPMをインストールしても、そのままビルドとテストが成功するはずです。
以下、SPMでRxSwiftをインストールしてから、Pushしたものです。無事成功しています。
スクリーンショット 2020-01-20 0.31.27.png

やってみた感想

Bitriseの使用も検証しましたが、普段使っているGithubのサービス内で完結できるのは魅力的かなと思いました。
最低限のworkflowしか記述していないので、今後追加して、最初に書いたようなこともできるようにしたいです。
修正点があれば、ご指摘いただけると。

テストで使用しているリポジトリ

https://github.com/u5-03/Swift-github-actions
Publicリポジトリであれば、Github Actionsは無料で使用できるので、検証で使用する場合はPublicリポジトリで!

参考資料

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