20190524のiOSに関する記事は9件です。

pod installしてるのにライブラリ使えない!

プロジェクトの開き方を確認してみてください

https://stackoverflow.com/questions/32906165/xcode-not-detecting-pods-directory

You should open .xcworkspace, not .xcodeproj.

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

よく使うUIKitの基本

こちらの記事はTUBの企画の際に使用したものです。

UIKitとは

UIKitとは、iOSアプリを構築するために必要なクラスを提供してくれるライブラリです。
簡単に言うとiOSアプリの画面をレイアウトしたりするには絶対必要なものと思っていただいて大丈夫です。

Xcodeで新規プロジェクトを立ち上げるとデフォルトで生成されているViewController.swiftを開くとこのようになっているかと思います。(最初にコメントアウトされている部分は気にせず削除してしまって問題ないです)

ViewController.swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

一番上にはこう書かれています、

import UIKit

さっそく今回説明するUIKitが書かれています。

その前にimportについて軽く説明すると、「インポート」ですので単語の意味そのままになりますが、あらかじめ用意されているフレームワークを.Swiftファイル内で使えるようにするために書くコードです。

iOSアプリの画面をレイアウトしたり、管理したりするためのクラスは「UIKit」フレームワークに含まれているため、
今回の場合だと、ViewController.swiftファイル内でUIKitを使えるようにしますよ。といった意味になります。

UIKitではどんなものが提供されているか

import Foundation
import UIKit.DocumentManager
import UIKit.NSAttributedString
import UIKit.NSDataAsset
import UIKit.NSFileProviderExtension
import UIKit.NSIndexPath_UIKitAdditions
import UIKit.NSItemProvider_UIKitAdditions
import UIKit.NSLayoutAnchor
import UIKit.NSLayoutConstraint
import UIKit.NSLayoutManager
import UIKit.NSParagraphStyle
import UIKit.NSShadow
import UIKit.NSStringDrawing
import UIKit.NSText
import UIKit.NSTextAttachment
import UIKit.NSTextContainer
import UIKit.NSTextStorage
import UIKit.UIAccelerometer
import UIKit.UIAccessibility
import UIKit.UIAccessibilityAdditions
import UIKit.UIAccessibilityConstants
import UIKit.UIAccessibilityContainer
import UIKit.UIAccessibilityContentSizeCategoryImageAdjusting
import UIKit.UIAccessibilityCustomAction
import UIKit.UIAccessibilityCustomRotor
import UIKit.UIAccessibilityElement
import UIKit.UIAccessibilityIdentification
import UIKit.UIAccessibilityLocationDescriptor
import UIKit.UIAccessibilityZoom
import UIKit.UIActionSheet
import UIKit.UIActivity
import UIKit.UIActivityIndicatorView
import UIKit.UIActivityItemProvider
import UIKit.UIActivityViewController
import UIKit.UIAlert
import UIKit.UIAlertController
import UIKit.UIAlertView
import UIKit.UIAppearance
import UIKit.UIApplication
import UIKit.UIApplicationShortcutItem
import UIKit.UIAttachmentBehavior
import UIKit.UIBarButtonItem
import UIKit.UIBarButtonItemGroup
import UIKit.UIBarCommon
import UIKit.UIBarItem

こちらは先ほど紹介したimport UIKitのUIKitをCommand + クリックで定義されている場所に移動した時に表示されています。ですがこれはほんの一部で実際はもっと何十行と下に続いています。

初めて見た時はよくわからないかと思いますが、これはUIKit内に存在しているクラスの一覧です。

よく使うUIKit

ざっくりと基本的なUIKitのみを抜粋してみました。
もちろんこれだけではなく、覚える必要があるものはたくさんあります。

よく使う基本的なUIKit 説明
UILabel テキストを表示する
UIButton ボタンのUIを提供する
UITextField テキストを入力するフィールド
UIImageView 画像を表示する
UITableView リスト表示などに使用
UICollectionView タイル貼りのように複数列表示することができる
UIScrollView 画面のスクロールを管理する
UITextView 複数行、スクロール可能なテキストを表示する
UISwitch ON/OFFすることができるUI
UIDatePicke 年月日を選択する

この中で今回は、UITableView、UICollectionView、UIScrollViewについてもう少し詳しく説明していきたいと思います。

TableViewの特徴

以下の画像のようにデータをリストとして単一の列に縦方向に表示したい時に使用します。
ちなみにAndroidで同じようにリスト形式で表示するのにListViewがありますが、最近はListViewではなくRecyclerViewと言われるものが使われるみたいです。

TableViewには「Plain」と「Grouped」の2種類のスタイルがあります。

スクリーンショット 2019-05-25 1.34.44.png

それと、TableViewにはセクションヘッダーとセクションフッターがあります。

TableViewを使用して実装した画面がどのようなものかイメージがつきやすいところで、iPhoneの設定画面を例に上げることができます。

IMG_AD52221B4988-1.jpeg

CollectionViewの特徴

