- 投稿日:2019-07-27T22:03:53+09:00
【iOS】iOS開発初心者の個人的に参考になったswift記事まとめ
趣旨
swift関連でお世話になった、参考になった記事のまとめ。
個人的に詰まった時にために参考にするためにまとめます。
勉強初めて1月程度の初心者なのでswift関連のおすすめ記事などあれば教えていただけると助かります。
多くなってきたら分類します・・・たぶん
随時更新していくはず記事一覧
デリゲートの概要
デリゲートってそもそも何ってなった際に以下の記事を読んだ。
この記事を理解して初心者向けの記事読んだほうが個人的には良いと思った。
参考先iOSの設定画面への遷移方法
アプリ側からiOS本体側への設定画面への遷移方法。
参考先Slider
地図
基本的なとこ
参考先地図でのピンのたてかた
swift5では地味に違ってる気がする。
あとピンを複数立てる場合は配列で渡すようになってるみたい
参考先
- 投稿日:2019-07-27T12:24:36+09:00
画像の読み込み時に「Thread 1: EXC_BAD_INSTRUCTION 」エラーが出る
UIImageを読み込む際、
Buildは成功するが、画面描画の際に、下記エラーが出ていた。Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) image literalUIImageViewで直接読み込もうとした際も下記エラーが出ていた。
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)解決法:
Assetsフォルダ内のContents.jsonで
「properties」
を丸ごと削除したら描画した。
- 投稿日:2019-07-27T12:04:39+09:00
FlutterViewControllerからiOSのUIViewControllerをpresentした際、下のFlutterがイベントを拾ってしまう件
FlutterでiOSのネイティブの画面をモーダル表示したい時、FlutterViewControllerからiOSのUIViewControllerをpresent()していたのだが、この際ハンドラが定義されてない部分タップすると後ろに隠れてるFlutter側のイベントが発火する挙動に困ってた。↓こういうの
問題の再現
- サンプルコードを作成して確認
- https://github.com/maeharin/flutter_ios_present_sample
- v1.7.8+hotfix.3(2019/7時点最新版のstable)とv1.5.4-hotfix.2で再現すること確認
原因調査
FlutterViewControllerの実装(engineのv1.5.4-hotfixesブランチ)を見ると、以下のようにtouchesBeganメソッド等の4メソッドをoverrideしており、ここからFlutter側にイベントを伝播させていることがわかる
https://github.com/flutter/engine/blob/v1.5.4-hotfixes/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm#L649-L663touchesBeganがどう呼び出されるかについては以下のappleのドキュメントを参照。iOS側のtouchイベントはResponder Chainを駆け上っていくようになっている
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events今回の挙動に関係ある部分をまとめると
- タッチイベントなら、タッチされたビューがファーストレスポンダとなる
- 各ビューのnextプロパティにresponder chain上の次のレスポンダが入っているので、それで確認できる
- UIViewオブジェクト
- ビューがView Controllerのルートビューの場合、次のレスポンダはView Controller
- それ以外の場合、次のレスポンダはビューのスーパービュー
- UIViewController オブジェクト
- View Controllerのビューがウィンドウのルートビューの場合、次のレスポンダはウィンドウオブジェクト
- View Controllerが別のView Controllerによって表示されている場合、次のレスポンダは表示側のView Controller
つまり、今回のケースは以下のようになっていた
- イベントハンドラがないUIの部分をタップする
- タップイベントがiOSのResponder Chainを駆け上っていき、FlutterViewControllerのtouchesBegan()まで伝播
- Flutter側のイベントハンドラが呼び出される
解決策
FlutterViewControllerのtouchesBegan()等が呼び出されないようにすればよい。以下2つの方法が考えられる
1. FlutterViewControllerからpresent()したUIViewControllerのtouchesBegan等の4メソッドをoverrideする
FlutterViewControllerからpresentされるUIViewControllerに以下を追記する。これにより、FlutterViewControllerにアンハンドルなイベントを伝播しないようにする
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {} override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {} override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {} override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}参考:UIKitのヘッダファイルの該当箇所
// Generally, all responders which do custom touch handling should override all four of these methods. // Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each // touch it is handling (those touches it received in touchesBegan:withEvent:). // *** You must handle cancelled touches to ensure correct behavior in your application. Failure to // do so is very likely to lead to incorrect behavior or crashes. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;2. FlutterViewControllerからpresent()するのをやめる
iOS側のrootViewControllerをFlutterViewControllerではなくNavigationControllerにするなどして、FlutterViewControllerからUIViewControllerを直接present()しないようにする。
rootViewControllerを変更する方法はこちらの記事が参考になる
https://qiita.com/najeira/items/fa52ca4a9f544ae08300
- 投稿日:2019-07-27T11:20:59+09:00
【Swift】Swift5.1の新機能 Collection Diffing入門
Collection Diffingとは?
Swift5.1で導入される新しい機能です。
Forumスレッド一覧
https://forums.swift.org/search?q=SE-0240Original Pitch
https://forums.swift.org/t/se-0240-ordered-collection-diffing/19514/34Proposal
https://github.com/apple/swift-evolution/blob/master/proposals/0240-ordered-collection-diffing.mdReview
https://forums.swift.org/t/se-0240-ordered-collection-diffing/19514/42
https://forums.swift.org/t/amendment-se-0240-ordered-collection-diffing/26084これは
difference(from:)
やdifference(from:by:)
を呼び出すことで
2つのコレクションの差分を計算して
何がどう変わっているのかを教えてくれます。
difference(from:)
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L188またCollection Diffingは全てSwiftで書かれています。
今回はCollection Diffingがどういうものなのか
とても参考になる記事がありましたので
そちらを参考に実際の実装などを見ながら
理解をしていきたいと思います。参考記事
https://www.fivestars.blog/code/swift-5-1-collection-diffing.html最初に使用される要素を見ていき
その後にメソッドを見ていきたいと思います。CollectionDifference
Genericな
Collection
で
2つの整列されているコレクションの
差分(どの要素が削除され、どの要素が挿入されているのか)を
を保持しています。A collection of insertions and removals that describe the difference between two ordered collection states
CollectionDifference
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L16ChangeElement
比較している要素の型でこれには何の制約も付いていません。
.insert
と.remove
という2つのケースを持った
CollectionDifference.Change
というenum
になっており
個々の要素の変化を表します。(詳細は後ほど記載します)
CollectionDifference.Change
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L19CollectionDifferenceの特徴
CollectionDifference
には下記のような特徴があります。CollectionDifferenceが返す値の順番が決まっている
まず最初に削除された要素のコレクション
その次に追加された要素のコレクションという順番になっています。これは内部の
init
で保証されており
必ず適切な順番になって返ってきます。CollectionDifferenceの要素はユニークである
init
のコメントに下記のような記載があります。The collection of changes passed as `changes` must meet these requirements: - All insertion offsets are unique - All removal offsets are unique - All associations between insertions and removals are symmetricつまり
削除や挿入された要素のオフセットは全てユニークでなければなりません。
また削除と挿入のこ関係は対称でなければなりません。同じインデックスに対して削除の処理をしていたりした場合などはできません。
挿入に関しても同じです。移動した要素は削除と挿入に対になる要素が一つ存在している。
どちらか片方だけというようなものは存在できません。これは内部でバリデーションをしており
init
で不適切なコレクションが渡ってきた場合はnilが返却されます。
_validateChanges
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L89insertionsとremovalsプロパティ
これらの2つのプロパティを使用することで
元のコレクションの要素に直接アクセスすることができます。insertions
挿入された要素がオフセットが小さいものから順番に並んでいます。
removals
削除された要素がオフセットが小さいものから順番に並んでいます。
CollectionDifference.Change
2つのコレクションを比較した時に
変化としては下記の3つのパターンがあります。
- 新しい要素が追加された
- 要素が削除された
- 移動して位置が変わった(元の位置から削除され、新しい位置に挿入された)
そのため
CollectionDifference.Change
は
insertとremoveの2つのenumで表現できます。
CollectionDifference.Change
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L19それぞれのcaseは
offset
element
associatedWith
という
3つのassociated valuesを持っています。
offset
挿入または削除された位置です。
削除された場合
let oldArray = ["a", "b", "c", "d"] let newArray = ["a", "b", "d"] // "c"という要素がindex 2から削除されました let difference = newArray.difference(from: oldArray) // differenceは1つの要素を含んだ配列です。 // これは`.remove`で`offset`は2となります。挿入された場合
let oldArray = ["a", "b", "c", "d"] let newArray = ["a", "b", "c", "d", "e"] // "e"という要素がindex 4に挿入されました let difference = newArray.difference(from: oldArray) // differenceは1つの要素を含んだ配列です。 // これは`.insert`で`offset`は4となります。element
実際の要素です。
これがCollectionDifferenceがジェネリックになっている要因です。let oldArray = ["a", "b", "c", "d"]
let newArray = ["a", "b", "d", "e"]// "c"という要素をindex 2から削除
// "e"という要素をindex 3に追加let difference = newArray.difference(from: oldArray)
// differenceは2つの要素の配列
// 最初の要素は.remove
でoffset 2、element "c"
// 次の要素は.insert
でoffset 3、element "e"associatedWith
要素を移動した時にこの値が入ります。
insert
の場合は移動後のオフセット
remove
の場合は移動前のオフセット
が入ります。これは計算に時間がかかるため
デフォルトでは全てnilとなっており
inferringMoves()
を呼び出す必要があります。
inferringMoves
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/CollectionDifference.swift#L323let oldArray = ["a", "b", "d", "e", "c"] let newArray = ["a", "b", "c", "d", "e"] // "c"をindex 4から2へ移動 // - index 4から削除 // - index 2へ追加 let difference = newArray.difference(from: oldArray) // differenceは2つの要素の配列 // - 最初の要素は`.remove` offset 4 associatedWith nil // - 次の要素は`.insert` offset 2 associatedWith nil let differenceWithMoves = difference.inferringMoves() // differenceWithMovesは2つの要素の配列 // - 最初の要素は`.remove` offset 4 associatedWith 2 // - 次の要素は`.insert` offset 2 associatedWith 4InferringMoves
CollectionDifference
インスタンスをスキャンして
削除と挿入のオフセットがマッチした(移動した)要素をコレクションに格納していきます。
もし存在しなければ元のコレクションと同じ要素が含まれたコレクションを返します。CollectionDifferenceの要素の並び順
init
の中を見てみるとわかるのですが
要素の順番は削除が先 挿入があと
になります。
挿入された要素は
insertions
配列に入り
削除された要素はremovals
配列に入ります。しかし
subscript
を見てみると
また違った順番で要素を参照しています。
removals
は大きいオフセットから小さいオフセットへ
insertions
は小さいオフセットから大きいオフセットに向かっています。これは順番に取得した要素を元のコレクション(変化前のコレクション)に適用していくと
新しいコレクションになるようになっており
変化を再現することができます。let oldArray = ["a", "b", "c", "d"] let newArray = ["x", "a", "e", "c"] var anotherArray = oldArray let difference = newArray.difference(from: oldArray) // differenceは4つの要素の配列 // 1. `.remove` at offset 3 // 2. `.remove` at offset 1 // 3. `.insert` at offset 0 // 4. `.insert` at offset 2 for change in difference { switch change { case let .remove(offset, _, _): anotherArray.remove(at: offset) case let .insert(offset, newElement, _): anotherArray.insert(newElement, at: offset) } } // この時点で`anotherArray`は`newArray`と等しくなる遷移をたどると下記のようになります。
["a", "b", "c", "d"] // 初期状態 ["a", "b", "c"] // "d"をindex 3から削除 ["a", "c"] // "b"をindex 1から削除 ["x", "a", "c"] // "x"をindex 0へ挿入 ["x", "a", "e", "c"] // "e"をindex 2へ挿入applying
上記では
difference(from:)
で取得した要素をコレクションに順番に適用してきましたが
applying(_:)
メソッドを使用することで変化を別のコレクションに適用できます。let oldArray = ["a", "b", "c", "d"] let newArray = ["x", "a", "e", "c"] var anotherArray = oldArray let difference = newArray.difference(from: oldArray) // ここでdifferenceをapplyingしている anotherArray = anotherArray.applying(difference)! // この時点で`anotherArray`は`newArray`と等しくなるこのメソッドは制約に適合している限りはどんなコレクションにも適用できます。
戻り値としてdifference
を適用したOptional
なコレクションを返します。なぜ
Optional
なのかは
difference
の要素のオフセットが
適用対称のコレクションのオフセットを超えた時に
クラッシュを避けてnilを返すようにしています。difference(from:)
これまでも出てきましたCollection Diffingの中心となるメソッドです。
extension BidirectionalCollection where Element : Equatable { public func difference<C: BidirectionalCollection>( from other: C ) -> CollectionDifference<Element> where C.Element == Self.Element { return difference(from: other, by: ==) } }
difference(from:)
はdifference(from: by:)
を呼び出しているだけで
要素がEquatable
の時に使用できます。difference(from: by:)
byに等しくなる条件を指定することで
Equatable
に適合していない要素にも
differenceを取得することができるようになります。public func difference<C: BidirectionalCollection>( from other: C, by areEquivalent: (Element, C.Element) -> Bool ) -> CollectionDifference<Element> where C.Element == Self.Element
difference(from:by:)
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L134シンタックスを見てみると2つのコレクションは
BidirectionalCollection
に適合している必要があります。つまり前から後からもコレクションの要素へ行ったり来たり参照できることが必要になります。
また2つのコレクションはassociated typeの型は同じである必要があります。byの条件によってdifferenceで取得できる要素が変わってきます。
例えば全てに対してfalseを返すようにすると
let oldArray = ["a", "b", "c"] let newArray = ["a", "b", "c"] // 同じ要素 let difference = newArray.difference(from: oldArray, by: { _, _ in false }) // `difference`は6つの要素を持つ配列: // - removalsに3要素 // - insertionsに3要素これは全ての要素の比較がfalseになるので
要素を全部入れ替えたとみなされているからです。difference(from: by:) の戻り値
Optional*ではない*CollectionDifferenceを返します。
上記でCollectionDifferenceのinitは
nilを返す可能性があることを紹介しましたが
なぜ違うのか?これは内部でnilにならないinitを保持しており
そちらを使用しているからです。これは妥当だと証明されたコレクションを使用しているので
Swift的にもnon-optionalで問題ありません。difference(from: by:)の内部
まず
_CountingIndexCollection
のコレクションを2つ作成し
これを_CollectionChanges
への変換しなおしています。let source = _CountingIndexCollection(other) let target = _CountingIndexCollection(self) let result = _CollectionChanges(from: source, to: target, by: areEquivalent)
_CountingIndexCollection
は実際のコレクションを包んで
開始のindexからのoffsetへアクセスしやすくします。let originalCollection = ["a", "b", "c", "d"] let offsetCollection = _CountingIndexCollection(originalCollection) if let index = offsetCollection.index(of: "d") { print(index.offset) // 3 print(offsetCollection[index]) // d }
_CollectionChanges
はCollectionDifference.Changeインスタンスの配列です。その後取得したChangeインスタンスの配列から
CollectionDifference
のインスタンスを生成して返却しています。このメソッドないでは実際の差分計算はしておらず
_CollectionChanges
の初期化の中で行っています。_CollectionChanges
2つのコレクションの要素を辿っていき、下記のようなことを行っています。
2つのコレクションの最長共通部分列を取得
https://ja.wikipedia.org/wiki/%E6%9C%80%E9%95%B7%E5%85%B1%E9%80%9A%E9%83%A8%E5%88%86%E5%88%97%E5%95%8F%E9%A1%8C削除と挿入の最短経路を探して最も少ない手順で
source
コレクションからtarget
コレクションへ遷移する方法を見つけます。
https://ja.wikipedia.org/wiki/%E6%9C%80%E7%9F%AD%E7%B5%8C%E8%B7%AF%E5%95%8F%E9%A1%8Cこの2つは表裏一体のような関係となっており
片方が見つかるともう片方も見つかるようになっています。挿入や削除は非常にコストの高い操作であるため
このプロセスで最も効率差分更新するための方法を導き出すことが
Collection Diffingの根幹になります。
_CollectionChanges
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L247ある状態からある状態への遷移方法は複数見つかる中で
もっとも効率の良いものを_CollectionChangesは導き出すことを保証します。Endpoint
_CollectionChangesの定義の冒頭にあるtypealiasです。
typealias Endpoint = (x: SourceIndex, y: TargetIndex)これは
source
コレクションとtarget
コレクションの関係を表します。
例えばsource
コレクションの最初の要素とtarget
コレクションの2番目の要素を関連づけたい場合
Endpoint
は(1,2)と定義します。pathStorage
ここからは_CollectionChangesが
具体的にどういう計算をしているのかを見ていきます。下記のような2次元のチャートがあります。
x軸がsourceのコレクションを
y軸はtargetのコレクションを表します。| • | X | A | B | C | D | • | | | | | | | X | | | | | | | Y | | | | | | | C | | | | | | | D | | | | | | |左上を(0,0)として一番右下の(lastIndexOfTarget, lastIndexOfSource)を目指します。
下記のようなイメージです。
```
| • | X | A | B | C | D |
• | S | | | | | |
X | | | | | | | // S: 開始
Y | | | | | | | // T: 目的地
C | | | | | | |
D | | | | | | T |// 開始位置 S: (0,0)
// 目的位置 T: (4,5)
```ルール
- 左から右にしか移動できない
- 上から下にしか移動できない
- 左右と上下は同じ距離だけ同時に動かして良い(左に1、下に1など)
- 左右と上下を同時に動かして良いのはxとyが同じ時だけ((A,A)の時など)この中でもっとも少ない数で対角線の位置に移動する方法を探します。
これが最長共通部分列問題です。
_CollectionChanges
に当てはめると
- 垂直への移動が挿入
- 平行への移動が削除
になります。
結果は下記になります。
| • | X | A | B | C | D | • | S | | | | | | X | | x | x | x | | | // S: 開始 Y | | | | x | | | // T: 目的地 C | | | | | x | | D | | | | | | T | // 経路: (0,0) → (1,1) → (1,2) → (1,3) → (2,3) → (3,4) → (4,5)これを_CollectionChangesで考えると
- (1,1) → (1,2)、(1,2) → (1,3)は2つの削除
- (1,3) → (2,3)は1つの挿入
になります。
この一つ一つの地点が
Endpoint
であり
pathはEndpoint
の配列になります。これが
pathStorage
プロパティになります。pathStartIndex
_CollectionChanges
が持つもう一つのプロパティで
差分計算のパスを探す開始位置を表します。これは内部最適化のために使われ
例えばAEOSとAEFTという文字列があった場合
AEは共通なのでこの分の計算は無駄になります。
そこでpathStartIndex
を見てこの無駄をスキップします。
pathStartIndex
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L279enum Element
internalな要素で上記の計算で見つけたパスを
CollectionDifference.Change
に変換するときに使用します。enum Element { case removed(Range<SourceIndex>) case inserted(Range<TargetIndex>) case matched(Range<SourceIndex>, Range<TargetIndex>) }
_CollectionChanges.Element
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L300※ なぜ
Range
なのか?上記の問題の例では一つ一つ
Endpoint
を分けて考えましたが
(1,1) → (1,2) → (1,3)は連続した平行移動で
(1,1) → (1,3)と一緒にできます。
これを表現するためにRange
となっています。
difference
メソッドでは
このElement
を一つ一つ取り出してCollectionDifference.Change
へと変換していきます。_CollectionChangesの内部
まず中心となる
init
の形です。fileprivate init<Source: BidirectionalCollection, Target: BidirectionalCollection>( from source: Source, to target: Target, by areEquivalent: (Source.Element, Target.Element) -> Bool ) where Source.Element == Target.Element, Source.Index == SourceIndex, Target.Index == TargetIndex { self.init() formChanges(from: source, to: target, by: areEquivalent) }空の
init
は下記になります。private init() { self.pathStorage = [] self.pathStartIndex = 0 }
formChanges
メソッドでpathStorage
にEndpoint
を追加していきます。下記ような処理を行っています。
最初のチェック
最初のチェックとして下記の2つを行います。
- 両方のコレクションが空の場合
すぐに処理を中断し、
diffrence
の結果も空になります。
- 共通の接頭辞を見つけ、かつ1つのコレクションがもう1つのコレクションの全要素を網羅している場合
上記の最短経路問題で考えると
右か下にぶつかるまで斜めに移動することができ
その後平行か垂直移動して対角線にいきます。いくつかの例を見てイメージしてみます。
// source = ASDFO // target = ASD // targetはsourceの接頭辞になっています: | • | A | S | D | F | O | • | S | | | | | | A | | x | | | | | // S: 開始 S | | | x | | | | // T: 目的地 D | | | | x | x | T | ↑ ここで端にぶつかって平行移動している // 最終経路: (0,0) → (3,3) → (3,5) // - 3つが一致 (0,0) → (3,3) // - 2つ削除 (FとO), (3,3) → (3,5) // source = ASD // target = ASDFO // sourceがtargetの接頭辞になっています: | • | A | S | D | • | S | | | | // S: 開始 A | | x | | | // T: 目的地 S | | | x | | D | | | | x | ← ここで端にぶつかって垂直移動している F | | | | x | O | | | | T | // 最終経路: (0,0) → (3,3) → (5,3) // - 3つが一致 (0,0) → (3,3) // - 2つ挿入 (FとO), (3,3) → (5,3) // source = ASD // target = ASD // 両方同じ要素 | • | A | S | D | • | S | | | | // S: 開始 A | | x | | | // T: 目的地 S | | | x | | D | | | | T | ← 端にぶつかる // 最終経路: (0,0) → (3,3) // - 3つが一致 (0,0) → (3,3)上記では2か3つの要素を持ったpathStorageができます。
formChangesCore
上記の条件に合わない場合は
下記のような状態になっています。
- 2つのコレクションの要素が等しくない(byで示した条件ですべてtrueにならない)
- 2つのコレクションが空ではない
- 共通の接頭辞がある可能性がある
この際には
formChangesCore
メソッドを呼び出します。
formChangesCore
https://github.com/apple/swift/blob/a0421124f0de37d696e503c17c05afba19f74cf2/stdlib/public/core/Diffing.swift#L411引数には2つのコレクションと等しいとみなすための条件に加えて
最初のロジックで見つけた共通の接頭辞のindexです。
これを渡すことで不要な計算をスキップします。このメソッド内では
Greedy LCS/SES Algorithmを実装しています。
http://www.xmailserver.org/diff2.pdf詳細は割愛しますが簡単に何をしているのかを示すと
- まずはもっとも時間がかかるように斜め移動なしでのDまでの移動手順を見つけます。
- この時点で目的地に到着したら計算を終了します。
- 到着していない場合、Dの値を一つずつ増やして再び最初から計算を開始します。
この際により最適化するために前のパスをロジックに従った形で保存しておくことで
できる限り計算の無駄をなくすようにされています。ご興味のある方は冒頭の参照記事をご覧ください。
まとめ
Collection Diffingについて見てきました。
使う側としては非常にシンプルに使えますが内部の複雑なアルゴリズムに支えられ
安全に効率良く差分計算を実現されていることもわかりました。参照記事の筆者も結論で述べていますが
これはCollectionView
やTableView
では使用しない方が良く
代わりにDiffable Data Sourcesというより最適化されたAPIが
iOS13で用意されています。詳細は下記にまとめましたのでもしよろしければご参照ください
https://qiita.com/shiz/items/a6032543a237bf2e1d19Swiftはまだまだ発展途上で
たくさんのプロポーサルが出てきています。これからもどんな新しい機能が増えていくのか楽しみですね
もし間違いなどございましたら教えていただけると嬉しいです
- 投稿日:2019-07-27T01:00:46+09:00
WebAuthnでBLEデバイスを使う方法
iOSデバイスを外部認証機器と使うためにはCTAPのBLEの実装が必要ですが、どこのWebAuthnのデモサイトにアクセスしてもBluetooth機器の認証を目にすることができませんでした。
調べてみたところ、Bluetoothの認証が出るか否かはWebAuthnのサーバ側の設定ではなく、アクセス元のブラウザがWeb Bluetooth APIに対応しているか否かで決まるそうです。
Web Bluetooth APIとは
- WebブラウザがBluetoothを制御するためのAPI
- ChromeやOperaでは利用できるが、FirefoxやIEでは対応は微妙らしい
ChromeでWeb Bluetooth APIを有効にする方法
Web Bluetooth APIは隠し機能のため、通常の設定メニューには出てきません。
アドレスバーに
chrome://flags/
を入力し、「Web Authentication API BLE support」をenableにします。設定を行った上で、Webauthn.ioにアクセスすると、次のようにBluetoothセキュリティキーが選択肢に現れます。
注意点
iOSのBluetoothアプリを作成し、chromeのWebauthnと通信が行われるかを確認しましたが、OSのバージョンやBluetoothのバージョンによっては通信ができませんでした。
クライアント側
OS
- Windows10 → NG(ChromeでWeb BluetoothをONにしても選択詞が出てこない)
- macOS 10.14.3 → OK
- macOS 10.15 beta4 → NG(選択詞は表示されるが、PeripheralであるiOSデバイスに通信が飛ばない)
BlueTooth IF
- 4.0 → NG(PeripheralであるiOSデバイスに通信が飛ばない)
- 4.2 → OK
認証機器側
OS
- iOS 12.4 → OK
- iOS 13 beta 4 → NG(Central側からの通信が受信されない)
今のところ、アプリの実装が試せそうなのはBlueTooth4.2を搭載のmacOS 10.14とiOS12端末の組み合わせのみのようです。