20191024のSwiftに関する記事は8件です。

「LLVM bitcode はポータブルでない」ってどういう意味なの?

発端

LLVM bitcode はポータブルでない
http://shinh.skr.jp/slide/elvm/002.html

これがどういう意味なのか分からなくてずっと気になってました。
LLVMのbitcodeってターゲット非依存なんじゃないの?みたいな感じで。

ここでは「ポータブルでない」というのを
(実行される)プラットフォームまたは(コンパイルの)ターゲット
依存している(dependent)または固有のもの(specific)を含むこと
ぐらいにふんわり理解してます。
「だから〇〇ができない」みたいなことは考えません。

結論あるいは感想

  • LLVM IRとそれをまとめたbitcode自体はターゲットに依存しない
  • 実際にはだいたい(?)ターゲットに依存したIRが出力される
    • C系の場合にはほぼ不可避
    • Swiftでもターゲット依存のIRっぽい
  • low levelな中間表現なんだからそりゃターゲット依存にもなるでしょ
    • 正しいのかは分からないけどそういう気持ちになった
    • ただしLLVMは何の略でもないことになってる

C/C++/Objective-C

Cとその上位互換たちの話のはずなので、Objective-Cもたぶん該当する

LLVM公式のFAQを翻訳してちょっとコメント

http://llvm.org/docs/FAQ.html#can-i-compile-c-or-c-code-to-platform-independent-llvm-bitcode

Question

Can I compile C or C++ code to platform-independent LLVM bitcode?

CやC++のコードをプラットフォームに依存しないLLVM bitcodeにコンパイルできますか?

Answer

3分割してタイトルを付けます。

コンパイル前(プリプロセッサ)

No. C and C++ are inherently platform-dependent languages. The most obvious example of this is the preprocessor. A very common way that C code is made portable is by using the preprocessor to include platform-specific code. In practice, information about other platforms is lost after preprocessing, so the result is inherently dependent on the platform that the preprocessing was targeting.

できません。CやC++は本質的にプラットフォーム依存な言語です。いちばん分かりやすい例としては、プリプロセッサがあります。Cのコードをポータブルにするためによくあるやり方は、プリプロセッサを使ってプラットフォーム固有のコードを含めることです。実際のところ、他のプラットフォームの情報はプリプロセスの後では失われ、その成果物はプリプロセスが対象としたプラットフォームに本質的に依存したものとなります。

要するに、LLVM IRを作るコンパイル以前に、既にプラットフォーム依存なコードになってるのが普通というわけです。

コンパイル時(sizeof)

Another example is sizeof. It’s common for sizeof(long) to vary between platforms. In most C front-ends, sizeof is expanded to a constant immediately, thus hard-wiring a platform-specific detail.

もう1つの例として、sizeofがあります。よく知られているように、sizeof(long)はプラットフォームごとに変わります。多くのC言語のフロントエンドでは、sizeofを定数値に直接展開して、プラットフォーム固有の詳細を埋め込みます。

ここでのフロントエンドは、clangとか、LLVMのフロントエンドのことですね。プリプロセッサのようにコンパイル前だけでなく、コンパイル過程でもプラットフォーム固有の値がそのまま入ってしまうのが普通、ということでしょう。

ABI

Also, since many platforms define their ABIs in terms of C, and since LLVM is lower-level than C, front-ends currently must emit platform-specific IR in order to have the result conform to the platform ABI.

また、多くのプラットフォームでABIはCレベルで定義され、そしてLLVMはCより低レベルなので、そのプラットフォームのABIに合わせた成果物を得るために、フロントエンドはプラットフォーム固有のIRを出力しなければならないのが現状です。

私はABIというものをちゃんと分かっていません。なので、それっぽい翻訳だけにとどめておきます。

Swift

SILのドキュメントでのLLVM IRの扱い

https://github.com/apple/swift/blob/master/docs/SIL.rst#sil-in-the-swift-compiler

SILというのは、コンパイル過程でASTとLLVM IRの中間にあるやつで、raw SILとcanonical SILの2段階があります。それ以外のことは何も分かってませんが、LLVM IRとの対比で興味深い表現を見つけました。

In contrast to LLVM IR, SIL is a generally target-independent format representation that can be used for code distribution, but it can also express target-specific concepts as well as LLVM can.

LLVM IRとは対照的に、SILは専らターゲットに依存しないフォーマットの表現形態で、コード配布に使うことができます。しかし、LLVMと同様に、ターゲット特有の概念を表現することもできます。

ここではLLVM IRがターゲット依存なものとして扱われているように読めます。これがC系のように不可避の現実なのか、(例えば何らかの最適化のための?)選択なのかは分かっていませんが、high-levelなSILではターゲットに依存しない表現を扱い、ターゲット依存なものはlow levelなLLVM IRの担当になっているようです。

Rust

LLVMのフロントエンドとして、C系、Swiftの他にRustでの扱いも挙げておきます。なお、私はRustのことは何も知りません。

Rustコミュニティでの質問と回答

