iOSのTODOアプリをポートフォリオとして11月1日までに実装するぞ

github.com

概要

初めましてmoaibleです。もあいとか、もあいぶるで呼ばれることもあります。

なんでこの記事を書こうかと思ったかというと、自分はこれまでプログラマを初めて

大体7年目になるんですがこれまで特に大きなアウトプットをしたこともなく、

仕事ばかりでコードを書いたり、ドキュメントを書くことが多かったです。

一応一時期は、

Qiitaで薄い内容の記事を量産したり、

iOSのアスペクト指向なObjCライブラリを作ったり、

Chatbotのお題でHubotの記事を執筆したり

(ちなみにペコッターの中の人と一緒の枠で書かせてもらいました)

上のようなことでちょいちょい活動はしてたんですが最近は全然外に向けて何かをすることってしてませんでした。

もちろん家に居てもコードを弄ってはいても自己完結して満足するばかりで、そのインプットで得た何かを外に出すってことはあんまりしておらず。

そこで、自分の名札代わりとなるような何かを残そうと思い自分で設定した11月1日の納期iOSアプリを開発していきます。

そしてここまでしないと散見しまくって横道に逸れるので言い逃れできないように宣言しておきます!

なんでTODOアプリ?

単純にTODOアプリであればプログラマ同士の共通認識としてコード見せた時に把握してもらい易いかな?という安直な理由です

こんな風に作っていきたい(お気持ち)

iOS

Swift 4

  • Xcode 9.0 (現時点で最新)

クリーンアーキテクチャ(風)

  • レイヤーを区切って役割を分離
    • Presenter
    • UseCase
    • Repository
    • DataStore
  • RxSwift.Observable単位な処理フローで進めていく

ログイン認証

  • Firebase Auth

アプリ内DB

  • Realm

開発環境整備

  • UnitTest / UITest、CIで回す
  • APIは仮でモックサーバーを用意
  • ドキュメントは自動生成でJazzyを利用

とりあえずこんな感じで

宣言通り11月1日にはdoneしてると言える品質で物を出してくぞ!!

Swiftが3系から4系に上がる現時点での各種パッケージマネージャを比較

Swiftが3系から4系に上がる現時点での各種パッケージマネージャを比較

iOS11が発表されXcode 9 betaをダウンロードでき、公式でSwift4.0が目前の中ついこの間にSwift.orgからマイグレーションガイドの項目が追加されていました。

Swift.org - Migrating to Swift 4

ここによるとSwift3.2、またはSwift4.0でのライブラリ配布でない場合はソースコードから直接依存させた方が良いとあります。

というのもSwift3.2からSwift4.0は互換性はあるのですが、Swift3.1以前は互換性が無いとのこと。

またSwift周りの環境が変わってきそうな中でライブラリ管理で使えるパッケージマネージャの予習・復習がてらに比較してみました。

iOS / macOSから始まりOSSになったSwiftですが、パッケージマネージャとして有名なのは以下3種類になります。

  • CocoaPods
  • Carthage
  • Swift Package Manager

それぞれ用途ごとに3つを比較しながら詳解していきます。

前提

  • Swift 4.0
    • Apple Swift version 4.0 (swiftlang-900.0.43 clang-900.0.22.8)
  • Xcode 9 beta 2
  • coocapods 1.3.0.beta.2
  • carthage 0.23.0

ソースコードの公開先

Swiftの各種パッケージマネージャは全てOSSとして公開されていて、GitHubからソースコードを確認することができます。

CocoaPods

github.com

Rubyで実装されていて、そのためかCocoaPods自体もRubyGemsの文化がそのまま引き継がれた形式になっています。

CocoaPods専用の巨大なリポジトリが存在しており、そこから指定のライブラリを取得してくる流れになっています。

Carthage

github.com

Swiftで実装されており、CocoaPodsが中央集権型のパッケージマネージャならCarthageは対照的でよりシンプルにライブラリを提供するためにGit経由でライブラリを導入します。

Swift Package Manager

github.com

Swift自体に内包されている言語として公式のパッケージマネージャになり、仕組み的にはCarthageに近くGit経由でライブラリを導入します。

他のパッケージマネージャとして違う点はiOS / macOSなどのAppleのプラットフォームには対応しておらずLinuxのみに対応しているのが大きな違いになります。

導入

CocoaPods

RubyGems

$ gem install cocoapods

gemからインストールします、gemの依存性を気にして間にbundlerを入れて管理するパターンも多いです。

