SignalとDriverの違い replayから読み解く

SignalとDriverについて

RxSwiftのSignalを調べてみると、 Signal は、replayを行わない。 Driver は、 replay を行う。 という解説を見かけました。 でも、replayってなんやろ?って思ったので、そこを踏まえて SignalDriver の違いについて解説をしていきたいと思います。

sharedReplayを使うメリット

1つのObservableに対して2つの購読対象がある場合2本のストリームが用意され、値が2度流れてくることになります。そこで、shareReplayを使います。 最後にshareReplayを書くと、1つの値しか流れ無くなります。 shareReplay(1)は、内部的にreplay(1).refCountを行なっています。 では、次の章でreplayを見ていきます。

ConnectableObservableとObservableの違い

Observableは、subscribeした時点でObserver毎に計算リソースが割り当てられています。 replayは、ConnectableObservableの変換を行っています。 ->map内に複数の処理が走ることになる

ConnectableObservableはsubscribeしているObserverが複数いたとしても、全てのObserver間で共有の計算リソースが割り当てられ、同時に値を通知する.

ただし、replayだけでは何も流れずconnectをする必要がある。 RxSwiftのConnectを使用すると、disposeをConnectされる毎にする必要がある。

そこで、refCountを使用する。 refCountは、subscribeされる度に内部でsubscribeしているのものをカウントしている。 そして、disposeする度に内部のカウントをデクリメントしている。 内部カウンターの値が0になると、Connectしたソースをdisposeする。

UIImageを丸くする。KingFisherを使って画像を丸くする

Kingfisherとは?

URLから画像を取得する方法として、KingfisherというLibraryがあります。

画像を丸くする

func roundImage() -> UIImage {
        let minLength: CGFloat = min(self.size.width, self.size.height)
        let rectangleSize: CGSize = CGSize(width: minLength, height: minLength)
        UIGraphicsBeginImageContextWithOptions(rectangleSize, false, 0.0)
        
        UIBezierPath(roundedRect: CGRect(origin: .zero, size: rectangleSize), cornerRadius: minLength).addClip()
        self.draw(in: CGRect(origin: CGPoint(x: (minLength - self.size.width) / 2, y: (minLength - self.size.height) / 2), size: self.size))
        
        let newImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return newImage
 }

kingfisherを使って画像を丸くする

func setCornerRadiusImage(url: URL?, placeholder: UIImage? = nil) {
        let processor = ResizingImageProcessor(referenceSize: self.frame.size) >> RoundCornerImageProcessor(cornerRadius: self.frame.size.width / 2)
        kf.setImage(with: url, placeholder: placeholder, options: [.processor(processor), .cacheSerializer(FormatIndicatedCacheSerializer.png)])
 }

jpgで、画像を保存するとキャッシュした後に白い背景が表示されるみたいです。 なので、pngに変えています。

[ios] FirebaseStorageの使い方・Singleを使って実装する。

現在、firebaseのstorageを使ってProfile画像を作っています。

主な使い方は、Firebaseのドキュメントを使って知ることができます。

Cloud Storage  |  Firebase

はじめに

CocoaPodsでFirebase/Storageをインストールします。

use_frameworks!
pod "Firebase"
pod "Firebase/Storage"

次にFirebase Storageの設定を修正します。 今はAuthorizeしたユーザーしかファイルの読み書きをできないので、誰でもできるように変更します。

FirebaseはStorageは、Pathを Storage.storage().reference() にchildを使って指定します。 child("a").child("b") と書くとaの中のbに画像が保存されます。

今回は、UserProfileを保存したいので、pathは .child("users").child("profile") とします。

let ref = Storage.storage().reference().child("users").child("profile") と変数を持たせれば、画像の取得を楽にすることができます。

画像のアップロード

画像のアップロードは、setDataと言う関数を用いることで使用することができます。 setDataの引数には、保存したい画像のData型とmetaDataを渡します。 metadataは、以下のURLで確認してください。

https://firebase.google.com/docs/storage/ios/file-metadata?hl=ja

基本は、nilで大丈夫です。

以下で、UIImageからData型に直すことができます。 0.3と言う数字は、画質に関する情報なのですが、容量の重い画像だとアップロードに時間がかかるので比較的小さい数字でいい気がします。 let imageData = myProfile.jpegData(compressionQuality: 0.3)

戻り値は、metadataとerrorが帰ってくるので、ハンドリングすれば終わりです。

つい数ヶ月前までは、metadata?.downloadURL()で保存先のURLを取得できたのですが現在は使えないようです。

以下がアップロードの処理です。

 func uploadUserProfile(imageData: Data) -> Single<Void> {
        let ref = Storage.storage().reference().child("users").child("profile")
        return Single.create { observer in
            ref.putData(imageData, metadata: nil) { metadata, error in
                if let error = error {
                    observer(.error(error))
                    return
                }
                observer(.success(()))
            }
            return Disposables.create()
        }
    }