TODO MIRの記事から何か分かるといいな

  • Introducing MIR
    • SwiftのSILのように、LLVM IRの手前に別の中間表現があるようです。
    • ほぼ図しか見てないので、そのうち読んで何か追記します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS13]UITextViewDelegateがTouchDownで反応してしまう

何が問題か

iOS13からテキストのリンクなどに用いられるUITextViewDelegatetextViewメソッド
iOS12まではTouchUpで反応していたもののiOS13からなぜかTouchDownで反応してしまうという問題がありました.
参考記事

どう解決するか

  • iOS13の不具合(?)の修正を待つ
  • textViewメソッドのtextViewインスタンスからタッチイベントを取得する
  • そもそもtextViewDelegateメソッドを使用しない

以上の3つがあると思います.

iOS13の不具合修正を待つ

いつAppleが修正を行うのかは不明瞭で今の所この修正はベータ版にも含まれていないようなので,これは懸命な解決法ではなさそうです.

textViewメソッドのtextViewインスタンスからタッチイベントを取得する

以下のtextViewメソッドから受け取れるtextViewのタッチイベントを取得する方法です.

func textView(_ textView: UITextView,
                  shouldInteractWith URL: URL,
                  in characterRange: NSRange,
                  interaction: UITextItemInteraction) -> Bool

以下のようにまずinteractionで処理をわけ,textViewのタップイベント処理が終わった時に何かの処理をさせることで擬似的にTouchUpの挙動と同じになるようにしています.出典はstackOverflowです.

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {

    switch interaction {
    case .invokeDefaultAction:
        if textView.gestureRecognizers?.contains(where: {$0.isKind(of: UITapGestureRecognizer.self) && $0.state == .ended}) == true {

            // Handle your custom logic here.

            return false
        }
        return true
    case .presentActions:

        // Default action.

        return true
    case .preview:

        // Default action.

        return true
    @unknown default:
        fatalError()
    }
}

そもそもUITextViewDelegateを使用しない

そもそもiOS13側の不具合である可能性が高く,いつ対応がされるのかがわからないため上記のtextViewのタッチイベントを拾ってtouchUpのように振る舞わさせるのは少し冗長な気もします.
iOS13からURLなどを長押しした際のOS側の挙動が変わっている(以前までのアクションシートが起動するだけのものから変更されてプレビューが表示されるなどOS準拠の挙動がリッチになってきている)こともあり,あえて自分でそのさきを処理せず,OS準拠の挙動に合わせるということもありなのではないかなと思います.

参考資料

https://stackoverflow.com/questions/58189447/ios-13-1-uitextview-delegate-method-shouldinteract-called-when-scrolling-on-atta

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

iOSの有名リポジトリのコードの小技

うまい人の研究してわかったこと

  • アクセス修飾子をちゃんと書く
  • アクセス修飾子ごとにextentionを切る
  • internal var heroIDToSourceView = [String: UIView]() というふうに、propertyをoptionalにするようなことはしない
    • optinalはoptinalにする必要にするときだけ
  • delegateはweakにする
  • UIView(frame: transitionContainer?.bounds ?? .zero) nullのcheckに演算子を使う
  • for v in animatingToViews { _ = context.snapshotView(for: v) }
  • 1行for文もあり
  • deferの使い方が上手い(loadingを消したり、初期化処理をしたりする)

    • deferとは、途中でreturnしても実行される文
  • funcに対するコメントを書く

  /**
   Update the progress for the interactive transition.
   - Parameters:
   - progress: the current progress, must be between 0...1
   */
  • propertyのViewControllerの宣言が、くっそうまい(関数的で、他に影響がないように書いている)
lazy var mmPlayerLayer: MMPlayerLayer = {
        let l = MMPlayerLayer()
        l.cacheType = .memory(count: 5)
        l.coverFitType = .fitToPlayerView
        l.videoGravity = AVLayerVideoGravity.resizeAspectFill
        l.replace(cover: CoverA.instantiateFromNib())
        return l
    }()

Layerのアニメーションに関して

fileprivate func setFrameWith(quadrant: VideoPositionType, dismissVideo: Bool) {
        let margin = self.config.margin
        var rect = self.config.playLayer?.playView?.frame ?? .zero
        let size = UIScreen.main.bounds
        switch quadrant {
        case .leftTop:
            rect.origin.x = dismissVideo ? -rect.size.width : margin
            rect.origin.y = margin
        case .rightTop:
            rect.origin.x = dismissVideo ? size.width : size.width-rect.size.width-margin
            rect.origin.y = margin
        case .leftBottom:
            rect.origin.x = dismissVideo ? -rect.size.width : margin
            rect.origin.y = size.height-rect.size.height-margin
        case .rightBottom:
            rect.origin.x = dismissVideo ? size.width : size.width-rect.size.width-margin
            rect.origin.y = size.height-rect.size.height-margin
        }

        to?.view.alpha = 0.0
        UIView.animate(withDuration: self.config.duration, animations: {
            if dismissVideo {
                self.config.playLayer?.playView?.alpha = 0.0
            }
            self.config.playLayer?.playView?.frame = rect
        }) { [unowned self] (_) in
            if dismissVideo {

                (self.config as? MMPlayerPassViewPresentConfig)?._dismissGesture = true
                self.config.playLayer?.setCoverView(enable: true)
                self.to?.dismiss(animated: true, completion: nil)
            }
        }
    }

