- 投稿日:2019-05-24T22:59:00+09:00
【学習記録33】2019/5/24(金)
学習時間
6.0H
使用教材
・本気ではじめるiPhoneアプリ作り Xcode 10.x対応 (Informatics&IDEA)
学習分野
chapter4
chapter5コメント
学習開始からの期間:34日目
今日までの合計時間:101.0H
- 投稿日:2019-05-24T20:14:12+09:00
Swiftのプロトコルってなに?
この記事は何?
Swiftプログラミングにおけるプロトコルについて基本の部分を調べました。
実行環境
Xcode10.2.1
Swift5.xプロトコル?
ゴースト・プロトコル
スパイ映画 Mission: Impossible シリーズにはこんなセリフが必ずある。
「例によって君もしくは君のメンバーが捕らえられ、或いは殺されても当局は一切関知しないから、そのつもりで。」
要は「任務を実行した結果、失敗しても知らんぷりするよ」ってこと。
もっと言うと...
「何かあっても知らんぷりするから、そのつもりで実行してね」ってこと。
スパイ活動って大変ですね。
この「知らんぷりする約束」を映画の中で「ゴースト・プロトコル」と呼んでいる。
バレても知らんぷりできるから、スパイ任務ができるんですね。Swiftプログラミングでのプロトコル
「あるデータ型オブジェクトが何かを実行するときのルール」と言える。
逆に言うと...
「ルールに従っているから、それできるよ」となる。
あるデータ型がプロトコルに準拠していれば、何かが出来ることが保証されるんですね。CustomStringConvertibleプロトコルに準拠する
こんな構造体があったとして
カスタムのFruit型struct Fruit { var emoji: String var name: String }
apple
とbanana
オブジェクトを生成して...
コンソール出力する。インスタンス化let apple = Fruit(emoji: "?", name: "Apple") let banana = Fruit(emoji: "?", name: "Banana") print(apple) print(banana)何が出力されるか。
?
かな?Apple
かな?
?
かな?Banana
かな?コンソール出力結果Fruit(emoji: "?", name: "Apple") Fruit(emoji: "?", name: "Banana")思ってたのと、違う。
テキトー感がスゴい。(イニシャライザのまんま)出力するルール
print()
関数を実行したら...
「description
プロパティを出力する」というルールがある。
これが CustomStringConvertible プロトコル。プロトコルに準拠させる
print()
関数でイイ感じに出力させるように、Fruit
型をCustomStringConvertible
に準拠させると...print()関数でemojiを出力させるstruct Fruit: CustomStringConvertible { var emoji: String var name: String var description: String { return "\(self.name) \(self.emoji)" } }
description
は計算プロパティを使って、値を返しています。
コンソール出力すると...CustomStringConvertibleなFruit型の出力結果Apple ? Banana ?いろんなプロトコル
Swiftには標準で、いくつものプロトコルが用意されています。
Equatable
Comparable
Codable
- ...
その型が何かしらプロトコルに準拠していることで動作が保証できるので、プログラムが安全になるという仕組みなんですね。
- 投稿日: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:24+09:00
【未解決です】CarthageとCocoaPodsの場合で挙動が変わる(RxCocoa)
結論としてRxCocoaの機能を誤使用したけど、Carthage経由で導入した場合はクラッシュせずにCocoaPods経由で導入した場合にクラッシュするという内容です。
■前提
RxSwift: 4.5.0 RxCocoa: 4.5.0■間違った使い方例
func viewDidLoad() { self.tableView.delegate = self.dataSource self.tableView.dataSource = self.dataSource viewModel.articles .asDriver() .drive( self.tableView.rx.items(dataSource: self.dataSource) ) .disposed(by: disposeBag) }この場合本来であれば以下のような理由でクラッシュするはずです。
This is a feature to warn you that there is already a delegate (or data source) set somewhere previously. The action you are trying to perform will clear that delegate (data source) and that means that some of your features that depend on that delegate (data source) being set will likely stop working.\nすでにどこかでデータソースまたはデリゲートが設定されていますよーって感じのエラーですが、まさにviewDidLoadの頭で明示的に両方設定してるのでダメですね笑
ただこのエラー、うちの環境ではRxSwift(RxCocoa)をCarthageで導入した場合は発生せずに(本来の使い方じゃないけど)意図した通りに動きますが、CocoaPodsで導入した場合にはしっかりとエラーが発生してクラッシュします。。。
またさらにわからないのはSchemeのBuildConfigurationでDebugビルドを行った時のみに発生し、Releaseやそれ以外(独自で設定した場合)の場合も発生しません。
※これはソースをちゃんと追ってないのですが、RxCocoaのなかでif DEBUGで分岐しているのかもしれません。Qiitaの使い方にそぐわないかもしれませんが、もし同じ事象に遭遇して原因を突き止めた方がいらっしゃいましたら、ご教授いただけたら幸いです。
よろしくお願いします。
- 投稿日:2019-05-24T16:06:24+09:00
CarthageとCocoaPodsで挙動が変わる(RxCocoa) ※5/27原因わかりました
結論としてRxCocoaの機能を誤使用したけど、Carthage経由で導入した場合はクラッシュせずにCocoaPods経由で導入した場合にクラッシュするという内容です。
■前提
RxSwift: 4.5.0 RxCocoa: 4.5.0■間違った使い方例
func viewDidLoad() { self.tableView.delegate = self.dataSource self.tableView.dataSource = self.dataSource viewModel.articles .asDriver() .drive( self.tableView.rx.items(dataSource: self.dataSource) ) .disposed(by: disposeBag) }この場合本来であれば以下のような理由でクラッシュするはずです。
This is a feature to warn you that there is already a delegate (or data source) set somewhere previously. The action you are trying to perform will clear that delegate (data source) and that means that some of your features that depend on that delegate (data source) being set will likely stop working.\nすでにどこかでデータソースまたはデリゲートが設定されていますよーって感じのエラーですが、まさにviewDidLoadの頭で明示的に両方設定してるのでダメですね笑
ただこのエラー、うちの環境ではRxSwift(RxCocoa)をCarthageで導入した場合は発生せずに(本来の使い方じゃないけど)意図した通りに動きますが、CocoaPodsで導入した場合にはしっかりとエラーが発生してクラッシュします。。。
またさらにわからないのはSchemeのBuildConfigurationでDebugビルドを行った時のみに発生し、Releaseやそれ以外(独自で設定した場合)の場合も発生しません。
※これはソースをちゃんと追ってないのですが、RxCocoaのなかでif DEBUGで分岐しているのかもしれません。Qiitaの使い方にそぐわないかもしれませんが、もし同じ事象に遭遇して原因を突き止めた方がいらっしゃいましたら、ご教授いただけたら幸いです。
よろしくお願いします。
====5/27追記======
まず実際にクラッシュする箇所(エラー)ですが、DelegateProxyType.swiftのinstallForwardDelegateメソッド内にある
assert(proxy._forwardToDelegate() === nil・・・
に引っかかることで、発生してます。
で、そもそもXcodeのデフォルト設定ではreleaseビルドはswift optimazation levelが-O
で設定されており、この場合assert
は評価されずにスキップされます。
※Debugビルドのデフォルトは-Onone
であり、この場合はassert
も評価され、引っかかったらRuntimeError
になります。
これがSchemeのBuildConfigurationでDebugビルドを行った時のみに発生し、Releaseやそれ以外(独自で設定した場合)の場合も発生しません。
の原因(理由)でした。またうちでは以下のコマンドでCarthage経由で各ライブラリをインストールしてます。
carthage update --platform iOS --no-use-binaries
carthage update
コマンドには--configuration
というオプションがあり、ここでDebug
と明示的に指定した場合にDebugビルドとなり、上記の理由によりCocoaPodsで導入した時に発生したエラーが再現できました。
※--configuration
を付与しなかった場合のデフォルトはReleaseなのでしょうか??わかってしまえば簡単な内容だったので、勉強不足を痛感しております。。。
もし同じような理由でハマってる方の一助になれば幸いです。
- 投稿日: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-24T15:16:43+09:00
【Swift4】日付と時刻を指定したフォーマットで表示する
Swiftで日付と時刻を指定したフォーマットで表示するためには、
DateFormatter
クラスを使います。日本のフォーマットで日付と時刻を表示するためには、
DateFormatter
にLocale(identifier:)
で"ja_JP"
を指定しておきます。表示文字列を取得するにはstring(from:)
メソッドを使います。標準のスタイルで表示する
Swiftでは日付、時刻それぞれに4種類のフォーマットが標準で用意されています。これらのフォーマットで表示するためには、
DateFormatter
クラスのdateStyle
プロパティとtimeStyle
プロパティに値を設定します。DateTimeStyle.swiftlet formatter = DateFormatter(); formatter.locale = Locale(identifier: "ja_JP") formatter.dateStyle = .medium formatter.timeStyle = .medium let now = Date(); print(formatter.string(from: now)) // 2019/05/24 13:52:48なお、
dateStyle
に.none
を指定すると時刻のみを、timeStyle
に.none
を指定すると日付のみを表示します。
dateStyle 表示結果 timeStyle 表示結果 .full 2019年5月24日 金曜日 .full 13時52分48秒 日本標準時 .long 2019年5月24日 .long 13:52:48 JST .medium 2019/05/24 .medium 13:52:48 .short 2019/05/24 .short 13:52 .none (表示しない) .none (表示しない) フォーマットを指定する
標準のスタイル以外で表示する場合は、
DateFormat
クラスのdateFormat
プロパティにフォーマットを文字列で指定します。元号を表示させたい場合は、あらかじめ
calendar
プロパティにCalendar(identifier: .japanese)
を設定しておきます。DateTimeFormat.swiftlet formatter = DateFormatter(); formatter.locale = Locale(identifier: "ja_JP") formatter.calendar = Calendar(identifier: .japanese) formatter.dateFormat = "Gy年M月d日EEEE ah時mm分ss秒" let now = Date(); print(formatter.string(from: now)) // 平成31年5月24日金曜日 午後2時33分09秒(注)投稿時点では「令和」には対応していないようでした。
フォーマットに指定する文字列は、以下のようなものがあります。
単位等 フォーマット文字列 説明 表示例 元号 G 西暦の場合は「西暦」と表示。和暦の場合は元号を表示。 平成 年 y, yyyy 年を表示。西暦と和暦によって表示する数は異なる。yyyyとすると足りない桁を0で埋める。 2019 月 M, MM 月を表示。MMとすると1〜9月の場合は、前に0を補う。 05 日 d, dd 日を表示。ddとすると1〜9日の場合は、前に0を補う。 24 曜日 E, EEEE 曜日を表示。Eなら漢字1文字、EEEEなら「○曜日」と表示する。 金曜日 午前/午後 a 「午前」または「午後」と表示。 午後 時間 h, hh, H, HH hとhhは12時間制で、HとHHは24時間制で時間を表示。hh, HHとすると0〜9時の場合は、前に0を補う。 14 分 m, mm 分を表示。mmとすると0〜9分の場合は、前に0を補う。 33 秒 s, ss 秒を表示。ssとすると0〜9秒の場合は、前に0を補う。 09 参考
【Swift】Dateの王道 【日付】 - https://qiita.com/rinov/items/bff12e9ea1251e895306
和暦に関するメモ - https://qiita.com/usagimaru/items/16263cbe32922cf57003
Date Field Symbol Table - http://www.unicode.org/reports/tr35/tr35-25.html#Date_Field_Symbol_Table
DateFormatter - https://developer.apple.com/documentation/foundation/dateformatter
- 投稿日:2019-05-24T14:31:36+09:00
アクションシートを表示する
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func tapAlert(_ sender: Any) { //アクションシートを作る let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .actionSheet) //ボタン1 alert.addAction(UIAlertAction(title: "追加する", style: .default, handler: nil)) //ボタン2 alert.addAction(UIAlertAction(title: "さらに追加する", style: .default, handler: nil)) //消去する alert.addAction(UIAlertAction(title: "消去する", style: .destructive, handler: nil)) //ボタン3 alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)) //アクションシートを表示する self.present(alert, animated: true, completion: nil) } }以上です。
- 投稿日:2019-05-24T14:26:00+09:00
アラートの表示
iOSアプリを作成していく中で必須のアラートの作り方について説明していきます。
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func tapAlert(_ sender: Any) { //アラートを作る let alert = UIAlertController(title: "タイトル", message: "メッセージ", preferredStyle: .alert) //ボタン1 alert.addAction(UIAlertAction(title: "追加する", style: .default, handler: nil)) //ボタン2 alert.addAction(UIAlertAction(title: "さらに追加する", style: .default, handler: nil)) //消去する alert.addAction(UIAlertAction(title: "消去する", style: .destructive, handler: nil)) //キャンセルボタン alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)) //アラートを表示する self.present(alert, animated: true, completion: nil) } }
- 投稿日: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-24T11:33:59+09:00
Swiftで指定ディレクトリ内のファイル一覧取得(メモ)
sample.swiftfunc getFiles(dirName: String) -> [String] { do { files = try fileManager.contentsOfDirectory(atPath: dirName) } catch { for path in files { print(path as NSString) } return files } return files }以上(ㆁωㆁ*)
- 投稿日: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倍速い
- 投稿日:2019-05-24T00:04:21+09:00
【学習記録32】2019/5/23(木)
学習時間
5.0H
使用教材
・本気ではじめるiPhoneアプリ作り Xcode 10.x対応 (Informatics&IDEA)
・わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉
学習分野
chapter4
コメント
学習開始からの期間:33日目
今日までの合計時間:95.0H