fastlane で空のgit commitを作る

はじめに

fastlane で git commit --allow-empty が必要になってやり方を調べました。

バージョン

fastlane 2.85.0

方法

以下のどちらかで。他にもあるかもしれない。

# test.rb
lane :test do
  # Actions.sh 使う方法
  Actions.sh('git commit --allow-empty -m"Actions.sh"')

  # git_commit 使う方法
  git_commit(path:"--allow-empty", message:"git_commit")
end

おまけ

fastlane 使い慣れてないので、最初はActions.sh() を思いつかず、git_commit アクションにはそんなオプションなくて、困ったなーとなりました。

git_commit - fastlane docs

同僚氏にプルリクしようとか煽られつつ、慣れないRubyコード読んでみたら、渡したファイルパスの存在チェックとかしてなさそうだったので、だったらオプション渡せばいけるじゃんとなりました。

find+xargsのよくある処理で空白を含む名前のファイルを扱う

はじめに

findxargsを使って、複数ファイルをまとめて処理するのはよくやると思います。

が、とくにオプションとか考えずにやると空白混じりのファイル名があった時に、エラーで怒られます。

space_files $ ls -la
total 24
drwxr-xr-x   5 sumida  staff   170  2 25 18:26 .
drwxr-xr-x  52 sumida  staff  1768  2 25 18:18 ..
-rw-r--r--   1 sumida  staff   331  2 25 18:24 hoge 1.txt
-rw-r--r--   1 sumida  staff   515  2 25 18:24 hoge2.txt
-rw-r--r--   1 sumida  staff   689  2 25 18:26 hoge3.txt
space_files $ find . -type f|xargs wc -l
wc: ./hoge: open: No such file or directory
wc: 1.txt: open: No such file or directory
      13 ./hoge2.txt
      25 ./hoge3.txt
      38 total
space_files $

これは、xargsが空白文字を区切り文字として使用するためです。

上の例だと、wc -l hoge 1.txtとなることを期待していますが、実際にはwc -l hogewc -l 1.txtに分割されてしまい、そんなファイルはないので怒られます。

解決策

find-print0オプションとxargs-0オプションを指定すれば解決。

space_files $ find . -type f -print0 |xargs -0 wc -l
       8 ./hoge 1.txt
      13 ./hoge2.txt
      25 ./hoge3.txt
      46 total
space_files $

このオプションは、二つとも区切り文字をヌル文字にするオプションなので、空白が含まれていても1つのファイル名として扱ってくれます。

画像とラベルを縦に並べるUIButtonのカスタムクラス

はじめに

たまに必要になるので、こんな感じに画像とラベルを縦に並べるUIButtonのカスタムクラスを作りました。

f:id:y_sumida:20180122225341p:plain

contentEdgeInsetsとか毎回調整するのめんどくさいですよね。

探せばライブラリとかもあるとは思うけど勉強も兼ねて車輪の再発明です。

コード

github.com

画像とラベルのサイズに合わせて、縦に並べてセンタリングします。

Storyboardからもコードからも使えるようになってます。

コードからは上下と左右のマージンが指定できます。

不具合

  • width指定、leadingEdge/trailingEdge指定などで、必要な幅より狭い幅の場合に表示が崩れる。
    • いろいろ試したけど、今のところうまくいってない。
    • drawに入ってきた時点でラベルの後ろが切れてたりするので。

感想など

  • もうちょっとスマートに書きたかった。
  • contentEdgeInsetsとかの親クラスの変数を変更不可にするのに手間取った。
  • UIButtonに縦並びとかアイコンとラベル入れ替えるオプション欲しい。

参考

UIViewのライフサイクルについて、以下の記事が大変参考になりました。

developers.eure.jp

2017年振り返り

個人の日記なので2017年の振り返り。

お仕事

ご縁があって11月から渋谷にある事業会社でiOSアプリ開発者として働いている。

春先、前年から開発していたiOSアプリのリリース延期が決まりモチベーションが下がっていたところに、今の会社の人に声をかけてもらい、会社見学やコード課題などを経て働くことになった。

2ヶ月経過した感想としては、今のところ概ね楽しく働けている。

完璧な職場なんてあるはずもないけど、月の残業時間が10時間未満だったり、希望したREALFORCEが特に何も言われることもなく出社時に用意されている程度にはホワイトな職場。

子持ちの自分にとっては比較的自由な勤務体系なのも嬉しいところ。

コードレビューやCI環境なども整っていたり改善が進んでいたりしていて学ぶことが多い。

少人数なチームだけど優秀な人ばかりなので、自分もがんばらねばと思っている。

勉強会など

久しぶりにiOSDCという大きめなイベントに参加して多くの刺激をもらった。

子どもがいるとなかなか参加しにくいけど厳選して参加していこうと思う。

プライベート

"Write Code Every Day" を実践しようと思い立ってやってみた。

仕事のコード除くとこんな感じ。後半息切れ気味な感は否めない。

f:id:y_sumida:20171231152722p:plain

github.com

結果としては毎日というわけにはいかなかったけど、日々ちょっとでもコードを書く、エディタに向かうという習慣はできた気がする。

どうしても作りたいものがあってプログラミングしている人間ではないのでサンプルコードや検証コードがほとんどだけど、転職の際にも多少は役に立ってくれた。

やってみてわかったのは、時間がない中でやると圧倒的にインプットが減るということ。

子どもたちを寝かしつけた後の22時〜24時の最大2時間程度が貴重な勉強時間なので、バランス良くインプット・アウトプットができるように改善したい。