willSet + newValueを使う

    @IBOutlet weak var okButton: UIButton! {
        willSet {
            // OKボタンの修飾
            newValue.layer.borderColor = UIColor.white.cgColor
            newValue.layer.borderWidth = 1.0
            newValue.layer.cornerRadius = 5.0
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift 5] スクロールに応じてページング対応APIから1ページずつコンテンツを取得する方法

Requestパラメータでpageを指定すれば指定したページの情報を取得できるようなAPIがあるとして、
そのAPIから取得したコンテンツをアプリでページングして表示する実装の備忘録です。

読み込み状態を定義

/// APIの読み込み状況
enum APILoadingStatus {
    case initial
    case fetching
    case loadMore
    case full
    case error
}

Viewでの実装

final class ViewController: UIViewController {

    /// APIの最終取得Page
    private var pageOfLastFetched = 1
    /// APIの読み込み状況
    private var loadStatus: APILoadingStatus = .initial

}

インクリメントして使うPage数と、読み込み状態を保持するプロパティを宣言します。

    // APIを使用してコンテンツを取得する
    func fetchContents() {
        // 読み込み中またはもう次に記事がない場合にはapiを叩かない
        if loadStatus == .fetching || loadStatus == .full {
            return
        }
        // loadStatusを読み込み中に変更
        loadStatus = .fetching

        // 記事一覧を取得するAPIを叩く
        repository.getContents(page: pageOfLastFetched) { [weak self] result in

            guard let self = self else {
                return
            }

            switch result {
            case .success(let contents):
                if contents.isEmpty {
                    // 結果が空だった=もう続きの記事がないのでloadStatusをfullにする
                    self.loadStatus = .full
                    return
                }
                self.pageOfLastFetched.increment()  // +1
                // 記事取得が終わった段階でloadStatusをloadMoreにする
                self.loadStatus = .loadMore

            case .failure(let error):
                // loadStatusをエラーにする
                self.loadStatus = .error
                // TODO: Error handling.
                print(error)
            }
        }
    }

APIをコールするメソッドを定義します。
成功したらPage数をインクリメントして、最後までFetchしたら読み込み状態を .full にして以後APIをコールしないようにします。

    // MARK: - UIScrollViewDelegate
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 一定割合スクロールすると次のPageを読み込む
        let currentOffsetY = scrollView.contentOffset.y
        let maximumOffset = scrollView.contentSize.height - scrollView.frame.height
        let distanceToBottom = maximumOffset - currentOffsetY

        // 最下部まで500pxを切ったら次のページを取得する
        if distanceToBottom < 500 {
            fetchContents()
        }
    }

ページングするタイミングですが、今回は画面最下部500pxを切るところまでスクロールされたらAPIでFecthするようにしました。

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

【Swift】Combineで一つのPublisherの出力結果を共有するメソッドやクラス�の違い(share, multicast, Future)

Combine.frameworkを使用していると
複数のSubscriberで同じ処理結果を共有したい場合があります。

例えば

  • ネットワーク通信を介したデータの取得
  • 画像のダウンロード

といった重い処理を繰り返し実行すると
メモリをたくさん使用したり
時間がかかるためユーザ体験を損なってしまう可能性もあります。

Combine.frameworkでは
そのような時に利用できるメソッドやクラスが用意されています。

今回はそういったメソッドやクラスの違いについて
見てみたいと思います。

下記のサンプルコードは全てPlayground上で実行しています。

share

shareメソッドでは
subscribeする度に新しいPublisherを生成するのではなく
Publisher参照を共有するようにします。

Publisherはだいたいがstructであるため
関数の引数で使用される場合やプロパティに代入する際は
コピーを生成します。

shareメソッドを呼ぶと戻り値には
Publishers.Shareという
クラスのインスタンスが返ってきます。

Publishers.Share
https://developer.apple.com/documentation/combine/publisher/3204754-share

このクラスはクラスが生成されるよりも
前(上流)のPublisherを保持することで
既存のPublisherを使用することができます。

最初のsubscribeで
Publisherは処理を開始し値を流し始めます。

そしてそれ以降のsubscribeからは
同じPublisherからの値の出力を受け取ることができます。

それでは具体的な例として
ネットワークリクエストの結果を
複数のSubscriberで共有する例を見ていきたいと思います。

var cancellables: Set<AnyCancellable> = []
let shared = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
    .map(\.data)
    .print("shared")
    .share() // **ここでshareを呼んでいる**

print("subscribe 1回目")
shared
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)
print("subscribe 2回目")
shared
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription2 receiveValue: '\($0)'") })
    .store(in: &cancellables)