(bundlerについては割愛)

Carthage

HomeBrew

$ brew install carthage

HomeBrewからインストールできます、他の方法だと.pkgを公開しているのでこちらからもインストールできます。

Swift Package Manager

Swift Package ManagerはSwift公式のパッケージマネージャとして言語自体に内包されているため特に導入の手順なくとも、Swiftの環境があれば使うことができます。

提供側のパッケージファイル

CocoaPods

{ライブラリ名}.podspec

https://guides.cocoapods.org/syntax/podspec.html

Pod::Spec.new do |spec|
  spec.name         = '{ライブラリ名}'
  spec.version      = '{Semantic Versioningなgit tag}'
  spec.license      = { :type => '{ライセンス形式}' }
  spec.homepage     = '{ライブラリのホームページURL}'
  spec.authors      = { '{作成名}' => '作成者のメールアドレス' }
  spec.summary      = '{ライブラリの概要}'
  spec.source       = { :git => '{ソースコードのURL}', :tag => '#{spec.version}' }
  spec.source_files = '{ライブラリのディレクトリ内でソースコードのパスを指定}' # Sourcesの下にあるコードを指定する場合はこうなる 'Sources/**/*.{swift,h,m}'
  spec.dependency = '{依存するライブラリ名}' # 複数ある場合は続けて 'spec.dependency = ...' と複数行に分けて書く
  spec.ios.deployment_target = "{対応してる中で最低なiOS version}"
  spec.osx.deployment_target = "{対応してる中で最低なmacOS version}"
  if spec.respond_to?(:watchos)
    spec.watchos.deployment_target = "{対応してる中で最低なwatchOS version}"
  end
  if spec.respond_to?(:tvos)
    spec.tvos.deployment_target = "{対応してる中で最低なtvOS version}"
  end
end

あとはここに記載したライブラリ名でCocoaPods側に登録をすることで

pod '{ライブラリ名}'

でCocoaPodsからライブラリを利用することができるようになります。

CocoaPods側に登録しない場合は直接GitのURLを指定することでライブラリを入れることができます。

Carthage

Swiftのdynamic libraryの仕組みを利用した配布方法を取るため提供側のパッケージファイルはありません。

なので配布する条件としては、

  • ライブラリのリポジトリのrootに{ライブラリ名}.xcprojectを用意する
  • dynamic framework用のスキームを用意する
  • Semantic Versioningのフォーマットに沿ったgit tagを打っておく

上記を満たしていることでライブラリ利用側からCarthageを経由してライブラリを導入することができます。

Swift Package Manager

Swift Package Managerはライブラリの提供側も利用側も同じフォーマットで扱うことができます。

提供側のリポジトリのルートにPackage.swiftがあれば利用側はライブラリをSwift Package Manager経由で利用することが可能になります。

利用側のパッケージファイル

どのパッケージマネージャもパッケージング用のファイルと、その依存を固定するためのロックファイルがペアで存在しています。

それぞれ表でまとめたものがこち

パッケージマネージャ CocoaPods Carthage Swift Package Manager
パッケージファイル Podfile Cartfile Package.swift
ロックファイル Podfile.lock Cartfile.resolved Package.pins

それでは実際にライブラリを導入する際にどう記載していくのかを試します。

ここでは一般的によく使われる通信用のライブラリであるAlamofireのSwift4対応branch導入を例にしていきます。

CocoaPods

guides.cocoapods.org

Podfile

target 'SampleProject' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for SampleProject
  pod 'Alamofire/Alamofire', :git => 'https://github.com/Alamofire/Alamofire', :branch => 'swift4'

  target 'SampleProjectTests' do
    inherit! :search_paths
    # Pods for testing
  end
end

Carthage

https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile

Cartfile

github 'Alamofire/Alamofire' 'swift4'

Swift Package Manager

Package.swift

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.


import PackageDescription

let package = Package(
    name: "SamplePackage",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "SamplePackage",
            targets: ["SamplePackage"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/Alamofire/Alamofire.git", .branch("swift4"))
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "SamplePackage",
            dependencies: ["Alamofire"]),
        .testTarget(
            name: "SamplePackageTests",
            dependencies: ["SamplePackage"]),
    ]
)

Swift4.0からSwift Package Managerのフォーマットが変わりました、従来のPackage.swiftと違う部分として新たにtargetsproductsが増えています。

上記はswift package initのコマンドで生成したPackage.swiftを編集しています。