画像のダウンロード

ダウンロードには、Data型とURLで取得する2通りの取得方法があります。

Data型で取得する。

Data型は、getDataで取得することができます。引数に、画像のサイズ ' maxSize 'を書きます。 ドキュメントには、1 * 1024 * 1024と書いてありました。 そして、引数には dataerror が帰ってきます。

 func downloadUserProfile() -> Single<Data> {
        let ref = Storage.storage().reference().child("users").child("profile")
        return Single.create { observer in
            ref.getData(maxSize: 1 * 1024 * 1024, completion: { data, error in
                if let error = error {
                    observer(.error(error))
                    return
                }
                guard let data = data else {
                    fatalError()
                }
                observer(.success(data))
            })
            return Disposables.create()
        }
    }

URL型で取得する

現在のプロダクトは、Kingfisherで画像をキャッシュしているのでURLで取得しています。 URLかたで受け取るには、downloadURLで取得でき、引数は必要ありません。 引数には URLerror が帰ってきます。

 func downloadProfileURL() -> Single<URL> {
        let ref = Storage.storage().reference().child("users").child("profile")
        return Single.create { observer in
            ref.downloadURL(completion: { url, error in
                if let error = error {
                    observer(.error(error))
                    return
                }
                guard let url = url else {
                    fatalError()
                }
                observer(.success(url))
            })
            return Disposables.create()
        }
    }

Singleを使うと、流れてくる最初の値のみを流しsuucessとerro rを流すため、APIなど 成功したら結果を、失敗したらエラー情報を返す場合 に便利に使えます。

サイバーエージェントで1ヶ月インターンをしました!

iOSを初めて、半年がたち自分の力を試してみたいと思い、インターンをしていたPopshootという会社からサイバーエージェントインターンをしました。

インターンでは、Carteというアプリの新機能の実装を行いました。 Carteは、順天堂大学の教授と開発を行なっている医療系のアプリです。脈拍を見て、自律神経が正常に働いているのかを計測することができます。 www.cyberagent.co.jp

開発

機能

計測後にユーザーが自分の体と心の体調を5つ星から選ぶという機能とタグを選択肢今どんな気持ちなのかをメモする機能を付け加えました。

取り組み方

はじめにデザイナーとPMの方からデザインと仕様を教えてもらい、メンターの方と工数について話しました。 それからは、毎日開発を行いました。わからないことは、メンターの方が丁寧に教えていただきスムーズな開発を行うことができました。 毎週金曜日には、進捗報告会が設けられその週に行なった進捗をデザイナーとPMの方にみていただく機会がありました。 それぞれのタスクが分散されおり、コードを書くことに集中できました。

取り組み

既にあるコードを読むことは自分自身慣れておらず、まずはそれを読むことからはじめました。 アプリをビルドし、端末で画面ごとにどんなロジックが走っているのかを探り探りではじめました。 特に、難しかったのはデータの保持の仕方をロジックから追うことです。 僕は普段RxSwiftを使っていたのですが、Carteでは他のライブラリが使われておりそれを理解しながら追うのが難しかったからです。

また、実装面では自分で設計やライブラリ選定をさせていただきました。 設計では、Firebaseを使いデータの送受信のタイミング、どんなデータを送るのかといった設計を行いました。 初めてFirebaseを触る機会でもあったので、何度もデータベースにデータの送受信を繰り返しバグがおこらないかを試しました。 また、どんなデータをどこに送るかは絵を書きながら頭にイメージができるようにしました。 絵を書くことでメンターの方にも伝わりやすかったので、絵でイメージすることは続けて行こうと思います。 ライブラリは、星を選択する画面で実装しました。 ライブラリを選定するときに、何度も更新されているのかとexampleを見て使いやすそうかというところを選定基準にしました。 使ったのは、Cosmosというライブラリです。 https://github.com/evgenyneu/Cosmos

実装で、とても手間がかかったのはタグを選択する画面です。タグ選択画面では、真ん中を軸にタグを並べるというUIでした。 このタグ選択の画面では、UICollectionViewを使ったのですがCollectionViewCellは左詰めで実装されるためこれを真ん中にするコードを実装する必要がありました。 まずはじめにUICollectionViewLayoutに準拠したクラスを作成し、CollectionViewのwidthとCellに入るデータを取得してきます。 それぞれのCellの横幅を足していきCollectionViewのwidthよりも長くなると次の列のCellになる。というのを繰り返していき、Cellの列の個数と各列のCellの個数を変数に格納します。 そして、それぞの列のCellの右端と左端になるCellのそれぞれに余白を作るという処理を書きました。

課題