// 出力結果
subscribe 1回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribe 2回目
shared: receive value: (13761 bytes)
subscription1 receiveValue: '13761 bytes'
subscription2 receiveValue: '13761 bytes'
shared: receive finished

出力結果を見てみると
1回目のsubscribeでは

subscribe 1回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited

と(上流の)Publisherへsubscribeしていますが

2回目の場合

subscribe 2回目
shared: receive value: (13761 bytes)
subscription1 receiveValue: '13761 bytes'
subscription2 receiveValue: '13761 bytes'
shared: receive finished


1回目のsubscribeで出力されていた

shared: receive subscription: (DataTaskPublisher)
shared: request unlimited

がなく
subscribeされていない
ことがわかりました。

それでも2回目のsubscribeも値を受け取っています。

このような結果から
Publisherは一つしか存在していないことがわかります。

では
shareがなかった場合を見てみます。

var cancellables: Set<AnyCancellable> = []
let shared = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
    .map(\.data)
    .print("shared")
    //.share() // **コメントアウト**

print("subscribe 1回目")
shared
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)
print("subscribe 2回目")
shared
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription2 receiveValue: '\($0)'") })
    .store(in: &cancellables)

// 出力結果
subscribe 1回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribe 2回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (13761 bytes)
subscription2 receiveValue: '13761 bytes'
shared: receive finished
shared: receive value: (13763 bytes)
subscription1 receiveValue: '13763 bytes'
shared: receive finished

となり
2回目のsubscribe時でも

subscribe 2回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited

と出力されています。

つまりPublisherのコピーが生成され
Publisherが2つ存在していることがわかります。

このようにshareを使うことで
不要なPublisherインスタンスを生成しなくなりました。

ただし
shareには注意点もあります。

shareでは
subscribeする前に出力された値を再度出力しません。

つまり
参照を保持するタイミングによっては
期待したい値が得られないかもしれません。


参照を保持する時点で
Publisherがcompletionしていた場合は
finished(completionイベント)のみが返ってきます。
※ sink(receiveCompletion:)の方に値が流れてきます。

下記の例を見てみます。

var cancellables: Set<AnyCancellable> = []
let shared = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
    .map(\.data)
    .print("shared")
    .share()

print("subscribe 1回目")
shared
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("subscribe 2回目")
    shared
        .sink(receiveCompletion: { print("subscription2 receiveCompletion \($0)")},
              receiveValue: { print("subscription2 receiveValue: '\($0)'") })
        .store(in: &cancellables)
}

// 出力結果
subscribe 1回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (13750 bytes)
subscription1 receiveValue: '13750 bytes'
shared: receive finished
subscribe 2回目
subscription2 receiveCompletion finished

出力結果からもわかるように
2回目のsubscribeのタイミングを遅らせると
Publisherはすでにcompletionしているため
finished(completionイベント)のみを受け取っていることがわかります。

multicast

上記のshareではタイミングによっては
値を受け取れない可能性がありました。

そこで
Combineでは値の出力を能動的にコントロールできる
multicastというメソッド(戻り値はPublishers.Multicastクラス)があります。

https://developer.apple.com/documentation/combine/publisher/3204734-multicast
https://developer.apple.com/documentation/combine/publisher/3204733-multicast
https://developer.apple.com/documentation/combine/publishers/multicast

これはConnectablePublisherプロトコルに適合しています。

/// A publisher that uses a subject to deliver elements to multiple subscribers.
final public class Multicast<Upstream, SubjectType>
    : ConnectablePublisher
    where Upstream : Publisher, SubjectType : Subject,
Upstream.Failure == SubjectType.Failure, Upstream.Output == SubjectType.Output {

https://developer.apple.com/documentation/combine/connectablepublisher

このプロトコルはconnectというメソッドを有しており
connectを呼び出して初めてPublisherSubscriberを受け取りを処理を開始します。
https://developer.apple.com/documentation/combine/connectablepublisher/3204394-connect

shareの例を
multicastに変えて違いを見てみます。

var cancellables: Set<AnyCancellable> = []
let multicasted = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
    .map(\.data)
    .print("shared")
    .multicast { PassthroughSubject<Data, URLError>() }

print("subscribe 1回目")
multicasted
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)
print("subscribe 2回目")
multicasted
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription2 receiveValue: '\($0)'") })
    .store(in: &cancellables)

multicasted
    .connect()
    .store(in: &cancellables)

// 出力結果
subscribe 1回目
subscribe 2回目
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (13741 bytes)
subscription1 receiveValue: '13741 bytes'
subscription2 receiveValue: '13741 bytes'
shared: receive finished

上記の結果では
shareと同じ結果を取得できました。

では
ここでconnectをコメントアウトしてみると

...

//multicasted
//    .connect()
//    .store(in: &cancellables)

// 出力結果
subscribe 1回目
subscribe 2回目

となり
subscribeしてはいるものの
Publisherはまだ処理を実行していません。

このことからmulticastでは
connectを呼ばないと
処理を実行しないことがわかります。

