- 投稿日:2020-11-30T23:48:46+09:00
ターミナルでCarthageの.sh実行のエラー:Build Failed Task failed with exit code 65
プロジェクトで使っているライブラリは、Carthageで管理しているものがあって、
ターミナルでPod Installではなく、
CarthageのコマンドやPodのコマンドが混じって書かれている。
そのファイルがxxx.shというファイル、
それをターミナルで実行した時には
Build Failed
Task failed with exit code 65
...
This is usually indicates that project itself failed to compile.
please check the xcodebuild log for more details: /var/folders/...carthage-xcodebuild.sHeTHo.logと出ました。冷静に分析しましょう。
言われた「 /var/folders/...carthage-xcodebuild.sHeTHo.log」へ入ってみると、
/bin/sh -c /Users/name/Library/Caches/org.carthage.carthageKit/Derived/...Script-xxxxx.shcp /Users/name/ExternalModules/... /users/name/...
cp: /Users/name/ExternalModules/...is a directory(not copied).
Command PhaseScriptExectution failed with a nonzero exit code
...
The following build commands failed:
PhaseScriptExecution Run Script Script-xxxxx.sh
(1 failure)言われた「Script-xxxxx.sh」へ入ってみると
確かに「/Users/name/ExternalModules/...」の中身をあるpathへCopyすると書かれているなんでこのコマンドが失敗だろうと思いながら、
Copy元の「/Users/name/ExternalModules/...」へ入ってみると、空っぽです。。。
だからCopy失敗しました。前にMacの容量が足りなかった時にいっぱいCacheを削除してしまったせいかもしれない、
「/Users/name/ExternalModules/...」の中身を復元してみると、
無事に実行できました。●勉強になったこと:
エラーlogを分析すること。Shellのコマンドをもっと勉強すること。
Shellで書かれていることが頻繁に出てくるので、Shellのコマンドが分からないなら、logを見てもしょうがいない。
今回は他人の知恵をお借りして、shellのコマンドをわかったら、logを見てどこに失敗していたことがわかって、
詳しく原因調査ができたことが解決のカギとなりました。
- 投稿日:2020-11-30T23:45:13+09:00
UINavigationControllerの戻るイベントを取得したい!
はじめに
UINavigationController
の左上に表示される「戻る」ボタンのイベントを取得したいことがたまにあります!「戻る」ボタンのイベントを取得するために試行錯誤した結果です。
結論から言うとちゃんとイベントを取得したいなら
navigationItem.leftBarButtonItem
をカスタムしましょうサンプル
サンプルとして
FirstiViewController
(赤) ->SecondViewController
(青) ->ThirdViewController
(緑) と push で遷移する画面構成でSecondViewController
の「戻る」について考えます。
SecondViewController
からは Full Screen の modal 遷移(黄)もつけています。deinit を使う
SecondViewController
から戻る際はdeinit
が呼ばれるのでここで試してみます。deinit { print("戻る!!!") }
SecondViewController
からFirstViewController
に戻る際に呼ばれますがここだとThirdViewController
からpopToRootViewController
などでFirstViewController
に戻った場合と区別することが困難ですUINavigationControllerDelegate を使う
UINavigationController
にはUINavigationControllerDelegate
がありこのデリゲートを使ってイベント取得を試みます。
SecondViewController
から戻った場合はFirstiViewController
に戻るのでデリゲートで表示されるのがFirstiViewController
かどうかで下記のように判定します。extension SecondViewController: UINavigationControllerDelegate { // ナビゲーション遷移の直前に呼ばれる func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { if viewController is FirstViewController { print("戻る!!!") } } // ナビゲーションの遷移直後に呼ばれる func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { if viewController is FirstViewController { print("戻る!!!") } } }
willShow
の方はThirdViewController
からpopToRootViewController
などでFirstViewController
に戻った際も呼ばれてしまうので使うならdidShow
の方になるでしょう。「戻る」押下時にログ表示とかならできますがアラートを表示したいなど遷移を止めたいときなどは残念ながらこれではダメです
(あと個人的に
UINavigationControllerDelegate
を各画面で使いたくない)
viewDidDisappear を使う
viewWillDisappear
とviewDidDisappear
を使って試してみます。こちらは
willShow
,didShow
と同じようなタイミングで呼ばれます(SecondViewController
が非表示になるタイミングです)。気をつけないといけないのは modal 遷移の Full Screen などでも呼ばれることです。
SecondViewController
からThirdViewController
に遷移する場合と modal 遷移の Full Screen などで他の画面に遷移する場合を判定しないといけないので下記のようにします。override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if navigationController?.viewControllers.contains(self) == false { print("戻る") } } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) if navigationController == nil { print("戻る") } }
didShow
と同じで「戻る」押下時にログ表示とかならできますがアラートを表示したいなど遷移を止めたいときなどは残念ながらこれではダメですleftBarButtonItem をカスタムする
上に書いたやつはすべて厳密に言うと「戻る」ボタンを押下したかどうかは取れていません
スワイプで戻るでも呼ばれるし、別にボタンを置いてpopViewController
しても呼ばれてしまいます。それに遷移途中でイベントを取っているので遷移自体を止めることはできません(アラート出してキャンセルとかできない)。結局のところ
leftBarButtonItem
をカスタムしてボタンの押下イベントを取るしかありません!!override func viewDidLoad() { super.viewDidLoad() let button = UIButton(type: .system) button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside) button.setTitle("Back", for: .normal) button.setImage(UIImage(named: "back"), for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 16) button.imageEdgeInsets = .init(top: 0, left: -8, bottom: 0, right: 0) navigationItem.leftBarButtonItem = .init(customView: button) } @objc private func back(_ sender: Any) { print("戻る!!!") navigationController?.popViewController(animated: true) }見た目も限りなく戻るボタン
使った画像はこれです(てきとーに作りました)。
これで「戻る」ボタン押下イベントを完璧に取得できました
がしかし、leftBarButtonItem
をカスタムするとスワイプバックできないし iOS14 からできたロングタップのメニュー表示もできなくなります結論
厳密に「戻る」ボタン押下イベントを取得したいなら
leftBarButtonItem
のカスタムかなという感じです。
(わたしの場合は戻るときにログとりたいという要件だったのでviewDidDisappear
を使いました。)おわりに
「戻る」ボタン押下イベントの取得方法としては結局、
leftBarButtonItem
のカスタムに落ち着きましたがそもそも戻るときにアラート出したいとかの場合は本当に push でいいのか遷移方法を見直すべきな気もします
- 投稿日:2020-11-30T23:09:54+09:00
UITextDocumentProxyのselectedTextは3行以上かつ改行含めて65文字以上選択していると正しい選択部分を返さない
想定外でした。
挙動の概要
動作確認は第1・2世代のiPhoneSE (iOS14.2)において行っています。
タイトルの通り、UITextDocumentProxyの
selectedText
は3行以上かつ65文字以上選択していると正しい選択部分を返しません。代わりに最初と最後の2行を結合したものを返してきます。そんなことある?なぜか最後だけ文字数が32と判定されています。
それぞれの場面でtextDocumentProxy.selectedText?.debugDescription
を出力すると以下のようになります。Optional("aaaaaaaaaaaaaaaa") Optional("aaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbb") Optional("aaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbb\ncccccccccccccccc") Optional("aaaaaaaaaaaaaaaa\ndddddddddddddddd")挙動をさらに理解するために次の二例を見ましょう。
非常にわかりにくくて申し訳ないのですが、文字数は改行を含まない設定で算出しているため、改行を含める場合一枚目は64文字、二枚目が65文字です。ここを境に挙動が変わっていることがわかります。
同様の事象は3行でも起こります。
同様に改行を含めて64文字、65文字を境に挙動が変わります。ハラスメントではないでしょうか。
挙動の問題点
さて、この挙動は実用上どう問題になるでしょうか。
まず、こちら側では得られた二行分のテキストが内部を省略されてしまったものなのか、それともそうでないのか、という判断ができません。従って次のように判断する必要があります。
- 三行以上のテキストがselectedTextに含まれていた場合→問題ない
- 二行のテキストが含まれていた場合→内部の省略かどうか判断ができない
- 一行のテキストが含まれていた場合→問題ない
ちょうど二行のテキストが得られた場合のみ挙動を変えなければならないということです。
しかしユーザ側から見れば、複数行選択した場合に望む挙動を得られる場合と得られない場合が生じることになります。この奇天烈な動作は不具合にしか見えませんし、「3行以上かつ改行含めて65文字以上選択しないで下さい」とはとても頼めません。従って実際は次のようにせざるを得ません。
❌ 三行以上のテキストがselectedTextに含まれていた場合→諦める
❌ 二行のテキストが含まれていた場合→諦める
⭕️ 一行のテキストが含まれていた場合→用いる提供できる機能がぐっと制限されますが、やむを得ないでしょう。
恣意的にやらなければ起こらなそうな動作なのに、ドキュメントに書いていないのが本当に意味不明でした。※こちらの環境によるものの可能性があるので、ぜひ検証をお願いします。
参照
- 投稿日:2020-11-30T22:51:52+09:00
Combining Operatorsチートシート
Combining Operators
RxSwiftでコードを書く必要があり、学習中です。
参考文献を元に学んだ内容をすぐ振り返れるようにまとめていきます。
今回はCombining Operatorsについてまとめました。
完全に自分の解釈でコメントを載せていますが、解釈に誤りがありましたら、コメント欄より教えてください。startWith()
example(of: "startWith") { // 1 let numbers = Observable.of(2, 3, 4) // 2 let observable = numbers.startWith(1) _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
Observable.concat
同じ型同士の時のみ、Concatできる!
example(of: "Observable.concat") { // 1 let first = Observable.of(1, 2, 3) let second = Observable.of(4, 5, 6) // 2 let observable = Observable.concat([first, second]) observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
concat
example(of: "concat") { let germanCities = Observable.of("Berlin", "Münich", "Frankfurt") let spanishCities = Observable.of("Madrid", "Barcelona", "Valencia") let observable = germanCities.concat(spanishCities) _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
concatMap
example(of: "concatMap") { // 1 let sequences = [ "German cities": Observable.of("Berlin", "Münich", "Frankfurt"), "Spanish cities": Observable.of("Madrid", "Barcelona", "Valencia") ] // 2 let observable = Observable.of("German cities", "Spanish cities") .concatMap { country in sequences[country] ?? .empty() } // 3 _ = observable.subscribe(onNext: { string in print(string) }) }コンパイル結果
mege
maxConcurrentを使うことで、同時にObserveするSequenceの上限を設定できる。
example(of: "merge") { // 1 let left = PublishSubject<String>() let right = PublishSubject<String>() // 2 let source = Observable.of(left.asObservable(), right.asObservable()) // 3 let observable = source.merge(maxConcurrent: 2) _ = observable.subscribe(onNext: { value in print(value) }) // 4 var leftValues = ["Berlin", "Munich", "Frankfurt"] var rightValues = ["Madrid", "Barcelona", "Valencia"] repeat { switch Bool.random() { case true where !leftValues.isEmpty: left.onNext("Left: " + leftValues.removeFirst()) case false where !rightValues.isEmpty: right.onNext("Right: " + rightValues.removeFirst()) default: break } } while !leftValues.isEmpty || !rightValues.isEmpty // 5 left.onCompleted() right.onCompleted() }コンパイル結果
combineLatest
全てのObservableから値が発信されるまで、何もしない。異なる方のSequenceをまとめて、最新の値を取り出す。
example(of: "combineLatest") { let left = PublishSubject<String>() let right = PublishSubject<String>() // 1 // let observable = Observable.combineLatest(left, right) { // lastLeft, lastRight in // "\(lastLeft) \(lastRight)" // } // 1 combineLatestでタプルにして、filterをかける方法がよく使われる。 // let observable = Observable // .combineLatest(left, right) { ($0, $1) } // .filter { !$0.0.isEmpty } // 1: Stringが確か受付内ため、余り使われない方法 let observable = Observable.combineLatest([left, right]) { strings in strings.joined(separator: " ") } _ = observable.subscribe(onNext: { value in print(value) }) // 2 print("> Sending a value to Left") left.onNext("Hello,") print("> Sending a value to Right") right.onNext("world") print("> Sending another value to Right") right.onNext("Swift") print("> Sending another value to Left") left.onNext("Have a good day,") left.onCompleted() right.onCompleted() }コンパイル結果
combine user choice and value
ユーザーの設定が変更した時に、表示値を変える
example(of: "combine user choice and value") { let choice: Observable<DateFormatter.Style> = Observable.of(.short, .long) let dates = Observable.of(Date()) let observable = Observable.combineLatest(choice, dates) { format, when -> String in let formatter = DateFormatter() formatter.dateStyle = format return formatter.string(from: when) } _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
zip
indexed sequencing: ペアが成立しなくなったら、zipは値を発しなくなるが、observableがCompleteするまで処理が終わらないため、observableが完了していることを確認すること。
example(of: "zip") { enum Weather { case cloudy case sunny } let left: Observable<Weather> = Observable.of(.sunny, .cloudy, .cloudy, .sunny, .cloudy) let right = Observable.of("Lisbon", "Copenhagen", "London", "Madrid", "Vienna") let observable = Observable.zip(left, right) { weather, city in return "It's \(weather) in \(city)" } _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
withLatestFrom
example(of: "withLatestFrom") { // 1 let button = PublishSubject<Void>() let textField = PublishSubject<String>() // 2 //the trigger observableが撮りガー let observable = textField.sample(button) //data Observableがトリガー // let observable = button.withLatestFrom(textField) _ = observable.subscribe(onNext: { value in print(value) }) // 3 textField.onNext("Par") textField.onNext("Pari") textField.onNext("Paris") button.onNext(()) button.onNext(()) }コンパイル結果
amb
二つのObservableのどちらをつかうかAmbiguousな場合。 使い所:connecting to redundant servers and sticking with the one that responds first.
example(of: "amb") { let left = PublishSubject<String>() let right = PublishSubject<String>() // 1 let observable = left.amb(right) _ = observable.subscribe(onNext: { value in print(value) }) // 2 left.onNext("Lisbon") right.onNext("Copenhagen") left.onNext("London") left.onNext("Madrid") right.onNext("Vienna") left.onCompleted() right.onCompleted() }コンパイル結果
switchLatest
example(of: "switchLatest") { // 1 let one = PublishSubject<String>() let two = PublishSubject<String>() let three = PublishSubject<String>() let source = PublishSubject<Observable<String>>() // 2 let observable = source.switchLatest() let disposable = observable.subscribe(onNext: { value in print(value) }) // 3 source.onNext(one) one.onNext("Some text from sequence one") two.onNext("Some text from sequence two") source.onNext(two) two.onNext("More text from sequence two") one.onNext("and also from sequence one") source.onNext(three) two.onNext("Why don't you see me?") one.onNext("I'm alone, help me") three.onNext("Hey it's three. I win.") source.onNext(one) one.onNext("Nope. It's me, one!") disposable.dispose() }コンパイル結果
reduce
注意:sequencesが完了しないと、Summaryがemitされない。
example(of: "reduce") { let source = Observable.of(1, 3, 5, 7, 9) // 1 // let observable = source.reduce(0, accumulator: +) let observable = source.reduce(0){summary, newValue in return summary + newValue } _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
scan
累計、統計、状態をコンピュートする際に使える。
値が変わると毎回、Observableから値がEmitされる。
example(of: "scan") { let source = Observable.of(1, 3, 5, 7, 9) let observable = source.scan(0, accumulator: +) _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
練習
example(of: "Challeng1") { let source = Observable.of(1, 3, 5, 7, 9) let scanedObservable = source.scan(0, accumulator: +) let observable = Observable.zip(source, scanedObservable){current, total -> String in return "Total:\(String(total)), Current:\(String(current)))" } _ = observable.subscribe(onNext: { value in print(value) }) }コンパイル結果
練習
acc(current, accumuratedValue)となるから、acc.1 + currentとしている。
//acc(current, accumuratedValue)となるから、acc.1 + currentとしている。 example(of: "Challeng2") { let source = Observable.of(1, 3, 5, 7, 9) let observable = source.scan((0, 0)) { acc, current in print("acc\(acc)") print("current\(current)") return (current, acc.1 + current) } _ = observable.subscribe(onNext: { tuple in print("Value = \(tuple.0) Running total = \(tuple.1)") }) }コンパイル結果
参考文献
1.Combining Operators Written by Florent Pillet
Chapter9 Transforming Operators Written by Scott Gardner
詳細が気になる方はこちらをご参照ください!
https://www.raywenderlich.com/13285844-rxswift-reactive-programming-with-swift-update-now-available
- 投稿日:2020-11-30T21:51:53+09:00
【備忘録】Swift 実践入門について 第5章
Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。第5章
制御構文 上から下へ流れていくプログラムの動きを変更したりすること。
理解度:60%くらいか5.2
条件分岐
・if-let文
let a = Optional(1)
if let b = a {
print(“値は\(a)です”)
} else {
print(“値が存在しません”)
}
値は1です・guard文
func printIfPositive() {
let a = 1
guard a > 0 else {
print(“aは0未満です”) //falseならこの部分が実行される
return //スコープ外に退出
}
print(a)
}
printIfPositive() //結果は1・switch文…Bool型ではなく、caseを設定していくことでもっと細かい条件分岐が可能。defaultをつけることで条件に網羅性が出るが、細かく設定した方が追加で条件を設定する場合に強い。
- 投稿日:2020-11-30T20:47:47+09:00
[Cocoa][Swift]iOS 14 でのトラッキングのオプトインについて
広告を使って Android と iOS のモバイルアプリを宣伝する場合、広告のクリックがアプリのインストールとアプリ内操作の獲得にどの程度貢献しているかを把握することが重要だ。
iOS の IDFA(広告ID)は、この情報を計測するために利用されている。
iOS14のトラッキングは、以下の種類がある。
- SKAdNetwork (SKA)
- ユーザの個人情報を利用しない。
- アプリのインストールと特定のキャンペーンを関連付けるためのもの。
- AppTrackingTransparency (ATT) フレームワーク
- IDFAを扱うためのもの。
iOS14から、ASIdentifierManager の isAdvertisingTrackingEnabled は非推奨。
ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager]; if ([identifierManager isAdvertisingTrackingEnabled]) { NSLog(@"[idfa] isAdvertisingTrackingEnabled enabled"); } else { NSLog(@"[idfa] isAdvertisingTrackingEnabled disabled"); } NSString *idfa = identifierManager.advertisingIdentifier.UUIDString; NSLog(@"[idfa] %@", idfa);コードの互換性は保たれていて、iOS14では isAdvertisingTrackingEnabled はfalseを返す。
iOS14では、設定のプライバシー > 広告 画面がなくなった。
システムが表示するダイアログの文言はInfo.plistで設定する。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSUserTrackingUsageDescription</key> <string>広告トラッキングしますよ。</string> </dict> </plist>ユーザに許可を得るコードは以下のとおり。
if (@available(iOS 14, *)) { [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler: ^(ATTrackingManagerAuthorizationStatus status) { NSLog(@"in requestTrackingAuthorizationWithCompletionHandler"); if (status == ATTrackingManagerAuthorizationStatusAuthorized) { NSLog(@"[requestTrackingAuthorization] authorized"); NSString *idfa = identifierManager.advertisingIdentifier.UUIDString; NSLog(@"[idfa] %@", idfa); } else if (status == ATTrackingManagerAuthorizationStatusDenied) { NSLog(@"[requestTrackingAuthorization] denied"); } else if (status == ATTrackingManagerAuthorizationStatusRestricted) { NSLog(@"[requestTrackingAuthorization] restricted"); } else { NSLog(@"[requestTrackingAuthorization] notDetermined"); } }]; } else { // Fallback on earlier versions }確認ダイアログは一度しか表示しないが、設定は後から有効にできる。その画面を表示するコードは以下のとおり。
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];【関連情報】
Cocoa Advent Calendar 2020
Cocoa.swift
Cocoa勉強会 関東
Cocoa練習帳
- 投稿日:2020-11-30T20:21:24+09:00
[Cocoa][Swift]Cocoa Update
以前のWWDCでは、Cocoaの変更内容を『Cocoa Update』という名称で発表していた。近年では『What's New in Cocoa』という名称で発表されていたが、今年のWWDCのセッションにはCocoaという用語が使われているものがない。Apple Developerサイトを確認しても、Cocoaという用語を見つけることが困難だ。
理由は色々あるだろう。以前はAppleのフレームワークの概念の理解させていということでCocoaという名前で説明していたが、もう、Cocoaという名称で設計思想を啓蒙する必要がなくなったということだろうか。
Cocoaと呼ばれていたフレームワークの基本的な考え方をApple Developerサイトから入手できた『Objective-Cプログラミングの概念(CocoaEncyclopedia.pdf)』の目次から拾ってみる。
- クラスクラスタ(Class Cluster)
- デリゲートとデータソース
- インストロスペクション
実行時にオブジェクト自身の内部の詳細を調べる機能- オブジェクトの初期化
- Model-View-Controllerパターン
- オブジェクトモデリング
- オブジェクトの可変性
- Receptionistパターン
キー値監視やKVO通知など- ターゲット - アクション機構
- フレームワークの相互乗り入れについて
Core FoundationとFoundationとで互換性のあるデータ型WWDC20の『Platforms State of the Union』から、フレームワークの更新に相当するものを抜き出してみる。
- Apple Silicon
たとえば、SDKを提供してる場合はライブラリはXCFrameworkでないと困るはず。- Mac Catalystの進化
iOSアプリケーションのmacOS対応の方法の選択肢が増えた。- macOS Big Sur
Aquaではなくなった。- Widget
- App Clips
- SwiftとSwiftUI
Cocoaとは異なる概念のものも。もはや、Cocoaという用語が通じない世代の開発者も存在していると思うので、世代間ギャップを感じる。
【関連情報】
Cocoa Advent Calendar 2020
Cocoa.swift
Cocoa勉強会 関東
Cocoa練習帳
- 投稿日:2020-11-30T19:37:34+09:00
Xcodeのビルド時にアーキテクチャが異なるframeworkファイルを差し替える
qnote Advent Calendar 2020 の2日目です。
とあるプロジェクトで使用している外部ライブラリが実機用とシミュレーター用の2つのframeworkファイルがありプロジェクトファイルには実機用がリンクされています。
実機用のファイルには実機のアーキテクチャが含まれているバイナリファイルがあり、シミュレータ用のファイルにはシミュレータのアーキテクチャがあり、実機のアーキテクチャにはシミュレータが含まれていないためシミュレーターで実行ができない状態になっているのでシミュレーターで実行ができるようにしたいと思います。
シミュレータで実行する際にビルド時にエラーになってしまうのでビルド前差し替えてビルド後に戻せば良いのではと思いましたのでこの方法で進んでみます。frameworkのバイナリに含まれているアーキテクチャについて
詳しくは以下のサイトをご確認ください。
・https://qiita.com/fuwamaki/items/ce1cd438ec5dfd4ccb9a#simulator%E3%81%A7%E3%81%AF%E3%83%93%E3%83%AB%E3%83%89%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%AE%E3%81%AB%E5%AE%9F%E6%A9%9F%E3%81%A0%E3%81%A8%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84
・https://blog.ymyzk.com/2015/12/ios-tvos-watchos-architectures/どのように差し替えるか
・Build PhaseのRun Script
・スキームのPre-actionsとPost-actions
のどちらかで差し替えをします。
調べると大体シェルスクリプトが出てきますがせっかくなのでSwiftでやっていこうと思います。
Run Scriptはドラッグ&ドロップで移動できるのでビルド前の方はCompile Sourcesより前に配置します。注意点
どちらもビルドが失敗するとビルド後の処理が実行されないので万が一のことを考えてGit等で元に戻せるようにしておいた方が良いです。
プロジェクトフォルダの状態
プロジェクトを新規作成してデフォルトでできる(プロジェクト名).xcodeprojファイルがあるディレクトリに
(プロジェクト名)/SDK/hoge/lib/iphoneos/hoge.framework
(プロジェクト名)/SDK/hoge/lib/iphonesimulator/hoge.framework
に各frameworkファイル(iphoneosが実機、iphonesimulatorがシミュレータ)と
script/script.swift
にスクリプト用Swiftファイルがある状態です。Swiftのファイルの実行
ビルド前
#ログを書き出して確認する場合は下の行を実行する #exec > ${PROJECT_DIR}/script/prebuild.log 2>&1 /usr/bin/env xcrun --sdk macosx swift ${SRCROOT}/script/script.swift "pre" ${PROJECT_DIR} ${PLATFORM_NAME}ビルド後
#ログを書き出して確認する場合は下の行を実行する #exec > ${PROJECT_DIR}/script/postbuild.log 2>&1 /usr/bin/env xcrun --sdk macosx swift ${SRCROOT}/script/script.swift "post" ${PROJECT_DIR} ${PLATFORM_NAME}preの部分がpostになっています。
script.swiftの中身は以下の内容になっています。
script.swift#!/usr/bin/swift import Cocoa import Foundation let actionType = CommandLine.arguments[1] // 引数1 "pre" または "post" let projectDirectory = CommandLine.arguments[2] // 引数2 ${PROJECT_DIR} let platform = CommandLine.arguments[3] // 引数3 ${PLATFORM_NAME} let frameworkFileName = "hoge.framework" let tempFrameworkFileName = "temp_\(frameworkFileName)" let libraryDirectory = "\(projectDirectory)/(プロジェクト名)/SDK/hoge/lib/" let iphoneFrameworkDirectory = "\(libraryDirectory)/iphoneos/" let iphoneOriginalPath = "\(iphoneFrameworkDirectory)\(frameworkFileName)" let iphoneTempPath = "\(iphoneFrameworkDirectory)\(tempFrameworkFileName)" let simulatorFrameworkDirectory = "\(libraryDirectory)/iphonesimulator/" let simulatorOriginalPath = "\(simulatorFrameworkDirectory)\(frameworkFileName)" class Script { var isSimulator: Bool { return platform == "iphonesimulator" } func preAction() { reverseActionIfNeeded() guard isSimulator else { return } let _ = rename(iphoneFrameworkDirectory, oldName: frameworkFileName, newName: tempFrameworkFileName) let _ = copy(simulatorOriginalPath, toPathName: iphoneOriginalPath) } func postAction() { guard isSimulator else { return } let _ = remove(iphoneOriginalPath) let _ = rename(iphoneFrameworkDirectory, oldName: tempFrameworkFileName, newName: frameworkFileName) } func reverseActionIfNeeded() { guard FileManager.default.fileExists(atPath: iphoneTempPath) else { return } let _ = remove(iphoneOriginalPath) let _ = rename(iphoneFrameworkDirectory, oldName: tempFrameworkFileName, newName: frameworkFileName) } func copy(_ atPathName: String, toPathName: String) -> Bool { do { try FileManager.default.copyItem(atPath: atPathName, toPath: toPathName) } catch { print(error) return false } return true } func rename(_ pathName: String, oldName: String, newName: String) -> Bool { let atPathName = "\(pathName)/\(oldName)" let toPathName = "\(pathName)/\(newName)" do { try FileManager.default.moveItem(atPath: atPathName, toPath: toPathName) } catch { return false } return true } func remove(_ pathName: String) -> Bool { do { try FileManager.default.removeItem(atPath: pathName) } catch { return false } return true } } if actionType == "pre" { Script().preAction() } else if actionType == "post" { Script().postAction() }大まかに動作内容を解説します。
ビルド前のpreAction()でシミュレータの場合に
iphoneos/hoge.framework
をiphoneos/temp_hoge.framework
にリネームして
iphonesimulator/hoge.framework
をiphoneos/hoge.framework
にコピーします。
これでプロジェクトファイルにリンクされている実機用のhoge.frameworkをシミュレータ用のファイルと入れ替えることができ、ビルドが成功するようになります。
ビルド後のpostAction()にて
iphoneos/hoge.framework
を削除して
iphoneos/temp_hoge.framework
をiphoneos/hoge.framework
にリネームしています。
これで元の状態に戻ります。注意点でも記載しましたがビルドに失敗した場合にpostAction()が実行されないので
preAction()のreverseActionIfNeeded()にてiphoneos/temp_hoge.framework
が存在していた場合iphoneos/hoge.framework
がシミュレータ用のままなので最初に元の状態に戻してから処理を行うようにしています。参考
https://qiita.com/kuluna/items/2225298e8a9a7b3b3ef6中々こういう状況にはならないかと思いますが、もし誰かが同じことで困った時の助けになればと思います。
- 投稿日:2020-11-30T19:03:16+09:00
Swiftにおける基本的な型の紹介〜Bool型〜
Bool型とは
Bool型とは真理値のことで、ある命題が真であるか偽であるかを表す値です。
つまり、式が型推論されない場合はBool型の値であるtrueかfalseを返します。let a = true // Bool型 let b = false // Bool型 let c: Bool c = 10 == 10 // true論理演算
論理演算とは真理値に対する演算になります。
Bool型には、否定・論理積・論理和の3つの論理演算子が用意されています。論理演算をうまく使うことにより様々な条件式を記載することが可能です。
・ 否定
否定は、真理値を逆にする論理演算です。
Bool型の値の否定を求めるには、!演算子を利用します。否定したい変数や定数に!をつけることにより、逆の真理値を導くことが可能になります。
let a = true // true let b = !a // false・ 論理積
論理積とは、与えられた複数の真理値全てが真であれば真となる論理演算子です。
2つ以上の真理値の論理積を求める場合には&&を使用します。
&& は、〜かつ〜 の、かつの部分だと思ってください。true && true の場合は全ての真理値が true なので結果も true になります。
しかし、true && false の場合は全ての真理値が true ではないので結果は false になります。国語のテストで、点数が50点を超えているかどうか調べる際に論理積を使うとこうなります。
たろう:100点、あやか:60点、まこと:10点の場合。let tarou = 100 // たろうは100点 let ayaka = 60 // あやかは60点 let makoto = 10 // まことは10点 var result: Bool // Bool型 //たろうの点数が50点以上 &&(かつ) あやかの点数が50点以上 -> true && true result = tarou >= 50 && ayaka >= 50 // true //太郎の点数が50点以上 &&(かつ) まことの点数が50点以上 -> true && false result = tarou >= 50 && makoto >= 50 //false //三人のテストの結果が50点以上 -> true && true && false result = tarou >= 50 && ayaka >= 50 && makoto >= 50 //false少しわかりにくいですかね・・・?(笑)
とにかく論理積を使っていた場合、
一つでもfalseが存在したら結果はfalseになるということです!・ 論理和
論理和とは、与えられた真理値の少なくともどれか一つが真であれば、結果も真となる論理演算です。
2つ以上の真理値の論理積を求める場合には||を使用します。
|| は、〜または〜 の、またはの部分だと思ってください。true || true の場合は、どちらもtrueなので、結果はtrueになります。
true || false の場合は、片方がtrueなので、結果はtrueになります。
false || false の場合は、両方ともfalseでtrueが存在しないので、結果はfalseになります。先ほどの例を使うとこのようになります。
let tarou = 100 // たろうは100点 let ayaka = 60 // あやかは60点 let makoto = 10 // まことは10点 var result: Bool // Bool型 //たろうの点数が50点以上 ||(または) あやかの点数が50点以上 -> true || true result = tarou >= 50 || ayaka >= 50 // true //太郎の点数が50点以上 ||(または) まことの点数が50点以上 -> true || false result = tarou >= 50 || makoto >= 50 //true //三人のテストの結果が50点以上 -> true || true || false result = tarou >= 50 || ayaka >= 50 || makoto >= 50 //true論理和の場合は、どれか一つでもtrueが入っていれば結果はtrueになるということです!
Bool型は最初のうちはよくわかりませんでしたが、
使っていく内に結構慣れていきました!分からなくても色々試して調べて使えるようにしていきましょう!
以上、ありがとうございました。
- 投稿日:2020-11-30T18:58:14+09:00
iOSでの半モーダル/ハーフモーダルの実装についてのまとめ
はじめに
- iOSのUIでハーフモーダルを見かけることが増えました。Apple純正のアプリでも実装されることが多く、今後実装することが多くなると思います。 今後の実装の参考になるように情報をまとめたいと思いました。
半モーダル/ハーフモーダルとは
- こちらの記事がとても参考になりますので引用します。iOSにおける半モーダルビューの解釈
・モードの完全遷移が起こらない ・モードを多重化しながらパラレルに対話可能 ・モードを終了させずにモードからの一時退避が可能 ・擬似的なマルチウインドウ・インターフェイスに応用可能 ・スワイプなどインタラクションコストの低い操作方法によってモードの切り替えが可能
- Pull dissmissや画面遷移せずにタスクを切り替える機能などによく使われます
- 代表的なものはAppleのMapアプリです
実装方法
UIModalPresentationStyleを使う
let vc = UIViewController() vc.modalPresentationStyle = .pageSheet present(vc, animated: true, completion: nil)
- この実装だと下階層の画面に対してタップできないなどの仕様の制限があります。
ライブラリを使う
- すでに完成度の高いライブラリが存在するので実装コストを下げることができます。
SCENEE/FloatingPanel
- Apple純正のアプリのような動きを再現できます
slackhq/PanModal
自前で実装する
- より完成度の高い完成度を目指す場合は自前で実装することも選択肢に入ります。実装する上でのポイントをあげておきます。
徹底調査
- 完成度の高いライブラリが多いので中身を覗いてみると理解が進みます。とくに上記で挙げたライブラリは参考になります。
- cocoacontrolsなどを使うとたくさんのライブラリを見つけることができます。
UIViewControllerTransitioningDelegate
- この実装をすることで表示のアニメーションのカスタマイズと表示後の画面もカスタマイズできます
let vc = UIViewController() vc.modalPresentationStyle = .custom vc.transitioningDelegate = self present(vc, animated: true, completion: nil)extension UIViewController: UIViewControllerTransitioningDelegate { public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return UIPresentationController(presentedViewController: presented, presenting: presenting) } public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PresentationAnimator() } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissionAnimator() } }PSPDFTouchForwardingView
- PSPDFTouchForwardingViewを使うことでModalで表示した下の階層のViewのタップイベントをとることができるようになります。
- Presentation Controllers and Adaptive Presentations
final class PSPDFTouchForwardingView: UIView { final var passthroughViews: [UIView] = [] override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let hitView = super.hitTest(point, with: event) else { return nil } guard hitView == self else { return hitView } for passthroughView in passthroughViews { let point = convert(point, to: passthroughView) if let passthroughHitView = passthroughView.hitTest(point, with: event) { return passthroughHitView } } return self } }private var touchForwardingView: PSPDFTouchForwardingView? override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() touchForwardingView = PSPDFTouchForwardingView(frame: containerView!.bounds) containerView?.insertSubview(touchForwardingView, at: 0) }まとめ
- 近年ハーフモーダルのUIを採用するアプリが増えてきている
- 完成度の高いライブラリも多く実装コストを下げることができる
- 自前実装もポイントを押さてしまえば簡単です?♂️
参考リンク
- 投稿日:2020-11-30T18:58:14+09:00
iOSでの半モーダル/ハーフモーダルの実装についてまとめ
はじめに
- iOSのUIでハーフモーダルを見かけることが増えました。Apple純正のアプリでも実装されることが多く、今後実装することが多くなると思います。 今後の実装の参考になるように情報をまとめたいと思いました。
半モーダル/ハーフモーダルとは
- こちらの記事がとても参考になりますので引用します。iOSにおける半モーダルビューの解釈
・モードの完全遷移が起こらない ・モードを多重化しながらパラレルに対話可能 ・モードを終了させずにモードからの一時退避が可能 ・擬似的なマルチウインドウ・インターフェイスに応用可能 ・スワイプなどインタラクションコストの低い操作方法によってモードの切り替えが可能
- Pull dissmissや画面遷移せずにタスクを切り替える機能などによく使われます
- 代表的なものはAppleのMapアプリです
実装方法
UIModalPresentationStyleを使う
let vc = UIViewController() vc.modalPresentationStyle = .pageSheet present(vc, animated: true, completion: nil)
- この実装だと下階層の画面に対してタップできないなどの仕様の制限があります。
ライブラリを使う
- すでに完成度の高いライブラリが存在するので実装コストを下げることができます。
SCENEE/FloatingPanel
- Apple純正のアプリのような動きを再現できます
slackhq/PanModal
徹底調査
- 完成度の高いライブラリが多いので中身を覗いてみると理解が進みます。とくに上記で挙げたライブラリは参考になります。
- cocoacontrolsなどを使うとたくさんのライブラリを見つけることができます。
自前で実装する
- より完成度の高い完成度を目指す場合は自前で実装することも選択肢に入ります。実装する上でのポイントをあげておきます。
UIViewControllerTransitioningDelegate
- この実装をすることで表示のアニメーションのカスタマイズと表示後の画面もカスタマイズできます
let vc = UIViewController() vc.modalPresentationStyle = .custom vc.transitioningDelegate = self present(vc, animated: true, completion: nil)extension UIViewController: UIViewControllerTransitioningDelegate { public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return UIPresentationController(presentedViewController: presented, presenting: presenting) } public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return PresentationAnimator() } public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissionAnimator() } }PSPDFTouchForwardingView
- PSPDFTouchForwardingViewを使うことでModalで表示した下の階層のViewのタップイベントをとることができるようになります。
- Presentation Controllers and Adaptive Presentations
final class PSPDFTouchForwardingView: UIView { final var passthroughViews: [UIView] = [] override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let hitView = super.hitTest(point, with: event) else { return nil } guard hitView == self else { return hitView } for passthroughView in passthroughViews { let point = convert(point, to: passthroughView) if let passthroughHitView = passthroughView.hitTest(point, with: event) { return passthroughHitView } } return self } }private var touchForwardingView: PSPDFTouchForwardingView? override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() touchForwardingView = PSPDFTouchForwardingView(frame: containerView!.bounds) containerView?.insertSubview(touchForwardingView, at: 0) }まとめ
- 近年ハーフモーダルのUIを採用するアプリが増えてきている
- 完成度の高いライブラリも多く実装コストを下げることができる
- 自前実装もポイントを押さてしまえば簡単です?♂️
参考リンク
- 投稿日:2020-11-30T18:20:32+09:00
【備忘録】Swift 実践入門について 第4章
Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。第4章
コレクション let a = [1, 2, 3]など要素を複数持ちたいとき。
理解度:80%くらいか4.2
Array型…[1, 2, 3]4.3
Dictionary型…let dictionary = [“a” : 1, “b” : 2] キーワードの“Key”とValue4.4
範囲型…let range = 1..< 44.5
String型のコレクション
Character型…単一文字を示す
String.Index型…startIndexやendIndexなど4.6
シーケンス…値にアクセスしていく順番。普通は左から順番にアクセスしていくが変更も可能。・filter…複数のエレメントを持つ変数aに対してlet b = a.filter({element in element % 2 == 0})とすれば、定数bは変数aの偶数番目のエレメントを持つようになる。
・map…全エレメントに変更を加える。let b = a.map({element in element * 2})とすれば、定数bは変数aを2倍したエレメントを持つようになる。
・flatMap…let b = a.flatMap({value in [value, value + 1]})とすれば、定数bはaのエレメントとそのエレメントに1を足した値をそれぞれ持つ。
・compactMap…mapに似ているが、変更できない値があった場合は無視してコンパクトにする。
・コレクションのプロトコル
let a = [1, 2, 3, 4, 5]
a[2] //3
a.isEmpty // false
a.count //5
a.first //1
a.last //5「.プロパティ」にまだ慣れない。本を読んでいてもSwift側で決められたプロパティなのか、こちらが個人的に決めたものなのかが曖昧。ただ4章まで進めてきて、少しづつ理解が深まってきている気はする。
- 投稿日:2020-11-30T13:13:22+09:00
[Swift] Resultで例外をいい感じに処理する
注意
これはネタエントリーです
Swiftの公式見解は「例外は例外としてちゃんと処理しろ。Resultはそういうものじゃねぇ」です。
ご理解の上お読みいただけますようお願いいたします。なにこれ?
例外処理を do - try - catchを使わずResultで処理しちゃおうという奴です。
準備
こちらのエントリにある ifSuccess / ifFailure を利用しますので確認しておいてください。
じゃあやってみようか
特に難しいことをしようというのではありませんのでコードをドーンと出しちゃいます。
以下のようなコードをResultで書き換えちゃいます。
struct HogeError: Error {} func hoge(_ s: String) throws -> Int { guard let i = Int(s) else { throw HogeError() } return i } do { print(try hoge("123")) } catch { print(error) } do { print(try hoge("abc")) } catch { print(error) }とくに解説はいりませんね。
では書き換えます。
struct HogeError: Error {} func hoge(_ s: String) throws -> Int { guard let i = Int(s) else { throw HogeError() } return i } Result { try hoge("123") } .ifSuccess { print($0) } .ifFailure { print($0) } Result { try hoge("abc") } .ifSuccess { print($0) } .ifFailure { print($0) }僕にはかなり読みやすいのですが、皆さんはどうでしょう?
エラーを複雑にしてみる 1
例外処理では複雑な例外に対処する必要が多分に発生します。
まずはenumな例外を見てみましょう。enum HogeError: Error { case notNumber case minus case lessThan10(Int) } func hoge(_ s: String) throws -> Int { switch Int(s) { case nil: throw HogeError.notNumber case (...0)?: throw HogeError.minus case let n? where n < 10 : throw HogeError.lessThan10(n) case let n?: return n } }do-try-catchでは以下のようになります。
do { print(try hoge("abc")) } catch HogeError.notNumber { print("not number") } catch HogeError.minus { print("minus") } catch let HogeError.lessThan10(n) { print(n, "is less than 10.") } catch { print("we caught unknown error.", error) }ごく普通です。
ではこれをResultで処理してみましょう。
そのために新たなstructを導入しResultのextensionに手を加えます。
長いので読み飛ばしましょうstruct ResultErrorHandler<Failure: Error> { let failure: Failure? let handled: Bool func ifFailure<F: Error>(as type: F.Type, _ f: (F) -> Void) -> Self { if let error = failure as? F { f(error) return .init(failure: failure, handled: true) } return self } func ifFailure(_ f: (Failure) -> Void) { if handled { return } guard let failure = failure else { return } f(failure) } } extension Result { @discardableResult func ifSuccess(_ f: (Success) -> Void)-> Self { if case let .success(v) = self { f(v) } return self } func ifFailure(_ f: (Failure) -> Void) { if case let .failure(error) = self { f(error) } } func ifFailure<F: Error>(as type: F.Type, _ f: (F) -> Void) -> ResultErrorHandler<Failure> { guard case let .failure(error) = self else { return .init(failure: nil, handled: false) } if let e = error as? F { f(e) return .init(failure: error, handled: true) } return .init(failure: error, handled: false) } }ここにJump
これで準備が出来ましたのでResultを使った例外処理に書き換えてみましょう。
Result<Void, Error> { print(try hoge("abc")) } .ifFailure(as: HogeError.self) { error in switch error { case .notNumber: print("not number") case .minus: print("minus") case let .lessThan10(n): print(n, "is less than 10.") } } .ifFailure { error in print("we caught unknown error.", error) }読み飛ばしていただいたextensionで実装された
ifFailure(_:)
メソッドに渡された関数/クロージャは、その前に実行されたifFailure(as:)
メソッドがすでにエラーハンドリングを終えている場合は実行されません。
つまりこの場合ですとFailureがHogeError
しか起こりえませんので、ifFailure(_:)
メソッドに渡した関数/クロージャが実行されることはありません。残念ながらenumの個別のcaseごとに処理が分けられません。
この点はdo-try-catchの方がよいかもしれません。
ただし、こちらの場合はswitchによるcaseの網羅性チェックが入りますのでどちらが良いかは微妙です。
もちろんその方法もdo-try-catchで記述可能です。エラーを複雑にしてみる 2
複数のtypeのエラーが投げられる場合。
例外が投げられる可能性のあるAPI呼び出しを含むメソッドが独自の例外を投げる可能性もある。という場合などです。
メソッド内でAPI側の例外を処理しろって話ですが、今回はそういうのはなしで。enum HogeError: Error { case notNumber case minus case lessThan10(Int) } struct FugaError: Error {} func getString() throws -> String { throw FugaError() } func hoge() throws -> Int { switch Int(try getString()) { case nil: throw HogeError.notNumber case (...0)?: throw HogeError.minus case let n? where n < 10 : throw HogeError.lessThan10(n) case let n?: return n } }HogeErrorに加えてFugaErrorを定義しました。
また文字列を取得するthrowableなgetString()
関数を定義し、関数hoge()
はそれを利用して文字列を取得するように変更しました。ではこれをdo - try - catchを使って書いてみます。
do { print(try hoge()) } catch HogeError.notNumber { print("not number") } catch HogeError.minus { print("minus") } catch let HogeError.lessThan10(n) { print(n, "is less than 10.") } catch is FugaError { print("Can not get string.") } catch { print("we caught unknown error.", error) }
catch is FugaError
が追加されただけで他は変わっていません。ではこれをResultを使って書き換えます。
Result<Void, Error> { print(try hoge()) } .ifFailure(as: HogeError.self) { error in switch error { case .notNumber: print("not number") case .minus: print("minus") case let .lessThan10(n): print(n, "is less than 10.") } } .ifFailure(as: FugaError.self) { print("Can not get string.") } .ifFailure { error in print("we caught unknown error.", error) }こちらも
.ifFailure(as: FugaError.self)
が追加されただけで他は変わっていません。エラーを複雑にしてみる 3
Errorに継承関係がある場合
ちょっと特殊だとは思いますがErrorに継承関連がある場合を考えます。class E0: Error {} class E1: E0 {} func fuga(_ e1Flag: Bool) throws -> String { if e1Flag { throw E1() } throw E0() }これをdo - try - catchで書いてみます。
do { print(try fuga(true)) } catch let error as E0 { print("E0", error) } catch let error as E1 { print("E1", error) } catch { print("Other", error) }特に問題はありませんね。
と思いますがこれは問題ありです。
出力結果はこうなります。E0 E1投げられた例外はE1です。しかしcatchしたのはE0としてです。
do - try - catchではswitch-caseと同様に記述された順番に検査され最初に合致したcatchのみが実行されそれ以降は無視されます。
E1はE0でもあるためこのような結果を得ることになります。ただしい書き方はこうなります。
do { print(try fuga(true)) } catch let error as E1 { print("E1", error) } catch let error as E0 { print("E0", error) } catch { print("Other", error) }E1(継承先、子孫)の場合をE0(継承元、親、先祖)の前に記述します。
ではこれをResultで書き換えます。
Result<Void, Error> { print(try fuga(true)) } .ifFailure(as: E0.self) { error in print("E1", error) } .ifFailure(as: E1.self) { error in print("E1", error) } .ifFailure { error in print("Other", error) }出力結果
E0 E1先に提示したextensionで実装された
ifFailure
メソッドのシリーズはメソッドチェーンによる記述となるため記述した順に実行されるのは自明です。
また、一度処理を実行した場合はそれ以降の処理を行わない設計となっていますので、出力結果はdo - try - catchと同様になります。
こちらも以下のように書き換えることで対応します。Result<Void, Error> { print(try fuga(true)) } .ifFailure(as: E1.self) { error in print("E1", error) } .ifFailure(as: E0.self) { error in print("E1", error) } .ifFailure { error in print("Other", error) }これで意図した結果が得られます。
まとめ
いかがでしたでしょうか?
適切なextensionを導入することでdo - try - catchを使用するのと遜色なくResultを例外処理機構として利用することができることがわかりました。
結論
だからといってやっちゃだめ。
冒頭に書いたとおり、Swift開発陣は例外は例外として扱いdo - try - catchを使用することを推奨しています。
また、Resultを例外処理機構の一部として利用することを想定していません。あくまでもネタエントリーであることをご理解いただけますようお願いします。
- 投稿日:2020-11-30T07:41:52+09:00
Swiftにおける基本的な型の紹介〜数値型〜
代表的な型とその表現
swiftにはさまざまな型が存在します。
型とは、値の特性と値の操作を表現したものです。基本的なよく使う型は下記の6個になります。
Int型:整数を表現し、整数を操作するための四則演算などの機能を持っている。
Double型・Float型:浮動小数点数を表す、またInt型と同じく計算の機能を持っている。
String型:文字列を表現し、基本的な文字列操作の機能を持っている。
Bool型:真偽値を表現する。
Optional型:全てに関連する型として、値があるか空なのかのいずれかを表す。
Any型:任意の値を表す。今回は、この中のInt型ついて記載します。
数値型
数値に関する型を数値型と言ったりするらしいので、まとめさせていただきます。
数値型は大きく分類すると、整数型と浮動小数点型の2つに分けることができます。
・ 整数型
整数型はその名の通り整数を表す型で、ビット数によって様々な型に分けられます。
その中でも代表的な整数型はInt型になります。ビット数とは二進数の一桁のことです。
16ビットなら、0110101000010100 のように、0もしくは1が16個続いているというイメージだと思います。
コンピュータは、膨大な量の0と1を読み解き動いているんです・・・。恐ろしい!Int型のビット数は32ビットのプラットフォーム上では32ビット、
64ビットのプラットフォーム上で64ビットとなります。Int型の他に、固定ビット数の整数型が存在し、
Int8型、Int16型、Int32型、Int64型が存在します。
それぞれのビット数は型についている数字がビット数になります。それぞれの整数型には扱える整数の幅が存在します。
Int8型:-128 ~ 127
Int16型:-32,768 ~ 32,767
Int32型:-2,147,483,648 ~ 2,147,483,647
Int64型:-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807これらの値を超える整数を代入してしまうとコンパイルエラーが発生します。
let a: Int8 // -128 ~ 127 a = 1000 // コンパイルエラーエラー内容:Integer literal '1000' overflows when stored into 'Int8'
和訳:整数リテラル「1000」は「Int8」に格納されるとオーバーフローします今回は、Int8をInt16などにすればコンパイルエラーは出ません。
もしかして、Int16とかの許容範囲を覚えなきゃいけないの・・・? と思うかもしれませんが、
基本的にはInt型を使えば大丈夫かなと思います!ただ、Int32型なども、使う機会があると思うので、型の存在だけでも覚えておいて損はないかなと思いました。
・ 浮動小数点型
浮動小数点型は浮動小数点方式で小数を表す数値型です。
浮動小数点方式とは、ビットを桁の並びを表す仮装部と小数点の位置を表す小数部の2つに分け、
これらを掛けて少数を表す方式だそうです。正直よくわからない!(笑)
浮動小数点型 = 小数という認識でいいと思います!
つまり、DoubleもFloatも小数を扱う数値型ということですね。DoubleとFloat どちらを使えばいいのだろうと思うところですが、
Float型はおよそ10の38乗の正負の値まで表すことができ、
Double型はおよそ10の308乗の正負の値まで表すことができます。さらに、表現できる値の範囲だけでなく、値の精度も大きく異なります。
Double型は最小でも15桁の精度を持ちますが、
Float型は最小で6桁の精度しか持ちません。つまりどういうことかというと、
let a: Double = 12345678.9 // 12345678.9 let b: Float = 12345678.9 // 1.234568e+07というようになります。
Float型の方は、1.2345678 × 10の7乗ということですね。
Floatで何回も計算をすると、どんどん値の誤差が大きくなってきます。なので、使うとしたらDouble型一択なのかな?と思いますが、そういうわけでもありません。
例えば、端末の画面上の座標に使用されるCGFloat型は、
32ビットのプラットフォーム上ではではFloat型、
64ビットのプラットフォーム上ではではDouble型になります。一方で、地球上の座標を表す型などでは、より高い精度が求められるので、
どのプラットフォームでもDouble型が使用されるらしいです。数値型どうしの変換
swiftでは、異なる型どうしの計算や代入が出来ません。
それは、同じ整数型や浮動小数点型でも同様です。let a: Int = 123 let b: Int64 = a //コンパイルエラー let c: Float = 1.23 let d: Double = c //コンパイルエラー数値型を他の数値型に変換するにはイニシャライザを使いキャストします。
各数値型には、他の数値型の値から自動に型を生成するイニシャライザが用意されており、
これらを使用することで数値型どうしの変換が行えます。let a: Int = 123 let b: Int64 = Int64(a) // 123 let c: Float = 1.23 let d: Double = Double(c) // 1.23また、変換したい型よりも高い精度の型から代入すると、変換した型の精度に合わせて処理が実行されます。
let a: Float = 1.23 let b: Int = Int(a) // 1 let c: Double = 1.23456789 let d: Float = Float(c) // 1.234568数値型の比較
数値型には比較演算という、数値型どうしを比較する演算が存在します。
== : 左辺と右辺が一致する 123 == 456 //false != : 左辺と右辺が一致しない 123 != 456 //true > : 左辺が右辺より大きい 123 > 456 //false >= : 左辺が右辺以上 123 >= 456 //false < : 左辺が右辺より小さい 123 < 456 //true <= : 左辺が右辺以下 123 <= 456 //true比較演算も計算と同じで型が一致していないと
コンパイルエラーになりますので注意してください。比較演算はif文で使用しますので覚えておいて損はしないと思います。
以上、ありがとうございました!
- 投稿日:2020-11-30T02:17:26+09:00
Swiftの型推論が原因らしいエラーについての考察
はじめに
Swiftで、map関数を使って
Double型変数をプロパティに持つクラスの配列 → Int型変数をプロパティに持つ構造体の配列
に変換する必要があったのですが、その時に(おそらく)型推論が原因であろうエラーに出会い、かなりハマりました。
頭の中の再整理 & 情報を共有できればと思い、ここに記しておきます。コード
とあるライブラリを使用する中で遭遇したエラーで、状況はもう少し複雑だったのですが、今回のエラーに関係のないところは削ぎ落として簡略化したコードを以下に書いておきます。
Errorclass Origin { var x: Double init(x: Double) { self.x = x } } struct Mutant { var y: Int init(y: Int) { self.y = y } } [Origin(x: 100)].map { Mutant(y: $0.x) } //Error: Value of type 'Any' has no member 'x'
以上のように、おそらくSwiftの文法的には何も問題ないのですが
Any型はxというメンバー変数を持っていないよというエラーメッセージが出て怒られてしまっています。
Any型ということは型が定まっていないということですので、map関数の引数$0(ここではOriginクラスの型)に原因があるようです。Originクラスを定義する時に、変数xはDouble型だよ〜としっかり明示してあるのですが、map関数を使いInt型の配列に変換しようとするとなぜかAny型判定を喰らってしまってうまくいかない...
最初は文法が間違っているのかなと思い色々試行錯誤してみたのですが、何も解決できず...
他のエンジニアの方々に相談して、やっと解決?しました。ありがとうございます?解決
型を変換しようとしてエラーが出るなら、Mutant構造体のイニシャライザで同じDouble型で受け取ってそこからInt型に変換してはどうか?ということでやってみたところ、この場合はエラーメッセージが出ませんでした。
NotErrorclass Origin { var x: Double init(x: Double) { self.x = x } } struct Mutant { var y: Int init(y: Int) { self.y = y } init(y: Double) { self.y = Int(y) } } [Origin(x: 10)].map { Mutant(y: $0.x) } //問題なし型推論がどういう仕組みで動いているのかは全くの未知なのですが、おそらく
1. Swiftが$0の型を推論しようとする
2. Swift「Double型からInt型への変換だから$0がDouble型である可能性は低いな...」
3. Swift「ってことは$0って何型なんだ...?? 分からないからAny型にしとこ...」という理由で文法的には問題ないにもかかわらずエラーメッセージが出てしまったのだろう.. という結論に至りました。
エラーメッセージが少々不親切だったのではないか感は拭えないですが、気を付けなければいけませんね..
普段何気なくお世話になっている型推論がこんなに奥が深かったとは... と良くも悪くも勉強になったエラーでした。何かご指摘やご意見ございましたらお気軽によろしくお願い致します。お読みいただきありがとうございました。