もし古いPackage.swiftも使いつつSwift4.0のPackage.swiftにも対応する場合は古いPackage.swiftには swift-tools-version の記述を行わず新しいPackage.swiftの名前を

Package@swift-4.swiftの名前にすることでSwift3.2以前のSwiftでもそのパッケージを利用することが可能になります。

コマンド比較

一般的に使うであろうコメントでそれぞれ比較して見ました。

こうしてみるとCocoaPodsは1つのbranchで管理してるのもあり割とコマンドが充実してます。

操作 CocoaPods Carthage Swift Package Manager
基本コマンド pod carthage swift package
インストー pod install carthage bootstrap swift package resolve
アップデート確認 pod outdated carthage outdated -
アップデート pod update carthage update swift package update
パッケージの表示 - - swift package show-dependencies
パッケージの検索 pod search - -
利用可能なパッケージの表示 pod list - -
リセット pod deintegrate - swift package reset

終わりに

段々とSwiftの環境が充実していくことが実感できますね、最近は3種のパッケージマネージャに対応してるライブラリもよく見ます。

Swift Package ManagerがAppleのプラットフォームに対応すればまた情勢が変わりそうですが、それぞれのプロジェクト用途に合わせて選択できたら良いですね。

Swift gybの環境構築

Swift gybの環境構築

ふとSwiftのgybが気になったので環境構築を試して見ました

gybとは

gybは「Generate Your Boilerplate」の略称みたいです(gyb --helpより)

具体的に何かと言うと、Swiftの公式リポジトリの中でSwiftコードの自動生成に使われているPython製のテンプレートエンジンになります

gybを導入する

qiita.com

というわけで、こちらを参考に環境構築して見ました

まずgyb自体はapple/swift/utils/の中にそのまま入っています

Qiitaの紹介記事ままですがcurlでgybファイルを取得していきます

mkdir gyb
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb.py" -o "gyb/gyb.py"
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb" -o "gyb/gyb"
chmod +x gyb/gyb

上記のshellをそのまま実行すればgybを動かすのに必要なファイルが揃います

実際に動くかどうか確認します

$ cd gyb
$ ./gyb --help

これでhelpの一覧が出れば成功です

usage: gyb [-h] [-D NAME=VALUE] [-o TARGET] [--test] [--verbose-test] [--dump]
           [--line-directive LINE_DIRECTIVE]
           [file]

Generate Your Boilerplate!

positional arguments:
  file                  Path to GYB template file (defaults to stdin)

optional arguments:
  -h, --help            show this help message and exit
  -D NAME=VALUE         Bindings to be set in the template's execution context
  -o TARGET             Output file (defaults to stdout)
  --test                Run a self-test
  --verbose-test        Run a verbose self-test
  --dump                Dump the parsed template to stdout
  --line-directive LINE_DIRECTIVE
                        Line directive prefix; empty => no line markers

    A GYB template consists of the following elements:

      - Literal text which is inserted directly into the output

      - %% or $$ in literal text, which insert literal '%' and '$'
        symbols respectively.

      - Substitutions of the form ${<python-expression>}.  The Python
        expression is converted to a string and the result is inserted
        into the output.

      - Python code delimited by %{...}%.  Typically used to inject
        definitions (functions, classes, variable bindings) into the
        evaluation context of the template.  Common indentation is
        stripped, so you can add as much indentation to the beginning
        of this code as you like

      - Lines beginning with optional whitespace followed by a single
        '%' and Python code.  %-lines allow you to nest other
        constructs inside them.  To close a level of nesting, use the
        "%end" construct.

      - Lines beginning with optional whitespace and followed by a
        single '%' and the token "end", which close open constructs in
        %-lines.

    Example template:

          - Hello -
        %{
             x = 42
             def succ(a):
                 return a+1
        }%

        I can assure you that ${x} < ${succ(x)}

        % if int(y) > 7:
        %    for i in range(3):
        y is greater than seven!
        %    end
        % else:
        y is less than or equal to seven
        % end

          - The End. -

    When run with "gyb -Dy=9", the output is

          - Hello -

        I can assure you that 42 < 43

        y is greater than seven!
        y is greater than seven!
        y is greater than seven!

          - The End. -

gybの使い方

ヘルプ

$ ./gyb --help

もしくは

$ ./gyb -h

テンプレートをコンソール上に出力

下記コマンドからgyb形式のテンプレートを元に出力を行えます