では
2回目のsubscribeを少し遅らせ
connectを呼び出してみた場合を今度は見てみます。

var cancellables: Set<AnyCancellable> = []
let multicasted = URLSession.shared
    .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
    .map(\.data)
    .print("shared")
    .multicast { PassthroughSubject<Data, URLError>() }

print("subscribe 1回目")
multicasted
    .sink( receiveCompletion: { _ in },
           receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("subscribe 2回目")
    multicasted
        .sink( receiveCompletion: { _ in },
               receiveValue: { print("subscription2 receiveValue: '\($0)'") })
        .store(in: &cancellables)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("connect")
    multicasted
        .connect()
        .store(in: &cancellables)
}

// 出力結果
subscribe 1回目
subscribe 2回目
connect
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (13745 bytes)
subscription1 receiveValue: '13745 bytes'
subscription2 receiveValue: '13745 bytes'
shared: receive finished

connectが呼ばれる前にsubscribeしているため
2回目のsubscribeでもデータがきちんと取得できています。

これでshareで起きていた問題は緩和できそうですが
ちょっと書き方が複雑であったり
connectを呼ぶタイミングによっては
subscribeし損ねてしまう可能性もまだ残っています。

他のライブラリでは出力された値を再度流してくれるメソッドがあります。

例えば
RxSwiftではshareReplayというメソッドと利用することで
指定したサイズ分の流れてきたデータをキャッシュすることができ
subscribe時にその値が流れてくるようにできますが
Combineでは現状存在しません。

もし必要な場合は↓のように独自の実装が必要になります。
https://github.com/tcldr/Entwine/blob/master/Sources/Entwine/Operators/ShareReplay.swift


補足になりますが
ConnectablePublisherプロトコルには
autoconnectというメソッドもありこれを呼び出した場合は
subscribeすると自動で処理を開始して値の出力を始めます。

https://developer.apple.com/documentation/combine/connectablepublisher/3235788-autoconnect

Future

少し形が違いますが
sharemulticastと同様に
処理の結果を複数のSubscriberで共有できます。

https://developer.apple.com/documentation/combine/future

他と違う特徴としては
処理が実行されるタイミングが

初めてSubscribeされたタイミング

ではなく

Futureインスタンスが生成されたタイミング

になります。

下記の例を見ていきます。

var cancellables: Set<AnyCancellable> = []
let future = Future<Data, URLError> { promise in
    URLSession.shared
        .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
        .map(\.data)
        .print("shared")
        .sink(receiveCompletion: {
            if case .failure(let error) = $0 {
                promise(.failure(error))
            }
        }, receiveValue: {
            promise(.success($0))
        }).store(in: &cancellables)
}

// 出力結果
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (14676 bytes)
shared: receive finished

上記のようにsubscribeしていない状態でも
クロージャ内の処理が実行されていることがわかります。

ではsubscribeしてみます。

var cancellables: Set<AnyCancellable> = []
let future = Future<Data, URLError> { promise in
    URLSession.shared
        .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
        .map(\.data)
        .print("shared")
        .sink(receiveCompletion: {
            if case .failure(let error) = $0 {
                promise(.failure(error))
            }
        }, receiveValue: {
            promise(.success($0))
        }).store(in: &cancellables)
}

print("subscribe 1回目")
future
    .sink(receiveCompletion: { print("subscription1 receiveCompletion: '\($0)'") },
            receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)

print("subscribe 2回目")
future
    .sink(receiveCompletion: { print("subscription2 receiveCompletion: '\($0)'") },
        receiveValue: { print("subscription2 receiveValue: '\($0)'") })
    .store(in: &cancellables)

// 出力結果
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribe 1回目
subscribe 2回目
shared: receive value: (14676 bytes)
subscription1 receiveValue: '14676 bytes'
subscription1 receiveCompletion: 'finished'
subscription2 receiveValue: '14676 bytes'
subscription2 receiveCompletion: 'finished'
shared: receive finished

このようにFuture内の処理は1回しか行われていませんが
2つのSubscriberはどちらも値を受け取ることができています。

では処理が完了した後に
subscribeした場合はどうでしょうか?

var cancellables: Set<AnyCancellable> = []
let future = Future<Data, URLError> { fulfill in
    URLSession.shared
        .dataTaskPublisher(for: URL(string: "https://www.google.com")!)
        .map(\.data)
        .print("shared")
        .sink(receiveCompletion: {
            if case .failure(let error) = $0 {
                fulfill(.failure(error))
            }
        }, receiveValue: {
            fulfill(.success($0))
        }).store(in: &cancellables)
}

print("subscribe 1回目")
future
    .sink(receiveCompletion: { print("subscription1 receiveCompletion: '\($0)'") },
            receiveValue: { print("subscription1 receiveValue: '\($0)'") })
    .store(in: &cancellables)

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("subscribe 2回目")
    future
        .sink(receiveCompletion: { print("subscription2 receiveCompletion: '\($0)'") },
            receiveValue: { print("subscription2 receiveValue: '\($0)'") })
        .store(in: &cancellables)
}

