20190524のSwiftに関する記事は16件です。

【学習記録33】2019/5/24(金)

学習時間

6.0H

使用教材

本気ではじめるiPhoneアプリ作り Xcode 10.x対応 (Informatics&IDEA)

学習分野

chapter4
chapter5

コメント

学習開始からの期間:34日目
今日までの合計時間:101.0H

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

Swiftのプロトコルってなに?

この記事は何?

Swiftプログラミングにおけるプロトコルについて基本の部分を調べました。

実行環境

Xcode10.2.1
Swift5.x

プロトコル?

ゴースト・プロトコル

スパイ映画 Mission: Impossible シリーズにはこんなセリフが必ずある。
「例によって君もしくは君のメンバーが捕らえられ、或いは殺されても当局は一切関知しないから、そのつもりで。」
要は「任務を実行した結果、失敗しても知らんぷりするよ」ってこと。
もっと言うと...
「何かあっても知らんぷりするから、そのつもりで実行してね」ってこと。
スパイ活動って大変ですね。
この「知らんぷりする約束」を映画の中で「ゴースト・プロトコル」と呼んでいる。
バレても知らんぷりできるから、スパイ任務ができるんですね。

Swiftプログラミングでのプロトコル

「あるデータ型オブジェクトが何かを実行するときのルール」と言える。
逆に言うと...
「ルールに従っているから、それできるよ」となる。
あるデータ型がプロトコルに準拠していれば、何かが出来ることが保証されるんですね。

CustomStringConvertibleプロトコルに準拠する

こんな構造体があったとして

カスタムのFruit型
struct Fruit {
    var emoji: String
    var name:  String
}

applebanana オブジェクトを生成して...
コンソール出力する。

インスタンス化
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
  • ...

その型が何かしらプロトコルに準拠していることで動作が保証できるので、プログラムが安全になるという仕組みなんですね。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【未解決です】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の使い方にそぐわないかもしれませんが、もし同じ事象に遭遇して原因を突き止めた方がいらっしゃいましたら、ご教授いただけたら幸いです。

よろしくお願いします。

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

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なのでしょうか??

わかってしまえば簡単な内容だったので、勉強不足を痛感しております。。。
もし同じような理由でハマってる方の一助になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【Swift4】日付と時刻を指定したフォーマットで表示する

Swiftで日付と時刻を指定したフォーマットで表示するためには、DateFormatterクラスを使います。

日本のフォーマットで日付と時刻を表示するためには、DateFormatterLocale(identifier:)"ja_JP"を指定しておきます。表示文字列を取得するにはstring(from:)メソッドを使います。

標準のスタイルで表示する

Swiftでは日付、時刻それぞれに4種類のフォーマットが標準で用意されています。これらのフォーマットで表示するためには、DateFormatterクラスのdateStyleプロパティとtimeStyleプロパティに値を設定します。

DateTimeStyle.swift
        let 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.swift
        let 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

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

アクションシートを表示する

スクリーンショット 2019-05-24 14.28.42.png:point_right_tone1: スクリーンショット 2019-05-24 14.28.56.png

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)

    }


}

以上です。

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

アラートの表示

ezgif.com-optimize.gif

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)
    }
}

以上です。
https://github.com/matushinn/AlertController

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Swiftで指定ディレクトリ内のファイル一覧取得(メモ)

sample.swift
func getFiles(dirName: String) -> [String] {

        do {
            files = try fileManager.contentsOfDirectory(atPath: dirName)
        } catch {
            for path in files {
                print(path as NSString)
            }
            return files
        }

        return files
    }

以上(ㆁωㆁ*)

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【学習記録32】2019/5/23(木)

学習時間

5.0H

使用教材

本気ではじめるiPhoneアプリ作り Xcode 10.x対応 (Informatics&IDEA)

わかばちゃんと学ぶ Git使い方入門〈GitHub、Bitbucket、SourceTree〉

学習分野

chapter4

コメント

学習開始からの期間:33日目
今日までの合計時間:95.0H

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