- 投稿日:2019-05-24T23:50:53+09:00
pod installしてるのにライブラリ使えない!
プロジェクトの開き方を確認してみてください
https://stackoverflow.com/questions/32906165/xcode-not-detecting-pods-directory
You should open .xcworkspace, not .xcodeproj.
- 投稿日:2019-05-24T19:46:26+09:00
よく使うUIKitの基本
こちらの記事はTUBの企画の際に使用したものです。
UIKitとは
UIKitとは、iOSアプリを構築するために必要なクラスを提供してくれるライブラリです。
簡単に言うとiOSアプリの画面をレイアウトしたりするには絶対必要なものと思っていただいて大丈夫です。Xcodeで新規プロジェクトを立ち上げるとデフォルトで生成されているViewController.swiftを開くとこのようになっているかと思います。(最初にコメントアウトされている部分は気にせず削除してしまって問題ないです)
ViewController.swiftimport 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種類のスタイルがあります。
それと、TableViewにはセクションヘッダーとセクションフッターがあります。
TableViewを使用して実装した画面がどのようなものかイメージがつきやすいところで、iPhoneの設定画面を例に上げることができます。
CollectionViewの特徴
格子状に部品を配置することができます。
TableViewは縦方向のみの表示でしたが、CollectionViewでは縦方向に加えて横方向にも表示することができます。
AndroidだとGridViewやTableLayoutと言われるものがiOSのCollectionViewと同じ働きをするみたいです。CollectionViewにも同様、セクションヘッダーとセクションフッターがあります。
某フリマアプリの商品一覧とかはCollectionViewで表示しているかと思います。
また、iPhone標準のMusicアプリなんかも横方向にスライドできる部分はCollectionViewで実装されています。ScrollViewの特徴
画面に入りきらないような大きな画像を表示したり、電子書籍のように複数のページを送って表示させたい場合などに使用します。
LPのようなシンプルな画面を縦スクロールさせたい時にも使用します。ユーザが画面上をスクロールするとUIScrollViewが移動し、表示領域が変化します。
CollectionViewはAndroidも同じ名前みたいです。
間違っていたらすみません。まとめ
今回は良く使うUIKitについて、それぞれの使用用途や実際に使用して作られた画面の例を紹介しました。
この画面はTableViewをを使って実装した方が良いな。とか、こっちの画面はTableViewではなくCollectionViewを使って実装だな。みたいなことが、アプリのデザインを見た時に判断・イメージができるようになることがアプリ開発をする際にとても役に立ちます。日頃から色々なアプリを触って、どうやってこのUI実装しているんだろう、このアニメーションすごいなとか、そういった観点でアプリを使用してみると楽しいですし、けっこう勉強になります。
さいごに
弊社では毎週土曜日都内でエンジニアやデザイナーが集まるコミュニティーを開催しています!
一緒に勉強したい方や、興味がある方はぜひご参加ください!!
https://www.tokyo-uppers-boost.mht-code.com/
- 投稿日:2019-05-24T17:44:09+09:00
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://www.reddit.com/r/iOSProgramming/comments/b67fbc/xcode_102_release_builds_frozen/
- 投稿日:2019-05-24T16:06:04+09:00
Xcode10.1ではiOS12.2以降の検証が出来ない。
はじめに
こんにちは!
古いXcodeプロジェクトをBuildしようとしたらエラーが出たので、備忘として残しておきます。あんましAR関係ないです。
※記載間違い等あれば教えてください。
エラー内容について
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を使っていました。
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までしかサポートしていません。
その為、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への更新はそこまで苦ではありませんでした!
- 投稿日:2019-05-24T16:06:04+09:00
Xcode10.1でiOS12.2以降の検証が出来ない場合
はじめに
こんにちは!
古いXcodeプロジェクトをBuildしたらエラーが出たので、備忘として残しておきます。あんましAR関係ないです。
※記載間違い等あれば教えてください。
エラー内容について
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を使っていました。
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までしかサポートしていません。
その為、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さんありがとう御座います!)3.Xcode 10.1で頑張る。(iOS12.2用の更新を諦める。)
尚、既にAppStoreにリリースされているアプリは、今の所iOS12.2の端末でもダウンロード出来ました。
逆にXcodeのバージョンを下げたい方は、Downloads for Apple Develpoers (Xcode)からどうぞまとめ
いよいよSwift3を対応してくれなくなってきたので、大変ですが更新しなければいけないですね...!
尚、Swift4.xからSwift5への更新はそこまで苦ではありませんでした!
- 投稿日:2019-05-24T14:22:08+09:00
UIWebViewでよく使うデリゲートメソッド一覧(Swift)
UIWebViewでよく使うデリゲートメソッド一覧
webView(_:shouldStartLoadWith:navigationType:)
概要
ロードの開始前に呼ばれる
ロードするかどうか
false
を返すとロードしない用途
特定のURLの場合にロードしないようにする、などFooViewController.swiftimport 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.swiftimport UIKit final class FooViewController: UIViewController, UIWebViewDelegate { @IBOutlet private weak var indicatorView: UIActivityIndicatorView! func webViewDidFinishLoad(_ webView: UIWebView) { self.indicatorView.stopAnimating() } }webView(_:didFailLoadWithError:)
概要
ロード時にエラーが発生した場合に呼ばれる用途
エラーハンドリング
インジケータを消す場合、などFooViewController.swiftimport UIKit final class FooViewController: UIViewController, UIWebViewDelegate { @IBOutlet private weak var indicatorView: UIActivityIndicatorView! func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { self.indicatorView.stopAnimating() } }
- 投稿日:2019-05-24T13:57:10+09:00
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
- 投稿日:2019-05-24T10:15:16+09:00
NotificationCenterの通知は果たして本当に遅いのだろうか?
はじめに
NotificationCenterを利用した通知が、なんとなく遅いイメージはありませんか?
果たして本当に通知が遅いのかどうかを、PublishSubjectとPublishRelayと比較しながら見ていこうと思います。
ちなみにパフォーマンスチェックを行う環境は以下になります。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時にobject
とuserInfo
がnilであることとします。
また、通知の範囲は計測のメソッド内だけなので、NotificationCenter.default
は利用せずにインスタンス化したものを利用します。PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfunc 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]であることとします。PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfunc 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を利用した場合はどのような結果になるでしょうか。NotificationCenterfunc 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をラップして計測します。NotificationCenterfinal 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を監視登録して、本当に遅くなるのかを確認します。
NotificationCenterfunc 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個の監視登録をしてから通知します。
PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfinal 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を通知できるようにしてみます。
NotificationCenterfinal 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
を利用した場合は暗黙的に監視登録されているものがあるため遅い
利用範囲に合わせてインスタンス化をした場合は利用範囲に合わせてインスタンス化をした場合かつOptimization Level -O0の場合はPublishSubject
やPublishRelay
と比べても速いPublishSubject
やPublishRelay
と比べても速い利用範囲に合わせてインスタンス化をしても、Optimization Level -Osの場合は
PublishSubject
やPublishRelay
の方が約3倍速い
- 投稿日:2019-05-24T10:15:16+09:00
NotificationCenterの通知は果たして本当に遅いのだろうか?[修正版]
はじめに
NotificationCenterを利用した通知が、なんとなく遅いイメージはありませんか?
果たして本当に通知が遅いのかどうかを、PublishSubjectとPublishRelayと比較しながら見ていこうと思います。
ちなみにパフォーマンスチェックを行う環境は以下になります。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時にobject
とuserInfo
がnilであることとします。
また、通知の範囲は計測のメソッド内だけなので、NotificationCenter.default
は利用せずにインスタンス化したものを利用します。PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfunc 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]であることとします。PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfunc 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を利用した場合はどのような結果になるでしょうか。NotificationCenterfunc 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をラップして計測します。NotificationCenterfinal 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を監視登録して、本当に遅くなるのかを確認します。
NotificationCenterfunc 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個の監視登録をしてから通知します。
PublishSubjectfunc 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() } } }PublishRelayfunc 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() } } }NotificationCenterfinal 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を通知できるようにしてみます。
NotificationCenterfinal 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
を利用した場合は暗黙的に監視登録されているものがあるため遅い
利用範囲に合わせてインスタンス化をした場合は利用範囲に合わせてインスタンス化をした場合かつOptimization Level -O0の場合はPublishSubject
やPublishRelay
と比べても速いPublishSubject
やPublishRelay
と比べても速い利用範囲に合わせてインスタンス化をしても、Optimization Level -Osの場合は
PublishSubject
やPublishRelay
の方が約3倍速い