注意点として--line-directive=のオプションを追加しない場合は// ###sourceLocationというgyb側がSwiftコンパイラの為に出力するコメントも一緒に出力されてしまうので見た目的にも追加した方が良いです

$ ./gyb {gyb_file} --line-directive=

Int/UIntの全てのビットの型に対して最大値を返す関数を追加

テンプレートがこち

template.swift.gyb

%{
  intTypes = [8,16,32,64]
}%

% for intType in intTypes:
    % for sign in ['','U']:

/// Extension that adds a few additional functionalities to ${sign}Int${intType}
extension ${sign}Int${intType} {

    /// Returns a ${sign}Int${intType} with all ones
        %if sign == '':
    public static var allOnes:Int${intType} {
       return Int${intType}(bitPattern: UInt${intType}.max)
    }
        %else:
    public static var allOnes:UInt${intType} {
       return UInt${intType}.max
    }
        %end
}
    %end
%end

出力してみます、下記コマンドを叩いて見ます

$ ./gyb template.swift.gyb --line-directive=

これでInt / UIntの8から64ビットの型に対してextensionするコードが生成されてるのが確認できれば成功です

/// Extension that adds a few additional functionalities to Int8
extension Int8 {

    /// Returns a Int8 with all ones
    public static var allOnes:Int8 {
       return Int8(bitPattern: UInt8.max)
    }
}

/// Extension that adds a few additional functionalities to UInt8
extension UInt8 {

    /// Returns a UInt8 with all ones
    public static var allOnes:UInt8 {
       return UInt8.max
    }
}

/// Extension that adds a few additional functionalities to Int16
extension Int16 {

    /// Returns a Int16 with all ones
    public static var allOnes:Int16 {
       return Int16(bitPattern: UInt16.max)
    }
}

/// Extension that adds a few additional functionalities to UInt16
extension UInt16 {

    /// Returns a UInt16 with all ones
    public static var allOnes:UInt16 {
       return UInt16.max
    }
}

/// Extension that adds a few additional functionalities to Int32
extension Int32 {

    /// Returns a Int32 with all ones
    public static var allOnes:Int32 {
       return Int32(bitPattern: UInt32.max)
    }
}

/// Extension that adds a few additional functionalities to UInt32
extension UInt32 {

    /// Returns a UInt32 with all ones
    public static var allOnes:UInt32 {
       return UInt32.max
    }
}

/// Extension that adds a few additional functionalities to Int64
extension Int64 {

    /// Returns a Int64 with all ones
    public static var allOnes:Int64 {
       return Int64(bitPattern: UInt64.max)
    }
}

/// Extension that adds a few additional functionalities to UInt64
extension UInt64 {

    /// Returns a UInt64 with all ones
    public static var allOnes:UInt64 {
       return UInt64.max
    }
}

あと例えばファイル内のgybファイルを一気に出力するような場合はomochiさんがこういうスクリプトを書いてくれてたりするので参考にすると良いかもしれません

github.com

終わりに

こんな感じで書くことが決まってるのに型の都合で書かないといけないコードだったりが、gybを使うことで人間が手を動かす量が減るのは最高ですね

まだ使い始めたばっかりなのでなんともですけど、swagger.ymlからAPI定義を元にgyb経由して自動でモデル定義吐けたりとかが理想です、どんどん楽していきたい…(切実)

mac osでLLVMの環境を構築してみる

mac osLLVMの環境を構築してみる

Homebrewからllvmをインストール

Homebrewから最新のllvmをインストールしてくる

brew install --with-clang --with-lld --with-python --HEAD llvm

インストール完了後、llvmのパスが表示されるのでパスを通す

echo 'export PATH=$PATH:/usr/local/Cellar/llvm/HEAD-1d6becb/bin' >> .zshrc

NOTE:

HEAD-1d6becbの部分が最新の状態によって変わるので合わせて変更する

rustupでRustを導入してみる

rustupでRustを導入してみる

前の記事でrsvmの導入についての記事を書いたが、公式的にはrustupを使うことを推奨しているようなので試してみた

www.rust-lang.org

導入

Rustのページで書かれてるようにコマンドを叩いてみる

$ curl https://sh.rustup.rs -sSf | sh

すると、どのオプションでインストールを実行するのかを確認されるのでとりあえずdefaultを選択してみる

Current installation options:

   default host triple: x86_64-apple-darwin
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation

インストールが実行される