格子状に部品を配置することができます。
TableViewは縦方向のみの表示でしたが、CollectionViewでは縦方向に加えて横方向にも表示することができます。
AndroidだとGridViewやTableLayoutと言われるものがiOSのCollectionViewと同じ働きをするみたいです。

CollectionViewにも同様、セクションヘッダーとセクションフッターがあります。

某フリマアプリの商品一覧とかはCollectionViewで表示しているかと思います。
また、iPhone標準のMusicアプリなんかも横方向にスライドできる部分はCollectionViewで実装されています。

IMG_0694.PNG

ScrollViewの特徴

画面に入りきらないような大きな画像を表示したり、電子書籍のように複数のページを送って表示させたい場合などに使用します。
LPのようなシンプルな画面を縦スクロールさせたい時にも使用します。

ユーザが画面上をスクロールするとUIScrollViewが移動し、表示領域が変化します。

CollectionViewはAndroidも同じ名前みたいです。
間違っていたらすみません。

まとめ

今回は良く使うUIKitについて、それぞれの使用用途や実際に使用して作られた画面の例を紹介しました。
この画面はTableViewをを使って実装した方が良いな。とか、こっちの画面はTableViewではなくCollectionViewを使って実装だな。みたいなことが、アプリのデザインを見た時に判断・イメージができるようになることがアプリ開発をする際にとても役に立ちます。

日頃から色々なアプリを触って、どうやってこのUI実装しているんだろう、このアニメーションすごいなとか、そういった観点でアプリを使用してみると楽しいですし、けっこう勉強になります。

さいごに

弊社では毎週土曜日都内でエンジニアやデザイナーが集まるコミュニティーを開催しています!
一緒に勉強したい方や、興味がある方はぜひご参加ください!!
https://www.tokyo-uppers-boost.mht-code.com/

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

Xcode10.2でシミュレーターを使った際に「/usr/lib/swift/libswiftCore.dylib: mach-o, but not built for iOS simulator」が発生してクラッシュする

概要

XcodeでRun Testしたところ、Reason: no suitable image found. Did find:/usr/lib/swift/libswiftCore.dylib: mach-o, but not built for iOS simulatorでランタイムエラーが発生

ログ

Showing All Messages
xctest (14228) encountered an error (Failed to load the test bundle. (Underlying error: The bundle “BallcapTests” couldn’t be loaded because it is damaged or missing necessary resources. The bundle is damaged or missing necessary resources. dlopen_preflight(/Users/ando/Library/Developer/Xcode/DerivedData/Ballcap-fmvoaoxolqhohiethegcuqxocwpu/Build/Products/Debug-iphonesimulator/BallcapTests.xctest/BallcapTests): Library not loaded: @rpath/libswiftCore.dylib
  Referenced from: /Users/ando/Library/Developer/Xcode/DerivedData/Ballcap-fmvoaoxolqhohiethegcuqxocwpu/Build/Products/Debug-iphonesimulator/BallcapTests.xctest/BallcapTests
  Reason: no suitable image found.  Did find:
    /usr/lib/swift/libswiftCore.dylib: mach-o, but not built for iOS simulator))

環境

Mac 10.14.5(18F132)
Xcode Version 10.2.1 (10E1001)
Simulator iPhoneX iOS12.0 12.1
Swift 5
対象のレポジトリ
https://github.com/1amageek/Ballcap-iOS

暫定対応

シミュレーターをiOS12.2にしたら動いた。

未検証

/usr/lib/swiftからシミュレーターに補完してあげると動くという情報あり