// 出力結果
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribe 1回目
shared: receive value: (14676 bytes)
subscription1 receiveValue: '14676 bytes'
subscription1 receiveCompletion: 'finished'
shared: receive finished
subscribe 2回目
subscription2 receiveValue: '14676 bytes'
subscription2 receiveCompletion: 'finished'

上記のように2回目のsubscribeでも
値を受け取れていることがわかりました。

Futureの注意点としては
処理が実行されるタイミングが
Futureインタンスが生成された時点になりますので
subscribeされた後に必要な処理などがある場合は
予期せぬ挙動に遭遇する可能性があります。

まとめ

出力結果を共有できるメソッドやクラスについて見ていきました。

メモリの使用量や通信量を抑えることができるという点で
非常に有用なものですが

いつPublisherは処理を開始して値を出力し始めるのか?
いつsubscribeすると値を受け取ることができるのか?

といったことを知らないと
「何が起きているんだ。。。?」
と悩んでしまうような事象に出くわすかもしれません。

そのような自体にならないためにも
違いを比較して見ていくことは大切だなと感じています。

Combine.frameworkはメソッドやクラスがたくさんあり
全てをは把握することは大変ですが
ある分類に分けて少しずつみていくと
効率的に把握できるのかなと思います。?

もし間違いなどございましたらご指摘いただけますと助かります??‍♂️

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

swift入門の為のメモ

実際には運用するアプリの制作入門としてのメモを随時記載

1、まずはメニュー有りき

// SubViewController へ遷移するために Segue を呼び出す

    performSegueWithIdentifier("showDetailView",sender: nil)

// Segueで遷移時の処理
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if (segue.identifier == "showDetailView") {
        let secondVC: ViewController4 = (segue.destinationViewController as? ViewController4)!

        // showDetailViewのtextに選択した文字列を設定する
        print("segue")
        print(view.frame.height)
        //secondVC.itemid = selectedText


    }
}

2、アプリ内記憶処理

3、sqlite3の利用

4、サーバーサイドのシステムとの連動

5、サーバーサイドデーター利用

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

iOS 13以上でサイレントプッシュのdidReceiveRemoteNotificationが呼ばれない問題

最近、個人的な都合で英語生活してるので、本記事も両言語で書いてみます。と言っても大した量じゃありませんが。

I'd like to write this article in both language Japanese and English due to personal reasons these days.

日本語版

didReceiveRemoteNotificationメソッドがiOS 13以上で呼ばれない理由

特定の手続きをしないと、iOS 13以上でサイレントプッシュを使っている時にAppDelegateのdidReceiveRemoteNotificationが呼ばれないことがわかりました。

AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    print("PUSH arrived.")
    completionHandler(.noData)
}

サーバー側でヘッダーにapns-push-type: backgroundを付けるか、もしくは以下のようにペイロードJSONのaps部分に空のalertを付けると、無事呼ばれるようになりました。

body.json
{
    "aps": {
        "alert": {},
        "content-available": 1
    }
}

WWDC2019で発表された正式な方法らしい前者が理想ですが、もしサーバーがカスタムヘッダーをサポートしていない場合は後者のほうが簡単です。

参考文献

English ver.

The reason your didReceiveRemoteNotification method won't work on iOS 13 or above

I found that didReceiveRemoteNotification method on AppDelegate without a specific procedure won't be called if you use a background push on iOS 13 or above.

AppDelegate.swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    print("PUSH arrived.")
    completionHandler(.noData)
}

The solution I finally found is adding apns-push-type: background to your header field, or adding an empty alert field in the aps section in your custom payload as follows.

body.json
{
    "aps": {
        "alert": {},
        "content-available": 1
    }
}

The former is ideal because it's an official way that was revealed in WWDC2019, but the latter would be easier if your server doesn't support any customizations of push headers.

References

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

ReactiveX入門

はじめに

  • ReactiveX の概念を理解するためにまとめた資料です
  • 社内クローズドな勉強会で話した内容を Qiita 向けに修正しています
  • 前提としてデザインパターンの知識がちょっと必要です
  • サンプルコードはほぼ Swift + RxSwift です

ReactiveXとは

ReactiveXとは

ReactiveX (以降 Rx) は Observable シーケンスを使った非同期でイベントベースのプログラムを構成するためのライブラリ。

デザインパターンの Observer パターン を下記の2点で拡張したもの。

  1. データやイベントのシーケンス をサポートする
  2. 宣言的にシーケンスを構成できるようにするための オペレータ を追加する

また、低レベルなスレッディング、同期、スレッドセーフ、並行データ構造、ノンブロッキングI/Oのような懸念事項を抽象化してくれる。