今回のインターンで、メンターの方によく指摘されたのが命名規則でした。これまでのインターンではそれほど意識をして来なかったのですが、「動詞なのか名詞なのか」、「もっと具体的にしたい」こういったところは、よく注意されました。なので、もっと自分で開発する際でも気をつけて開発していきたいと思いました。 また、これまで自分はMVVMを使った開発が多かったです。しかし、今回の開発はMVCを使った開発で読みやすかったのですが、ロジック部分とUIの部分が混在しておりMVVMの良い面を改めてしる機会になりました。他のアーキテクチャを知ることでいろんな知識を得ることも知ったので、今後は複数のライブラリを使って見たいと思いました。 また、工数は結局予定よりも大きく伸ばしてしまいました。なので、何ができるのかどれくらいでできるのかという点に関しては、もっと意識していきたいと思いました。

その他の生活

piggの決起会で表参道のブラジル料理屋に連れて行ってもらったり、カルテの方達と築地に海鮮を食べに行ったり、人事の方に焼肉に連れって行っていただいたりと美味しいものを食べさせていただきました。 また、賞の名前は忘れたのですが、サイバー内の賞を取られたエンジニアの方と話す機会やAbemaTV、CatsのiOSエンジニアの方ともご飯をさせていただきとても学びの多い時間を過ごさせていただきました。

宿泊施設は、会社から歩いて10分ほどの場所にありとても近く渋谷とは思えないほど静かな場所にありました。 また、宿泊施設には他の技術を持ったエンジニアの方も宿泊されており、夜になると一緒に鍋をつつきながら技術について話をしました。

感想

メンター、PM、デザイナーの方が優しく毎日伸び伸び開発できたのがとても過ごしやすく楽しかったです。 集中した開発ができたので毎日成長する実感があり、本当に有意義な時間でした。 また、モチベーションがみなさん高く、仕事に対する取り組みに関して凄く尊敬しました。 いろんな人と話すことが好きなので、大勢の方が働いておられる会社はとても自分にあっているなとも思いました。 本当に多くの方にお世話になった1ヶ月でした。ありがとうございました。 力試しでのインターンでしたが、「もっともっと成長しないといけない」と改めて思うようになりました。

今後

今後は、再びPopshootでインターンをさせていただきます。Popshootには、優秀なエンジニアの方が多くおられるので今回つけた基礎力をもとに少しでも近づけるようにしていき、命名と開発スピードも意識して開発をしていきたいと思います。

Carthage bootstrapをしすぎるとエラーが起こる

今回、carthage bootstrapをしているとエラーが起こっていたのでそれについて解説します。 API rate limit exceeded for 202.214.112.129というエラーです。 XCodeのvaersionが上がり、Carthage周りでエラーが出てたのでBootstrapをすることにしました。 しかし、bootstrapをしても通らないエラーでした。すると次は、上記のようなエラーが起こりました。 ここでは、このエラーの解決策に関してまとます。

解決策は、 Homebrewなどと同様に Github APIAccess Tokenを使う事になります。 Carthageではこのtokenを GITHUB_ACCESS_TOKEN という環境変数に指定することで利用できます。

export GITHUB_ACCESS_TOKEN=xxxxxxxxxxxx carthage checkout

Goで、apiを叩いてみた

//packageは、SwiftでいうClassに近いものかと理解しています。他のfileで関数を呼ぶときは、このpacage名を使う必要があります。 package main

//importは、libraryに近いです。今回のliberaryはGoが用意しているlibraryらしいです。 import (  //fmtは、plintとかする時に必要です "fmt" "io" "io/ioutil"   //このlibrary使えばapiを叩けるらしいです "net/http" "time" ) func request() { client := &http.Client{ Timeout: 30 * time.Second, } //ここで、urlとメソッドを入力したらapiはとって来れます。 req, err := http.NewRequest("GET", "https://qiita.com/api/v2/schema", nil) if err != nil { } resp, err := client.Do(req) if err != nil { } defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
}
fmt.Println(data)
//ここをdecode出来たら、string型でに変換できそう

Xcodeのversionをあげた際に起こるCarthageでのエラー修正

エラーが出た経緯

iOSのversionが12.0になったので、updateすると自分の実機でdebugできなくなったのでxcodeのversionもあげました。

そしたら、Carthageあたりでめっちゃエラー出ました。

エラー

Module compiled with Swift 5.1.3 cannot be imported by the Swift 5.2 compiler: file名/Build/iOS/Carthage名/Modules/Carthage名/.swiftmodule/arm64.swiftmodule

こんな感じのエラーです。

対策

今回のエラーの対策は、3種類あります。

1

RxSwiftなど、Swift5.2に対応しているCarthjgeはversion指定を外して Carthage updateをすればbuildが通ります。

今回詰まったのは、SwiftyBeaverというLibraryがswift5.2に対応していなかったのでCarthage updateしても ターミナルでTask failed with exit code 65が返されました。 なので、Xcodever9.41をapple developerからdownload してきました。

3

開発チームによっては、Xcodeの吐き出すバイナリを統一するためXcodeのバージョンを縛っているケースがあります。

その際は、チームの方に聞いてみましょう!