sudo cp -r /usr/lib/swift/*.dylib /Applications/Xcode.app/Contents/Frameworks
sudo touch /Applications/Xcode.app/Contents/Frameworks/.swift-5-staged

https://stackoverflow.com/questions/55633946/cant-build-app-because-of-2-paths-for-libswiftcore-dylib

関連情報

https://www.reddit.com/r/iOSProgramming/comments/b67fbc/xcode_102_release_builds_frozen/

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

Xcode10.1ではiOS12.2以降の検証が出来ない。

はじめに

こんにちは!
古いXcodeプロジェクトをBuildしようとしたらエラーが出たので、備忘として残しておきます。あんましAR関係ないです。
※記載間違い等あれば教えてください。

エラー内容について

err1.png

Could not locate device support files.
This iPhone ~~ (Model AXXXX, AXXXX, AXXXX) is running iOS 12.2 (16E227), which may not be supported by this version of Xcode.

<訳>
デバイスサポートファイルが見つかりませんでした。
このiPhone ~~(モデルAXXXX、AXXXX、AXXXX)はiOS 12.2(16E227)を実行しています。これは、このバージョンのXcodeではサポートされていない可能性があります。

環境

・Xcode Version 10.1 (10B61)
・iOS 12.2 (16E227)

・Development Target iOS 11.0
・Swift Version 3

原因

最新版であるXocde 10.2はSwift 3をサポートしていない為、
一つ前のバージョンであるXcode10.1を使っていました。
err2.png

Unsupported Swift Version
The project “pinnar” contains source code developed with Swift 3.x. This version of Xcode does not support building or migrating Swift 3.x targets.
Use Xcode 10.1 to migrate the code to Swift 4.

<訳>
サポートされていないSwiftのバージョン
プロジェクト“~~”はSwift 3.xで開発されたソースコードを含みます。 このバージョンのXcodeはSwift 3.xターゲットのビルドまたは移行をサポートしません。
Xcode 10.1を使用してコードをSwift 4に移行します。

ただしXcode10.1は、Development Target iOS 12.1までしかサポートしていません。
err3.png

その為、iOS 12.2以降のiOSにビルドするとエラーとなります。
※Build Successにはなります。

参考記事:Xcode,Swiftのバイナリ互換性

対策

このエラーが出た場合の対応は、以下いづれかです。

Xcode 10.1で頑張る。(iOS12.2用の更新を諦める。)
尚、今の所AppStoreにリリースされているアプリは、iOS12.2の端末でも引き続きダウンロード出来るようです。
逆にXcodeのバージョンを下げたい方は、
Downloads for Apple Develpoers (Xcode)からどうぞ

Xcode 10.2用に対応する為、Swiftのバージョンを4.2以上にアップデートする。
[Target]→[Build Setting]内に記載されている[SWIFT_VERSION = 3.x]を4.2以上に更新してください。
大半の場合は、Swift4.2以降で廃止になった文法やライブラリの更新も必要です。

Xcode 10.1に、Xode10.2のデバイスサポートファイルをコピーする
参考記事:Xcode7 で iOS10 の実機デバッグをする
裏技感ありますが、とりあえず通したいのであればこれが一番良さそうです。
(@zeeroさんありがとう御座います!)

まとめ

いよいよSwift3を対応してくれなくなってきたので、大変ですが更新しなければいけないですね...!
尚、Swift4.xからSwift5への更新はそこまで苦ではありませんでした!

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

Xcode10.1でiOS12.2以降の検証が出来ない場合

はじめに

こんにちは!
古いXcodeプロジェクトをBuildしたらエラーが出たので、備忘として残しておきます。あんましAR関係ないです。
※記載間違い等あれば教えてください。

エラー内容について

err1.png

Could not locate device support files.
This iPhone ~~ (Model AXXXX, AXXXX, AXXXX) is running iOS 12.2 (16E227), which may not be supported by this version of Xcode.

<訳>
デバイスサポートファイルが見つかりませんでした。
このiPhone ~~(モデルAXXXX、AXXXX、AXXXX)はiOS 12.2(16E227)を実行しています。これは、このバージョンのXcodeではサポートされていない可能性があります。

環境

・Xcode Version 10.1 (10B61)
・iOS 12.2 (16E227)

・Development Target iOS 11.0
・Swift Version 3

原因

最新版であるXocde 10.2はSwift 3をサポートしていない為、
一つ前のバージョンであるXcode10.1を使っていました。
err2.png

Unsupported Swift Version
The project “~~” contains source code developed with Swift 3.x. This version of Xcode does not support building or migrating Swift 3.x targets.
Use Xcode 10.1 to migrate the code to Swift 4.

<訳>
サポートされていないSwiftのバージョン
プロジェクト“~~”はSwift 3.xで開発されたソースコードを含みます。 このバージョンのXcodeはSwift 3.xターゲットのビルドまたは移行をサポートしません。
Xcode 10.1を使用してコードをSwift 4に移行します。

ただしXcode10.1は、Development Target iOS 12.1までしかサポートしていません。
err3.png

その為、iOS 12.2以降のiOSにビルドするとエラーとなります。
※Build Successにはなります。

参考記事:Xcode,Swiftのバイナリ互換性

対策

このエラーが出た場合の対応は、以下いづれかです。

1.Xcode 10.2用に対応する為、Swiftのバージョンを4.2以上にアップデートする。
[Target]→[Build Setting]内に記載されている[SWIFT_VERSION = 3.x]を4.2以上に更新してください。
大半の場合は、Swift4.2以降で廃止になった文法やライブラリの更新も必要です。

2.Xcode 10.1に、Xode10.2のデバイスサポートファイルをコピーする
裏技感ありますが、とりあえず通したいのであればこれが一番良さそうです。
(@zeeroさんありがとう御座います!)

参考記事:Xcode7 で iOS10 の実機デバッグをする

3.Xcode 10.1で頑張る。(iOS12.2用の更新を諦める。)
尚、既にAppStoreにリリースされているアプリは、今の所iOS12.2の端末でもダウンロード出来ました。
逆にXcodeのバージョンを下げたい方は、Downloads for Apple Develpoers (Xcode)からどうぞ

まとめ

いよいよSwift3を対応してくれなくなってきたので、大変ですが更新しなければいけないですね...!
尚、Swift4.xからSwift5への更新はそこまで苦ではありませんでした!

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

UIWebViewでよく使うデリゲートメソッド一覧(Swift)

UIWebViewでよく使うデリゲートメソッド一覧

webView(_:shouldStartLoadWith:navigationType:)

概要
ロードの開始前に呼ばれる
ロードするかどうか
false を返すとロードしない

用途
特定のURLの場合にロードしないようにする、など

FooViewController.swift
import UIKit

final class FooViewController: UIViewController, UIWebViewDelegate {

    func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
        if let url = request.url?.absoluteString, url.lowercased() == "https://example.com".lowercased() {
            return false
        }
        return true
    }

}

webViewDidFinishLoad(_:)

概要
ロードの完了後に呼ばれる

用途
インジケータを消す場合、など

FooViewController.swift
import UIKit

final class FooViewController: UIViewController, UIWebViewDelegate {

    @IBOutlet private weak var indicatorView: UIActivityIndicatorView!

    func webViewDidFinishLoad(_ webView: UIWebView) {
        self.indicatorView.stopAnimating()
    }

}

webView(_:didFailLoadWithError:)

概要
ロード時にエラーが発生した場合に呼ばれる

用途
エラーハンドリング
インジケータを消す場合、など

FooViewController.swift
import UIKit

final class FooViewController: UIViewController, UIWebViewDelegate {

    @IBOutlet private weak var indicatorView: UIActivityIndicatorView!

    func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
        self.indicatorView.stopAnimating()
    }

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

iOS12.2よりdevicemotion, deviceorientationがデフォルトで取得できなくなったことと、その検知方法

iOS12.2より、safariに「モーションと画面の向きのアクセス」項目が追加され、デフォルトでOFFの状態となったため、devicemotionイベント等を取得することができなくなりました。

設定はどうやって変えるの?

設定>Safariから「モーションと画面の向きのアクセス」項目が追加され、デフォルトでOFF
をONにすることで

どうやって検知したらいい?

ググると

if('ondevicemotion' in window) {}

的なコードが出てくるんですが、実際にiOS12.2の設定をOFFにしていても検知してくれません。

const id = setTimeout(() => {
                 //ここに動かない時にさせたい挙動を書く
             }, 500);
             window.addEventListener('devicemotion', (e) => clearTimeout(id));

このようなコードをかくことで、
つまり、デバイスオリエンテーションイベントが発生したときにタイマー処理をしているものをクリアしてキャンセル=>動かない時だけsetTimeout()の中身が実行される、ということができるようです。

参考

https://www.macrumors.com/2019/02/04/ios-12-2-safari-motion-orientation-access-toggle/
https://github.com/w3c/deviceorientation/issues/57#issuecomment-481039307

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

NotificationCenterの通知は果たして本当に遅いのだろうか?

はじめに

NotificationCenterを利用した通知が、なんとなく遅いイメージはありませんか?
果たして本当に通知が遅いのかどうかを、PublishSubjectPublishRelayと比較しながら見ていこうと思います。
ちなみにパフォーマンスチェックを行う環境は以下になります。

Mac mini 2018
Intel Core i7 3.2 GHz
DDR4 2667 MHz 32 GB
SSD 512 GB
iPhone XR 12.2 Simulator
Xcode 10.2.1
Swift 5
RxSwift 5.0.0

はじめにの追記(2019/05/25)

2019/05/24時点の記事ではNotificationCenterの通知がPublishSubjectなどと比べて速いという結論を出していましたが、Optimization Level -O0での検証しかしておらず、結論を出すには不十分な状態での記事公開となってしまっておりました。申し訳ございません。
Optimization Level -Osの場合という項目を追加し、再度結論を出しているので一読いただけますと幸いです。

パフォーマンスチェック

PublishSubject、PublishRelay、NotificationCenterの通知に関して、いくつかの観点でパフォーマンスチェックしようと思います。
パフォーマンスチェックには、XCTestのmeasureMetricsを利用します。

Optimization Level -O0の場合

CocoaPodsを利用し、特にビルド設定をいじらずにテストを実行した場合、importしているframeworkはOptimization Level -O0でビルドされた状態になると思います。
まずは、その状態で計測をしていきます。

Generic ArgumentがVoidの場合

ここでは100000回通知し、その都度監視先ではcountに1を足しつつ、countが100000になった場合は計測を終了する実装になっています。
NotificationCenterにVoidを通知するという概念はないので、post時にobjectuserInfonilであることとします。
また、通知の範囲は計測のメソッド内だけなので、NotificationCenter.defaultは利用せずにインスタンス化したものを利用します。

PublishSubject
func test_PublishSubject_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Void>()
        let to: Int = 100000
        var count: Int = 0

        _ = subject
            .subscribe(onNext: {
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            subject.onNext(())
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Void>()
        let to: Int = 100000
        var count: Int = 0

        _ = relay
            .subscribe(onNext: {
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            relay.accept(())
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
func test_NotificationCenter_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let to: Int = 100000
        var count: Int = 0

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: { _ in
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            nc.post(name: name, object: nil)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.272 sec 0.276 sec 0.151 sec
2 0.284 sec 0.280 sec 0.153 sec
3 0.276 sec 0.280 sec 0.151 sec
4 0.278 sec 0.284 sec 0.147 sec
5 0.274 sec 0.280 sec 0.148 sec

PublishRelayはPublishSubjectをラップしているため、あまり数値に大きな差は見られません。
ところがNotificationCenterが約半分の時間で完了しています。

Generic ArgumentがIntの場合

ここでは100000回通知し、監視先で受け取った値が100000になった場合は計測を終了する実装になっています。
NotificationCenterにIntを通知するという概念はないので、post時にuserInfo[String: Int]であることとします。

PublishSubject
func test_PublishSubject_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Int>()
        let to: Int = 100000

        _ = subject
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0..<to).forEach {
            subject.onNext($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Int>()
        let to: Int = 100000

        _ = relay
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0..<to).forEach {
            relay.accept($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
func test_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let key = "user-info-key"
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.userInfo?[key] as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: nil, userInfo: [key: $0])
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.264 sec 0.273 sec 0.343 sec
2 0.272 sec 0.273 sec 0.329 sec
3 0.267 sec 0.272 sec 0.329 sec
4 0.275 sec 0.274 sec 0.330 sec
5 0.270 sec 0.271 sec 0.348 sec

NotificationCenterは、PublishSubjectやPublishRelayと比べて遅い結果となりました。

post時にobjectを利用した場合

NotificationCenterのpost時、追加情報はuserInfoに渡すことになると思います。
objectには送信元のオブジェクトを渡しますが、それ以外のオブジェクトを渡すこともできます。
objectを利用した場合はどのような結果になるでしょうか。

NotificationCenter
func test_NotificationCenter_Int_with_object_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.object as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: $0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

userInfo object
1 0.343 sec 0.186 sec
2 0.329 sec 0.180 sec
3 0.329 sec 0.184 sec
4 0.330 sec 0.175 sec
5 0.348 sec 0.182 sec
PublishSubject PublishRelay NotificationCenter
1 0.264 sec 0.273 sec 0.186 sec
2 0.272 sec 0.273 sec 0.180 sec
3 0.267 sec 0.272 sec 0.184 sec
4 0.275 sec 0.274 sec 0.175 sec
5 0.270 sec 0.271 sec 0.182 sec

objectを利用した場合、userInfoと比べて約60%の時間で計測が完了しました。
PublishSubjectやPublishRelayと比べても速い結果となりました。

NotificationCenterをType-safeにラップした場合

通知するとき監視をして値を受け取るときの型を合わせるために、以下のようにNotificationCenterをラップして計測します。

NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(using: @escaping (T) -> Void) -> NSObjectProtocol {
        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: { if let v = $0 as? T { using(v) } })
    }

    func post(_ value: T) {
        nc.post(name: name, object: value)
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        _ = nc
            .addObserver {
                if $0 == to {
                    completion()
                }
            }

        (0...to).forEach {
            nc.post($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

Normal Type-safe
1 0.186 sec 0.178 sec
2 0.180 sec 0.174 sec
3 0.184 sec 0.173 sec
4 0.175 sec 0.177 sec
5 0.182 sec 0.174 sec

ラップした場合でも、特に大きな差はない結果となりました。

NotificationCenterのdefaultを利用した場合

それでは、NotificationCenterを利用範囲に合わせてインスタンス化したものではなく、NotificationCenter.defaultを利用した場合はどのような結果になるでしょうか。

final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter.default
    ...
}

計測結果

init() default
1 0.178 sec 0.512 sec
2 0.174 sec 0.531 sec
3 0.173 sec 0.526 sec
4 0.177 sec 0.518 sec
5 0.174 sec 0.525 sec

数値が約3倍になっています。
NotificationCenter.defaultは暗黙的に複数の監視登録がされているため、遅くなっていると考えられます。
NotificationCenterは遅いというイメージは、この場合に該当しているのかもしれません。

100個のNotification.NameをaddObserverしてから通知した場合

それでは、インスタンス化したNotificaionCenterに100個のNotification.Nameを監視登録して、本当に遅くなるのかを確認します。

NotificationCenter
func test_NotificationCenter_Int_performance() {

    let nc = NotificationCenter()
    (0..<100).forEach {
        nc.addObserver(forName: Notification.Name("performance-test-\($0)"),
                       object: nil,
                       queue: nil,
                       using: { _ in })
    }

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in

        let name = Notification.Name("performance-test")
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.object as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: $0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

default init()
1 0.512 sec 0.550 sec
2 0.531 sec 0.556 sec
3 0.526 sec 0.556 sec
4 0.518 sec 0.561 sec
5 0.525 sec 0.555 sec

インスタンス化したNotificaionCenterでも遅くなりました。
NotificationCenter.defaultには暗黙的に複数の監視登録されていると考えても、あながち間違ってはいなさそうです。

別途10個の監視登録をした場合

次に、PublishSubject、PublishRelayとNotificationCenterに別途10個の監視登録をしてから通知します。

PublishSubject
func test_PublishSubject_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = subject.subscribe()
        }

        _ = subject
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            subject.onNext($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = relay.subscribe()
        }

        _ = relay
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            relay.accept($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(using: @escaping (T) -> Void) -> NSObjectProtocol {
        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: { if let v = $0 as? T { using(v) } })
    }

    func post(_ value: T) {
        nc.post(name: name, object: value)
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = nc.addObserver(using: { _ in })
        }

        _ = nc
            .addObserver {
                if $0 == to {
                    completion()
                }
            }

        (0...to).forEach {
            nc.post($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 1.243 sec 1.235 sec 0.885 sec
2 1.220 sec 1.225 sec 0.879 sec
3 1.238 sec 1.223 sec 0.894 sec
4 1.229 sec 1.259 sec 0.856 sec
5 1.262 sec 1.284 sec 0.891 sec

この場合でも、NotificationCenterの方が速い結果となりました。

NotificationCenterでもErrorを扱えるようにした場合

PublishSubjectはErrorも通知できるので、NotificationCenterをラップしたクラスでもErrorを通知できるようにしてみます。

NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(onSuccess: ((T) -> Void)? = nil,
                     onError: ((Error) -> Void)? = nil) -> NSObjectProtocol {
        let using: (Notification) -> Void = { notification in
            guard let v = notification.object as? Result<T, Error> else {
                return
            }

            do {
                try onSuccess?(v.get())
            } catch {
                onError?(error)
            }
        }

        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: using)
    }

    func onSuccess(_ value: T) {
        nc.post(name: name, object: Result<T, Error>.success(value))
    }

    func onError(_ error: Error) {
        nc.post(name: name, object: Result<T, Error>.failure(error))
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        _ = nc
            .addObserver(onSuccess: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            nc.onSuccess($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject NotificationCenter
1 0.264 sec 0.220 sec
2 0.272 sec 0.221 sec
3 0.267 sec 0.222 sec
4 0.275 sec 0.222 sec
5 0.270 sec 0.224 sec

Errorを通知できるようにした場合でも、NotificationCenterの方が速い結果となりました。

Optimization Level -Osの場合

次は、carthageでOptimization Level -Osでビルドされたframeworkを利用してパフォーマンスチェックを行います。(TypeSafeNotificationCenterもframework化した成果物を利用します)

Optimization Level -O0のように、100000回通知し監視先で受け取った値が100000になった場合は終了する計測を行います。

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.067 sec 0.069 sec 0.197 sec
2 0.068 sec 0.070 sec 0.201 sec
3 0.067 sec 0.069 sec 0.194 sec
4 0.068 sec 0.069 sec 0.196 sec
5 0.069 sec 0.070 sec 0.191 sec

NotificationCenterの計測を完了するまでに、PublishSubjectやPublishRelayと比べて約3倍の時間がかかっている結果となりました。

結論

  • NotificationCenter.defaultを利用した場合は暗黙的に監視登録されているものがあるため遅い
  • 利用範囲に合わせてインスタンス化をした場合はPublishSubjectPublishRelayと比べても速い 利用範囲に合わせてインスタンス化をした場合かつOptimization Level -O0の場合はPublishSubjectPublishRelayと比べても速い

  • 利用範囲に合わせてインスタンス化をしても、Optimization Level -Osの場合はPublishSubjectPublishRelayの方が約3倍速い

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

NotificationCenterの通知は果たして本当に遅いのだろうか?[修正版]

はじめに

NotificationCenterを利用した通知が、なんとなく遅いイメージはありませんか?
果たして本当に通知が遅いのかどうかを、PublishSubjectPublishRelayと比較しながら見ていこうと思います。
ちなみにパフォーマンスチェックを行う環境は以下になります。

Mac mini 2018
Intel Core i7 3.2 GHz
DDR4 2667 MHz 32 GB
SSD 512 GB
iPhone XR 12.2 Simulator
Xcode 10.2.1
Swift 5
RxSwift 5.0.0

はじめにの追記(2019/05/25)

2019/05/24時点の記事ではNotificationCenterの通知がPublishSubjectなどと比べて速いという結論を出していましたが、Optimization Level -O0での検証しかしておらず、結論を出すには不十分な状態での記事公開となってしまっておりました。申し訳ございません。
Optimization Level -Osの場合という項目を追加し、再度結論を出しているので一読いただけますと幸いです。

パフォーマンスチェック

PublishSubject、PublishRelay、NotificationCenterの通知に関して、いくつかの観点でパフォーマンスチェックしようと思います。
パフォーマンスチェックには、XCTestのmeasureMetricsを利用します。

Optimization Level -O0の場合

CocoaPodsを利用し、特にビルド設定をいじらずにテストを実行した場合、importしているframeworkはOptimization Level -O0でビルドされた状態になると思います。
まずは、その状態で計測をしていきます。

Generic ArgumentがVoidの場合

ここでは100000回通知し、その都度監視先ではcountに1を足しつつ、countが100000になった場合は計測を終了する実装になっています。
NotificationCenterにVoidを通知するという概念はないので、post時にobjectuserInfonilであることとします。
また、通知の範囲は計測のメソッド内だけなので、NotificationCenter.defaultは利用せずにインスタンス化したものを利用します。

PublishSubject
func test_PublishSubject_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Void>()
        let to: Int = 100000
        var count: Int = 0

        _ = subject
            .subscribe(onNext: {
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            subject.onNext(())
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Void>()
        let to: Int = 100000
        var count: Int = 0

        _ = relay
            .subscribe(onNext: {
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            relay.accept(())
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
func test_NotificationCenter_Void_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let to: Int = 100000
        var count: Int = 0

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: { _ in
                count += 1
                if count == to {
                    completion()
                }
            })

        (0..<to).forEach { _ in
            nc.post(name: name, object: nil)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.272 sec 0.276 sec 0.151 sec
2 0.284 sec 0.280 sec 0.153 sec
3 0.276 sec 0.280 sec 0.151 sec
4 0.278 sec 0.284 sec 0.147 sec
5 0.274 sec 0.280 sec 0.148 sec

PublishRelayはPublishSubjectをラップしているため、あまり数値に大きな差は見られません。
ところがNotificationCenterが約半分の時間で完了しています。

Generic ArgumentがIntの場合

ここでは100000回通知し、監視先で受け取った値が100000になった場合は計測を終了する実装になっています。
NotificationCenterにIntを通知するという概念はないので、post時にuserInfo[String: Int]であることとします。

PublishSubject
func test_PublishSubject_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Int>()
        let to: Int = 100000

        _ = subject
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0..<to).forEach {
            subject.onNext($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Int>()
        let to: Int = 100000

        _ = relay
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0..<to).forEach {
            relay.accept($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
func test_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let key = "user-info-key"
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.userInfo?[key] as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: nil, userInfo: [key: $0])
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.264 sec 0.273 sec 0.343 sec
2 0.272 sec 0.273 sec 0.329 sec
3 0.267 sec 0.272 sec 0.329 sec
4 0.275 sec 0.274 sec 0.330 sec
5 0.270 sec 0.271 sec 0.348 sec

NotificationCenterは、PublishSubjectやPublishRelayと比べて遅い結果となりました。

post時にobjectを利用した場合

NotificationCenterのpost時、追加情報はuserInfoに渡すことになると思います。
objectには送信元のオブジェクトを渡しますが、それ以外のオブジェクトを渡すこともできます。
objectを利用した場合はどのような結果になるでしょうか。

NotificationCenter
func test_NotificationCenter_Int_with_object_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = NotificationCenter()
        let name = Notification.Name("performance-test")
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.object as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: $0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

userInfo object
1 0.343 sec 0.186 sec
2 0.329 sec 0.180 sec
3 0.329 sec 0.184 sec
4 0.330 sec 0.175 sec
5 0.348 sec 0.182 sec
PublishSubject PublishRelay NotificationCenter
1 0.264 sec 0.273 sec 0.186 sec
2 0.272 sec 0.273 sec 0.180 sec
3 0.267 sec 0.272 sec 0.184 sec
4 0.275 sec 0.274 sec 0.175 sec
5 0.270 sec 0.271 sec 0.182 sec

objectを利用した場合、userInfoと比べて約60%の時間で計測が完了しました。
PublishSubjectやPublishRelayと比べても速い結果となりました。

NotificationCenterをType-safeにラップした場合

通知するとき監視をして値を受け取るときの型を合わせるために、以下のようにNotificationCenterをラップして計測します。

NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(using: @escaping (T) -> Void) -> NSObjectProtocol {
        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: { if let v = $0 as? T { using(v) } })
    }

    func post(_ value: T) {
        nc.post(name: name, object: value)
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        _ = nc
            .addObserver {
                if $0 == to {
                    completion()
                }
            }

        (0...to).forEach {
            nc.post($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

Normal Type-safe
1 0.186 sec 0.178 sec
2 0.180 sec 0.174 sec
3 0.184 sec 0.173 sec
4 0.175 sec 0.177 sec
5 0.182 sec 0.174 sec

ラップした場合でも、特に大きな差はない結果となりました。

NotificationCenterのdefaultを利用した場合

それでは、NotificationCenterを利用範囲に合わせてインスタンス化したものではなく、NotificationCenter.defaultを利用した場合はどのような結果になるでしょうか。

final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter.default
    ...
}

計測結果

init() default
1 0.178 sec 0.512 sec
2 0.174 sec 0.531 sec
3 0.173 sec 0.526 sec
4 0.177 sec 0.518 sec
5 0.174 sec 0.525 sec

数値が約3倍になっています。
NotificationCenter.defaultは暗黙的に複数の監視登録がされているため、遅くなっていると考えられます。
NotificationCenterは遅いというイメージは、この場合に該当しているのかもしれません。

100個のNotification.NameをaddObserverしてから通知した場合

それでは、インスタンス化したNotificaionCenterに100個のNotification.Nameを監視登録して、本当に遅くなるのかを確認します。

NotificationCenter
func test_NotificationCenter_Int_performance() {

    let nc = NotificationCenter()
    (0..<100).forEach {
        nc.addObserver(forName: Notification.Name("performance-test-\($0)"),
                       object: nil,
                       queue: nil,
                       using: { _ in })
    }

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in

        let name = Notification.Name("performance-test")
        let to: Int = 100000

        _ = nc
            .addObserver(forName: name,
                         object: nil,
                         queue: nil,
                         using: {
                if let value = $0.object as? Int, value == to {
                    completion()
                }
            })

        (0..<to).forEach {
            nc.post(name: name, object: $0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

default init()
1 0.512 sec 0.550 sec
2 0.531 sec 0.556 sec
3 0.526 sec 0.556 sec
4 0.518 sec 0.561 sec
5 0.525 sec 0.555 sec

インスタンス化したNotificaionCenterでも遅くなりました。
NotificationCenter.defaultには暗黙的に複数の監視登録されていると考えても、あながち間違ってはいなさそうです。

別途10個の監視登録をした場合

次に、PublishSubject、PublishRelayとNotificationCenterに別途10個の監視登録をしてから通知します。

PublishSubject
func test_PublishSubject_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let subject = PublishSubject<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = subject.subscribe()
        }

        _ = subject
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            subject.onNext($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
PublishRelay
func test_PublishRelay_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let relay = PublishRelay<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = relay.subscribe()
        }

        _ = relay
            .subscribe(onNext: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            relay.accept($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}
NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(using: @escaping (T) -> Void) -> NSObjectProtocol {
        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: { if let v = $0 as? T { using(v) } })
    }

    func post(_ value: T) {
        nc.post(name: name, object: value)
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        (0..<10).forEach { _ in
            _ = nc.addObserver(using: { _ in })
        }

        _ = nc
            .addObserver {
                if $0 == to {
                    completion()
                }
            }

        (0...to).forEach {
            nc.post($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject PublishRelay NotificationCenter
1 1.243 sec 1.235 sec 0.885 sec
2 1.220 sec 1.225 sec 0.879 sec
3 1.238 sec 1.223 sec 0.894 sec
4 1.229 sec 1.259 sec 0.856 sec
5 1.262 sec 1.284 sec 0.891 sec

この場合でも、NotificationCenterの方が速い結果となりました。

NotificationCenterでもErrorを扱えるようにした場合

PublishSubjectはErrorも通知できるので、NotificationCenterをラップしたクラスでもErrorを通知できるようにしてみます。

NotificationCenter
final class TypeSafeNotificationCenter<T> {
    private let nc = NotificationCenter()
    private let name = Notification.Name("performance-test")

    func addObserver(onSuccess: ((T) -> Void)? = nil,
                     onError: ((Error) -> Void)? = nil) -> NSObjectProtocol {
        let using: (Notification) -> Void = { notification in
            guard let v = notification.object as? Result<T, Error> else {
                return
            }

            do {
                try onSuccess?(v.get())
            } catch {
                onError?(error)
            }
        }

        return nc.addObserver(forName: name,
                              object: nil,
                              queue: nil,
                              using: using)
    }

    func onSuccess(_ value: T) {
        nc.post(name: name, object: Result<T, Error>.success(value))
    }

    func onError(_ error: Error) {
        nc.post(name: name, object: Result<T, Error>.failure(error))
    }
}

func test_type_safe_NotificationCenter_Int_performance() {

    let measurePerformance: (@escaping () -> Void) -> Void = { completion in
        let nc = TypeSafeNotificationCenter<Int>()
        let to: Int = 100000

        _ = nc
            .addObserver(onSuccess: {
                if $0 == to {
                    completion()
                }
            })

        (0...to).forEach {
            nc.onSuccess($0)
        }
    }

    measureMetrics([.wallClockTime], automaticallyStartMeasuring: true) {
        measurePerformance {
            self.stopMeasuring()
        }
    }
}

計測結果

PublishSubject NotificationCenter
1 0.264 sec 0.220 sec
2 0.272 sec 0.221 sec
3 0.267 sec 0.222 sec
4 0.275 sec 0.222 sec
5 0.270 sec 0.224 sec

Errorを通知できるようにした場合でも、NotificationCenterの方が速い結果となりました。

Optimization Level -Osの場合

次は、carthageでOptimization Level -Osでビルドされたframeworkを利用してパフォーマンスチェックを行います。(TypeSafeNotificationCenterもframework化した成果物を利用します)

Optimization Level -O0のように、100000回通知し監視先で受け取った値が100000になった場合は終了する計測を行います。

計測結果

PublishSubject PublishRelay NotificationCenter
1 0.067 sec 0.069 sec 0.197 sec
2 0.068 sec 0.070 sec 0.201 sec
3 0.067 sec 0.069 sec 0.194 sec
4 0.068 sec 0.069 sec 0.196 sec
5 0.069 sec 0.070 sec 0.191 sec

NotificationCenterの計測を完了するまでに、PublishSubjectやPublishRelayと比べて約3倍の時間がかかっている結果となりました。

結論

  • NotificationCenter.defaultを利用した場合は暗黙的に監視登録されているものがあるため遅い
  • 利用範囲に合わせてインスタンス化をした場合はPublishSubjectPublishRelayと比べても速い 利用範囲に合わせてインスタンス化をした場合かつOptimization Level -O0の場合はPublishSubjectPublishRelayと比べても速い

  • 利用範囲に合わせてインスタンス化をしても、Optimization Level -Osの場合はPublishSubjectPublishRelayの方が約3倍速い

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