info: syncing channel updates for 'stable-x86_64-apple-darwin'
info: downloading component 'rustc'
 31.5 MiB /  31.5 MiB (100 %)  23.0 MiB/s ETA:   0 s
info: downloading component 'rust-std'
 41.3 MiB /  41.3 MiB (100 %)  31.9 MiB/s ETA:   0 s
info: downloading component 'cargo'
  3.2 MiB /   3.2 MiB (100 %)   1.3 MiB/s ETA:   0 s
info: installing component 'rustc'
info: installing component 'rust-std'
info: installing component 'cargo'
info: default toolchain set to 'stable'

  stable installed - rustc 1.14.0 (e8a012324 2016-12-16)


Rust is installed now. Great!

To get started you need Cargo's bin directory in your PATH environment
variable. Next time you log in this will be done automatically.

To configure your current shell run source $HOME/.cargo/env

そして言われた通りに更新するようコマンドを叩く

$ source $HOME/.cargo/env

入ったかどうかバージョンを確認する

$ rustc -V
rustc 1.14.0 (e8a012324 2016-12-16)

現時点で最新のRustのバージョン1.14.0入った 👍

rsvmでRustのバージョン管理を試してみた

rsvmでRustのバージョン管理を試してみた

言語のバージョン管理でいう〜env系のRust版があったので試して見た

github.com

環境

rsvmをインストールする

$ curl -L https://raw.github.com/sdepold/rsvm/master/install.sh | sh

これでパスも通ったはずなので更新して確認、試しにヘルプを出してみる

$ source .zshrc
$ rsvm -h

下記のように出てれば成功

Rust Version Manager
====================

Usage:

  rsvm help | --help | -h       Show this message.
  rsvm install <version>        Download and install a <version>.
                                <version> could be for example "0.12.0".
  rsvm uninstall <version>      Uninstall a <version>.
  rsvm use <version>            Activate <version> for now and the future.
  rsvm ls | list                List all installed versions of rust.
  rsvm ls-remote                List remote versions available for install.
  rsvm ls-channel               Print a channel version available for install.

Current version: 0.5.1

rsvmでrustを入れてみる

どのバージョンを入れて良いのか分からないのでRustのバージョンを確認する

$ rsvm ls-remote

現在の最新が1.14.0であることが分かるので指定してインストールする

rsvm install 1.14.0

以下のように表示されたら完了

Creating the respective folders for rust 1.14.0 ... done
Downloading sources for rust 1.14.0 ...
######################################################################## 100.0%
Extracting source ... done
Downloading sources for rustc sourcecode 1.14.0 ...
######################################################################## 100.0%
Extracting source ... install: creating uninstall script at /Users/moaible/.rsvm/versions/1.14.0/dist/lib/rustlib/uninstall.sh
install: installing component 'rustc'
install: installing component 'rust-std-x86_64-apple-darwin'
install: installing component 'rust-docs'
install: installing component 'cargo'

    Rust is ready to roll.


And we are done. Have fun using rust 1.14.0.
Activating rust 1.14.0 ... done

指定したバージョンがインストールされたかを確認

$ rsvm ls
rsvm_initialize:23: file exists: /Users/moaible/.rsvm/.rsvm_version
Installed versions:

  =>  1.14.0

$ rustc -V
rustc 1.14.0 (e8a012324 2016-12-16)

Rustのバージョンも反映されてることが分かる

疑問

他の〜env系と違ってlocalglobalの切り替えができない模様、nvmとインターフェースが似てるのでどうにか解決できるかもしれない

終わりに

他の言語と同じようにRustでもバージョン管理することができたので、どんどんRustを追いかけていきたい

SwiftでCLIしやすくするモジュールSwCLIを作ってみた

SwiftでCLIしやすくするモジュールSwCLIを作ってみた

github.com

なぜ作ったか

  • Xcode 8.0からsystem関数をコールするとエラーになった
  • NSTaskで扱えるが癖を把握して使わないといけないため、いちいち面倒くさい

ということである程度wrapして扱えるようにしたライブラリを実装したので紹介します

実行

let ret = try! SwCLI().runWithRead(["echo", "abc"])
// -> abc

実行確認

if SwCLI().passes(["cd", "Sources"]) {
    // -> changed directory
}

コマンド確認

if SwCLI().contains(["git"]) {
    // -> can use git command
}

Assert

fail("forced termination")

終わりに

こんな感じで簡単に試せることを意識して実装してみました。

ぜひ触って見てください