(引用元: http://reactivex.io/intro.html)

Rx は Observer パターンの拡張

  1. データやイベントのシーケンスをサポートする
  2. 宣言的にシーケンスを構成できるようにするためのオペレータを追加する

【復習】Observerパターン

Observerパターン

オブジェクトのイベントを他のオブジェクトへ通知するためのパターン。GoF による デザインパターン の一つ。
- Observerable :point_right: 通知する側のオブジェクト=監視可能なオブジェクト。Observer に監視される。
- Observer :point_right: 通知される側のオブジェクト=監視するオブジェクト。Observerable を 監視する。

49dea009-175c-4702-b15c-c082cb8d9ca7-1920x1814r.png

Observerパターンの例 (Swift)

observer-pattern
import Foundation

// MARK: - Interfaces

protocol IObserver: class {

    associatedtype Value

    func notify(_ value: Value)
}

protocol IObservable: class {

    associatedtype Value

    associatedtype Observer: IObserver where Observer.Value == Value

    func registerObserver(_ observer: Observer)

    func unregisterObserver(_ observer: Observer)

    func notifyObservers(_ value: Value)
}


// MARK: - Implementations

class IntObserver: IObserver {

    typealias Value = Int

    let name: String

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

    func notify(_ value: Value) {
        print("\(name) -> \(value)")
    }
}

class IntObservable: IObservable {

    typealias Value = Int

    typealias Observer = IntObserver

    func registerObserver(_ observer: Observer) {
        observers.append(observer)
    }

    func unregisterObserver(_ observer: Observer) {
        observers.removeAll(where: { $0 === observer })
    }

    func notifyObservers(_ value: Observer.Value) {
        observers.forEach { $0.notify(value) }
    }

    private var observers: [Observer] = []
}

// MARK: - Examples

let observable = IntObservable()

// register observers
observable.registerObserver(IntObserver(name: "A"))
observable.registerObserver(IntObserver(name: "B"))

//notify
observable.notifyObservers(10)
observable.notifyObservers(20)
result
A -> 10
B -> 10
A -> 20
B -> 20

Observable ≒ データやイベントのシーケンス

observable.notifyObservers(10)
observable.notifyObservers(20)
  • Observer には Observable から 10, 20 というデータが順番に非同期に通知される
  • また、ここで通知されるものはデータに限らず、例えばボタンクリックのようなイベントかもしれない
  • つまり、Observable は データやイベントのシーケンス とみなせる

:point_right: データやイベントの非同期なシーケンスに対して順番に反応する Reactive Programing

【復習】Iterator パターン

Iterator パターン

  • 集約したオブジェクトを列挙する手段を提供するデザインパターン。これも GoF の一つ。
  • モダンなプログラミング言語のコレクションコンテナでは大抵実装されている。

13c320f6-ef02-48c4-843a-3e733908b36b-1920x2407r.png

Iterator パターンの例 (Swiftのコレクション)

Swift の配列などのコレクションは Iterator パターンを定義した Sequence というプロトコル(≒ Interface)に準拠している。

swift-sequence
protocol IteratorProtocol {

    associatedtype Element

    mutating func next() -> Element?

}

protocol Sequence {

    associatedtype Element

    associatedtype Iterator: IteratorProtocol

    func makeIterator() -> Iterator

}

Iterator パターンの例 (Swiftのコレクション)

  • Sequence プロトコルに準拠していると Iterator によりコレクションの要素を次のように列挙することができる。
  • next()nil (null) を返すと列挙は完了する。
array-iterator
let array = [1, 2, 3, 4, 5]

var iterator = array.makeIterator()
while let next = iterator.next() {
    print(next)
}
  • (おまけ)上記のコードは Swift の for in 構文でシンプルに記述することができる。
for-in
let array = [1, 2, 3, 4, 5]
for element in array {
    print(element)
}

Observer/Iterator パターンのまとめ

Observer/Iterator パターンのまとめ

  • Observer パターンの Observable はデータやイベントの シーケンス とみなせる
  • Iterator パターンはシーケンスの要素を 次へ (Next) により順番に得ることができる
  • また、次の要素がない場合はシーケンスの 完了 (Complete) となる

コレクション と Observable (Rx)

コレクション と Observable (Rx) ? どちらもシーケンス

コレクションは Iterator パターンにより要素を順番に処理できる。

let array = [1, 2, 3, 4, 5]
var iterator = array.makeIterator()
while let next = iterator.next() {
    print(next)
}

これを Rx の Observable の世界にするとこうなる。

let observable = Observable.of(1, 2, 3, 4, 5)
observable.subscribe(onNext: { next in
    print(next)
}

? 似てるよね!

コレクション と Observable (Rx) の違い

  • コレクションはシーケンスの全ての要素を同時に処理する
  • Rx の Observable はシーケンスの要素が非同期に放出され、そのタイミングで処理する 6f95afb4-39c6-4cde-bbac-ae17bb13686d.gif

(引用元: https://www.atmarkit.co.jp/fdotnet/introrx/introrx_01/introrx_01_01.html)

? Rx の Observable は時間軸に乗る非同期なストリーム(小川)

コレクション と Observable (Rx) の違い (ソースコードで比べる)

コレクションでは next() で能動的に次の要素を取りに行く。

let array = [1, 2, 3, 4, 5]
var iterator = array.makeIterator()
while let next = iterator.next() {
    print(next)
}

Rx の Observable では、それを 購読 (Subscribe) し、次の要素が放出されるのを onNext で待つ。

let observable = Observable.of(1, 2, 3, 4, 5)
observable.subscribe(onNext: { next in
    print(next)
}

Rx のマーブルダイアグラム

Rx の Observable はマーブルダイアグラムにより図示される。
929103d8-c5a2-4bdc-8974-8550a52105fe-1920x905r.png

Rx とは Observer パターンの拡張

  1. データやイベントのシーケンスをサポートする
  2. 宣言的にシーケンスを構成できるようにするためのオペレータを追加する

オペレータ

コレクションのオペレータ

モダンなプログラミング言語ではコレクションに対する様々なオペレータとサポートする。

  • 射影 : map, flatMap, ...
  • 抽出 : filter, take, ...
  • 合成 : concat, zip, ...
  • 集計 : reduce, distinct, ...
  • 並び替え : sort, reverse, ...

(参考: https://qiita.com/amay077/items/9d2941283c4a5f61f302)

コレクションのオペレータの例 (Swift)

整数のコレクションに次のオペレーション行う。

  • 全ての要素に3を足す
  • そこから偶数のみを抽出する
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array.map { $0 + 3 }                    // [4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
    .filter { $0.isMultiple(of: 2) }    // [4, 6, 8, 10, 12]
    .subscribe(onNext: { print($0) }

Observable のオペレータ

ほぼ同じ!Observable もシーケンス。Rx からは様々なオペレータが提供されている。

let observable = Observable.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
observable.map { $0 + 3 }               // [4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
    .filter { $0.isMultiple(of: 2) }    // [4, 6, 8, 10, 12]
    .subscribe(onNext: { print($0) }

Observable の具体的な例 (Spread Sheet)

みんな大好きスプレッドシート。

cfcd46eb-e679-4ea4-8305-39a86587e327-1920x435r.png

let a1 = Observable.of(1)
let b1 = Observable.of(2)
let c1 = Observable.combineLatest(a1, b1).map { $0 + $1 }

Observable の具体的な例 (UI)

検索バーにキーワードを入力するたびに検索結果が表示される(インクリメンタルサーチ)

// テキストフィールドからの入力が変化する度に、その入力内容が放出される
textField.rx.text.asObservable()
    // 連続入力でAPIを叩きすぎないようにするために200msec以内の連続入力は無視する
    .debounce(.milliseconds(200), scheduler: MainScheduler.instance)
    // 入力内容で検索APIを実行
    .flatMap { keyword in
        API.search(by: keyword).asObservable()
    }
    // その結果を購読し、処理する(結果一覧を表示する、など)
    .subscribe(onNext: { result in
        print(result)
    })

Rxの起源

  • Microsoft Research の実験的なプロジェクトとして始まる
  • 2009年に .NET Framework 向けの Rx.NET を提供開始
  • 2011年に正式な製品に昇格し、以降各プラットフォームに移植される
  • Rx 全体の開発を現在リードしているのは Netflix (MSでRxやってた人が転職した先)
  • Rx のオペレータは 2007年にリリースされた C# 3.0 の新機能 LINQ の非同期/イベント版といえる

(参考: https://www.atmarkit.co.jp/fdotnet/introrx/introrx_01/introrx_01_01.html)
(参考: https://www.buildinsider.net/column/kawai-yoshifumi/004)

Rxの対応プラットフォーム

触れないプラットフォームなど、(ほぼ)ない! Let's play with Rx!

言語/PF Rx
Java RxJava
Java (Android) RxAndroid
JavaScript RxJS
C# Rx.NET
C#(Unity) UniRx
Scala RxScala
Clojure RxClojure
C++ RxCpp
Lua RxLua
Ruby Rx.rb
Python RxPY
Go RxGo
Groovy RxGroovy
JRuby RxJRuby
Kotlin RxKotlin
Swift RxSwift
Swift (iOS/macOS) RxCocoa
PHP RxPHP
Elixir reaxive
Dart RxDart

おまけ

脱Rxの兆し

iOS/Android 界隈では脱 Rx の兆しが...

  • iOS : Apple が Swift 用 RP フレームワーク Combine を提供開始
    • iOS13以降で利用可能。Swift UI のためのフレームワークでもある。
  • Android : Kotlin の Coroutine 機能に RP 的機能が増えてきている
    • Channel, Flow, ...

Rx is not FRP

ReactiveX はたまに Functional Reactive Programming (FRP) と称されるが、それは 誤り 。ReactiveX は Functional で Reactive かもしれないが、FRP とは別のもの。

Difference
FRP operates on values that change continuously over time
時間とともに継続的に変化する値を操作する
ReactiveX operates on discrete values that are emitted over time
時間の経過とともに放出される離散的な値を操作する

なるほど、わからん\(^o^)/

(参考: https://twitter.com/ReactiveX/status/483625917491970048)

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