立てていた目標

  • Qiita:Teamをディレクターさん、デザイナーさんも使うようになってきたのでそっち向けの記事も書く
    • 前職で多職種向けの記事を書いたけど十分じゃなかった
  • Swift環境での自動テストを導入する
    • できなかった
  • iOSアプリ開発に偏った1年だったので、Web開発もうちょっとがんばる
    • できなかった
  • 何かしらアウトプットする
    • "Write Code Every Day"を不完全ながら実践

トータルで見ると、あんまり進捗よくなかった。無念。

来年の目標

  • Androidで何か作ってみる
  • UIKitの知識を深める
  • インプットを増やしてインプット・アウトプットのバランスを改善する

たくさん目標立ててもできないのでこんなもんで。

Swiftでクロージャが入れ子の場合のキャプチャリスト

はじめに

Swiftのクロージャには循環参照を回避するための仕組みとしてキャプチャリストがあります。

最近、キャプチャリストがあるのにメモリリークするというバグに遭遇して、自分の認識が間違っていたことが分かったのでメモしておきます。

結論としては、クロージャ入れ子の場合には、直接使っていなくても外側にキャプチャリストを書いておかないと強参照になります。

バージョン

Swift 4.0

サンプルコード

class Hoge {
    deinit {
        print("deinit")
    }
    let n: Int
    
    init(n: Int) {
       self.n = n
    }
    // 内側だけweak
    lazy var closure1: () -> Int = {
        return {[weak self] in return (self?.n)! * 2}()
    }
    // 外側だけweak
    lazy var closure2: () -> Int = { [weak self] in
        return {return (self?.n)! * 2}()
    }
    // 両方weak
    lazy var closure3: () -> Int = { [weak self] in
        return {[weak self] in return (self?.n)! * 2}()
    }
    // 内側だけunowned
    lazy var closure4: () -> Int = {
        return {[unowned self] in return self.n * 2}()
    }
    // 外側だけunowned
    lazy var closure5: () -> Int = { [unowned self] in
        return {return self.n * 2}()
    }
    // 両方unowned
    lazy var closure6: () -> Int = { [unowned self] in
        return {[unowned self] in return self.n * 2}()
    }
}


do {
    // deinit呼ばれない
    print("closure1")
    let hoge = Hoge(n: 100)
    print(hoge.closure1())
}

do {
    print("closure2")
    let hoge = Hoge(n: 100)
    print(hoge.closure2())
}

do {
    print("closure3")
    let hoge = Hoge(n: 100)
    print(hoge.closure3())
}

do {
    // deinit呼ばれない
    print("closure4")
    let hoge = Hoge(n: 100)
    print(hoge.closure4())
}

do {
    print("closure5")
    let hoge = Hoge(n: 100)
    print(hoge.closure5())
}

do {
    print("closure6")
    let hoge = Hoge(n: 100)
    print(hoge.closure6())
}

iOSで画像をリサイズして保存するときに気をつけること

はじめに

カメラロールからユーザーが選んだ画像をUIImageViewに表示してから、リサイズしてファイル保存する際に、ちょっとはまったのでメモ。

バージョン

  • Xcode9.0.1
  • Swift4.0

やりたかったこと

  • ユーザーが選択した画像ファイルを一定の幅にリサイズして保存しておき、非同期でアップロードする

起こった事象

  • iPhone SEだとアップロードできる画像が、iPhone Xだとサーバのファイルサイズ制限に引っかかった
  • ファイルを調べると、オリジナル画像よりもファイルサイズが大きくなっていた

コード

最終的にこんな感じのリサイズ処理にしました。

サンプルコードなので単純に縦横半分にしています。

    private func resize(image: UIImage) -> UIImage {
        // 縦横の画素数を半分にする
        let width = image.size.width * 0.5
        let height = image.size.height * 0.5
        // scale の設定が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()

        return resizeImage!
    }

原因

UIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat)scale パラメータに0.0を指定すると、デバイスにあわせて@2/@3の指定にしてくれます。

The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.

UIGraphicsBeginImageContextWithOptions(_:_:_:) - UIKit | Apple Developer Documentation

その結果、iPhon Xだと@3の画像となり、結果としてファイルサイズが大きくなってしまっていたのでした。

アプリ埋め込みのアイコンなどはデバイスにあわせた画像を用意しますが、ユーザーの写真などは原寸でいいと判断して、scale を 1.0指定することで解決しました。

UIVIewController.viewの生成タイミング

Instantiateというライブラリがあります。

github.com

StoryboardやXibを使いつつコンストラクタインジェクションできる素敵ライブラリなのですが、コードを読んでてよくわからなかったところがありました。

Instantiate/Storyboard+UIViewController.swift at master · tarunon/Instantiate · GitHub

このコードの self.view を参照しているところ。 左辺が _ となっているので何もしてないように見えます。

            if self is ViewLoadBeforeInject {
                _ = self.view
            }

ViewLoadBeforeInject というのはプロトコルで、定義はこんな感じ。

/// This protocol allow viewController force load view before call `inject`
public protocol ViewLoadBeforeInject {
    
}

コメントを読むと、 UIViewController.viewinject が呼ばれる前に強制的にロードするもののようです。

inject はコンストラクタ内で呼ばれるメソッドです。

色々読んでるうちに UIViewContoroller.view のコメントにたどり着き理解できました。

    open var view: UIView! // The getter first invokes [self loadView] if the view hasn't been set yet. Subclasses must call super if they override the setter or getter.

先程のコードはコンストラクタ中のコードであり、最初のアクセスなのでviewを生成してるようです。

ゲッターにこんな副作用持たせてるとは思わなかった。

このコードのおかげで、inject メソッドで UIViewContoroller に置いてある UILabel とかをセットするような場合に、このプロトコルを適用すればクラッシュしません。

内部の変数に保持して ViewDidLoad とかでセットするなら、このプロトコルは不要です。

うまいことできてる。