- 投稿日:2020-09-20T22:18:08+09:00
Swiftで複数の戻り値をreturnする。
戻り値を宣言する
メソッド宣言時の戻り値部でメソッド名()->(Data型1,Data型2,Data型3...)の様に記述をすると
複数のデータを1つのメソッドで返すことができる。//日付取得メソッド func getDate() -> (String, String, String, String){ //操作日取得(yyyy-MM-dd) let date = Date() let formatter = DateFormatter() formatter.setLocalizedDateFormatFromTemplate("yMd") //日付計算 let dayFromToday = Calendar.current.date(byAdding: .day, value: -7, to: date)! let weekFromToday = Calendar.current.date(byAdding: .weekday, value: -7, to: date)! let monthFromToday = Calendar.current.date(byAdding: .month, value: -7, to: date)! let today = formatter.string(from: date); let DayFromToday = formatter.string(from: dayFromToday); let WeekFromToday = formatter.string(from: weekFromToday); let MonthFromToday = formatter.string(from: monthFromToday); return (today,DayFromToday,WeekFromToday,MonthFromToday) } //日付呼び出しメソッド func viewDatePeriod(today:String,DayFromToday:String,WeekFromToday:String,MonthFromToday:String) { //処理〜〜〜 }また、呼び出す際には以下のように取得したデータを割り振る必要がある。
var date = getDate() viewDatePeriod(today: date.0,DayFromToday: date.1,WeekFromToday: date.2,MonthFromToday: date.3)データに名前をつける
上記では、data.0など分かりづらいので以下の様にして宣言をすると呼び出し時に迷うことなくコーディングすることができる。
//データ選択時の呼び出しメソッド func viewDatePeriod(today:String,DayFromToday:String,WeekFromToday:String,MonthFromToday:String) { //日付呼び出しメソッド viewDatePeriod(today: date.today,DayFromToday: date.DayFromToday,WeekFromToday: date.WeekFromToday,MonthFromToday: date.MonthFromToday)
- 投稿日:2020-09-20T21:46:14+09:00
[Swift] protocol制約をかけた型で"Cannot use mutating member on immutable value"と言うエラーが出る場合の対処。
swift5で次のコードを実行すると
Cannot use mutating member on immutable value: 'hoge' is a 'let' constant
と言うエラーが出ます。これにしばらくハマってしまったので解決方法を書いておきます。
protocol Hoge{ var items:[String]{get set} } class Fuga:Hoge{ var items: [String] init(_ items: [String]){ self.items = items } } class Piyo:Hoge{ var items: [String] init(_ items: [String]){ self.items = items } } var hoges:[Hoge] = [Fuga(["a"]), Piyo(["a"]), Fuga(["a"])] hoges.forEach{ (hoge: Hoge) in hoge.items.append("b") //エラー:Cannot use mutating member on immutable value: 'hoge' is a 'let' constant } print(hoges.map{$0.items})
items
は{get set}
で宣言しており、問題はないように見えますが、エラーではhoge
が定数だと言っています。forEach
内で変更しているのが問題なわけでもなく、実際次のコードはすんなり動きます。var hoges:[Fuga] = [Fuga(["a"]), Fuga(["a"]), Fuga(["a"])] hoges.forEach{ (hoge: Fuga) in hoge.items.append("b") }このエラーの原因は
protocol
に準拠した型がclass
かstruct
か分からないことです。struct
は基本的に不変なので、もしもHoge
に準拠した型がstruct
だった場合変更ができなくなってしまいます。ということで、対応には
protocol
にclass
の制約をかけてあげることです。protocol Hoge:class{ //ここが大事!!!! var items:[String]{get set} } class Fuga:Hoge{ var items: [String] init(_ items: [String]){ self.items = items } } class Piyo:Hoge{ var items: [String] init(_ items: [String]){ self.items = items } } var hoges:[Hoge] = [Fuga(["a"]), Piyo(["a"]), Fuga(["a"])] hoges.forEach{ (hoge: Hoge) in hoge.items.append("b") } print(hoges.map{$0.items})以上です。
- 投稿日:2020-09-20T16:07:19+09:00
[Swift] NSLogを安易に使ってしまうとセキュリティ的にマズい
前提環境
- Xcode 11.3.1
- Swift 5.1.3
ログ出力とセキュリティ
NSLog
は日時を出力してくれるので便利そうに見えますが、、、
端末をPC接続して、Apple Configurator 2などのツールを使うとコンソールに出力されてしまいます。
セキュリティ的に危うい状況です。
一方、
https://github.com/apple/swift-log
- 投稿日:2020-09-20T16:07:03+09:00
大規模なiOSアプリ開発を2年以上継続して得られた知見の共有
前書き
それなりに大規模なアプリの新規開発〜機能追加を行う中で得られた知見をまとめます。
アプリのジャンル/ライフサイクル、チームメンバーのスキルレベルなどによって適用できない事項もあるかと思いますが、一つの事例として参考にしていただければ幸いです。
<アプリ/チームの概要>
- iOS版開発チームとしては最小4人〜最大9人。
- その他、Androidチーム、バックエンドチーム、デザインチーム…などなど諸々合わせて総勢20人〜30人
- アプリの累計ダウンロード数は数百万。
- App Store上のカテゴリは「ファイナンス」。
- リリースサイクル(計画リリース)は平均で2ヶ月に1回。
- 開発環境はXcode + Swift。
- Scrumで開発。
本文・コメント含め、これ以上に具体的なことは書けませんのでご了承ください。
本記事の内容は内容はあくまで私個人の見解であり、所属企業における立場、戦略、意見を代表するものではありません。UI構築
ポリシー
- 一時期はStoryboardと、コードベース・レイアウトが混在していた時期もありましたが、現在ではコードベース・レイアウトのみに統一しました。
- 理由は以下のようなStoryboardのデメリットを感じることが多くなってきたためです。
- 人数が多いとStoryboardのconflictが多く、その解消がとにかく面倒臭い。
- Storyboardでは、画面修正のレビューが辛い。
- アプリ規模が大きくなるにつれ、Xcode上でStoryboardを開く時の待ち時間が長くなってしまった。
- デザイナーからのUI/UX要求レベルが上がってきて、画面の状態が複雑になってきた。
- 例えば、ローディングプレースホルダを表示しつつ裏でAPI通信>取得成功時は通常View、エラー時はエラー用の特殊なView、など。。。
- Storyboardでは静的な状態はプレビューできるが、上のような動的な状態変化が多いと意味がない。
- Storyboardでは、フォント、色など、アプリの統一ポリシーを適用し忘れることがしばしばある。
- ただし、Unwind Segueは便利なので、画面遷移はStoryboardを使っています。
- コードベース・レイアウトを容易にするために、自作ライブラリを構築しました(productionコードなので公開はできません)。
参考リンク:
iOSアプリケーションでコードベースのレイアウトを積極利用する
[Xcode] [Swift] 実務的Tips: Unwind Segueで2つ以上前の画面に一気に戻って画面を再更新する評価
- コードベース・レイアウト統一に至るまでに紆余曲折はありましたが、それが生産性・品質・保守性ともにベストな方法である、と評価しています。
- ただし、画面遷移のみStoryboardに依存している状態はあまり「見通し」がよくないので、改善の余地がありそうです。
アーキテクチャー・パターン
ポリシー
- 当初は標準的なCocoa MVCで作っていました。当然FatViewController化しました。
- アプリの規模が大きくなり、またエンハンスメントを重ねるごとに改修が困難になってきました。
- このため、MVVMへのリアーキテクチャーを進めました。
- 一気に行わず、新規機能や大規模改修に合わせて実施。
- ライブラリは使わず、設計パターンだけを適用。
- MVVMアーキテクチャーを選定した理由:
- Clean Architectureなどの複雑なアーキテクチャーよりも、MVVMへの変更の方が改修規模が小さかったため。
- メンバーのスキルレベルに差があり、MVVMより複雑なアーキテクチャーを全員が習熟することは困難であったため。
(具体的な実装例は追って別記事を投稿する予定です。)
評価
- 保守性・拡張性が上がったと評価しており、今後も継続する予定です。
ライブラリ管理
ポリシー
- ライブラリに起因するトラブルは結構多いので、ライブラリ導入は必要最小限に止めるようにしています。
- ライブラリ管理ツールとしてCarthageを使っています。
- CocoaPodsよりもビルドが早いのでメリットを感じて導入しました。
評価
- ライブラリに起因するトラブルは開発計画を大きく狂わせるので、今後も必要最小限に止める方針に変わりないと思います。
- Carthageについては、2020年9月現在でXcode 12でビルドエラーになるというクリティカルな問題が発生しているので、CocoaPodsに近日中に乗り換えるかもしれません。。。
- 大規模アプリにおいては、ライブラリ管理ツール、導入ライブラリ共に、その選定は慎重にならざるを得ないです。
コードレビュー
ポリシー
- IBM Cloud上にGitLabを導入しました。
- コードレビューは99.9%実施しています。
- GitLabのマージリクエスト上でレビューコメントをやりとりするスタイルです。
- レビューアーは原則として複数人としています。
- 一次レビューはメンバー
- 最終レビューはリーダー
- コードレビューの目的は以下の通りと宣言しており、その観点で気になったことがあればレビューコメントを書くようにしています。
- システムのメンテナンス性、リーダビリティ、理解のしやすさを改善すること。
- ブラックボックステストでは検出しづらい不具合(例:メモリーリーク)を発見すること。
- 知識を共有すること。
- 一応、コーディングガイドラインはありますが項目は最低限です。
- 「強制アンラップは理由がない限りは使わないようにしよう」「インデントは合わせよう」「Xcodeの設定を合わせよう」程度です。
- 私見ですが、ガチガチに縛ろうとして規約を増やしたところで、読むことが大変になるだけで、完璧に守ることは困難です。
- 規約で縛るよりも、上のような「目的」を共有することの方が大事では、と考えます。
参考リンク:
[Xcode] 実務的Tips: チーム内でXcodeの設定項目を合わせて不要な修正差分が出ないようにする評価
- 品質・保守性には大きな効果が認められ、コードレビューを行わないという選択肢は今後もありません。
- 以下の点は要改善ポイントです。
- リーダーの負荷が高いこと(レビューに追われて自分の担当分の実装時間が削られる)。
- リーダーがボトルネックになること。
- リーダーが不在だとマージできない。
- 先行タスクのマージリクエストがレビュー未完了だと後続作業が滞る。
テスト
ポリシー
- 1年ぐらい、XCUITestによるテストの自動化に取り組みました。→詳細は「参考リンク」の記事を参照
- しかしながら、機能改修時のメンテナンスコストがあまりにも大きかったです。
- コード修正は1時間でも、既存のテストの修正に半日以上かかることもザラ。。。
- このため、現在では以下のような手法を取っています。
- ロジック部分のテストはXCTest。
- UI部分のテストは実動作で確認。
- APIについては、Node.jsベースのスタブツール(ローカルサーバー)を自作し、正常系/異常系レスポンスのテストを行う。
- 以上は開発チームとしてのテストで、業務観点での総合テストは別途QAチームがテストシナリオに即して行う。
参考リンク:
XcodeのUIテストフレームワーク「XCUITest」のTips評価
- 私たちのプロダクトではUIの改変が多いため、XCUITestは費用対効果の観点で合いませんでした。
- MVVMパターンへのリアーキテクチャーとの「合わせ技」で、なるべくView/ViewControllerには画面表示以外のロジックは書かないようにし、ViewModelのXCTestを増やすように取り組んでいるところです。
CI/CD
ポリシー
- 以下の目的で、CI/CDを取り入れています。
- Sprintの途中であっても、マージされた機能はいち早くQAエンジニアに渡してテストしてもらいたい。
- あるいは、オーナーやデザイナーに動かしてもらい、フィードバックしてもらいたい。
- 現状は、JenkinsでGitLabのブランチを監視して、fastlaneをキックしてビルドを走らせ、TestFlightにアップロードしています。
- テストは自動で走らせるものの、失敗はSlackに自動postするだけで、デプロイは止めません。
- 緊急のバグ修正時などはテストコードの修正は後回しにするケースもあるので…
評価
- アプリの規模が大きくなるに応じてビルド時間が長くなるので、大規模アプリでは自動デプロイは必須です。
後書き
- DX時代が到来し、各企業とも顧客とのエンゲージメント向上のためにスマホアプリを活用する例は多く、ライバルに遅れを取らないためには高・多機能なアプリを早いサイクルでリリースすることが求められています。
- しかしながら、OSも開発環境も絶えず進化している中で、大規模なアプリを、迅速かつトラブルなくリリースし続けることは容易ではありません。
- それを成し遂げるのためには「完璧な手法をじっくり作り上げること」ではなく「早いサイクルで小さい改善を積み重ねること」と考えています。
- 投稿日:2020-09-20T16:07:03+09:00
大規模なiOSアプリ開発を2年以上継続して得られた知見
前書き
それなりに大規模なアプリの新規開発〜機能追加を行う中で得られた知見をまとめます。
アプリのジャンル/ライフサイクル、チームメンバーのスキルレベルなどによって適用できない事項もあるかと思いますが、一つの事例として参考にしていただければ幸いです。
<アプリ/チームの概要>
- iOS版開発チームとしては最小4人〜最大9人。
- その他、Androidチーム、バックエンドチーム、デザインチーム…などなど諸々合わせて総勢20人〜30人
- アプリの累計ダウンロード数は数百万。
- App Store上のカテゴリは「ファイナンス」。
- リリースサイクル(計画リリース)は平均で2ヶ月に1回。
- 開発環境はXcode + Swift。
- Scrumで開発。
本文・コメント含め、これ以上に具体的なことは書けませんのでご了承ください。
本記事の内容は内容はあくまで私個人の見解であり、所属企業における立場、戦略、意見を代表するものではありません。UI構築
ポリシー
- 一時期はStoryboardと、コードベース・レイアウトが混在していた時期もありましたが、現在ではコードベース・レイアウトのみに統一しました。
- 理由は以下のようなStoryboardのデメリットを感じることが多くなってきたためです。
- 人数が多いとStoryboardのconflictが多く、その解消がとにかく面倒臭い。
- Storyboardでは、画面修正のレビューが辛い。
- アプリ規模が大きくなるにつれ、Xcode上でStoryboardを開く時の待ち時間が長くなってしまった。
- デザイナーからのUI/UX要求レベルが上がってきて、画面の状態が複雑になってきた。
- 例えば、ローディングプレースホルダを表示しつつ裏でAPI通信>取得成功時は通常View、エラー時は特殊なView、など。。。
- Storyboardでは静的な状態はプレビューできるが、上のような動的な状態変化が多いと意味がない。
- Storyboardでは、フォント、色など、アプリの統一ポリシーを適用し忘れることがしばしばある。
- ただし、Unwind Segueは便利なので、画面遷移はStoryboardを使っています。
- コードベース・レイアウトを容易にするために、自作ライブラリを構築しました(productionコードなので公開はできません)。
参考リンク:
iOSアプリケーションでコードベースのレイアウトを積極利用する
[Xcode] [Swift] 実務的Tips: Unwind Segueで2つ以上前の画面に一気に戻って画面を再更新する評価
- コードベース・レイアウト統一に至るまでに紆余曲折はありましたが、私たちのアプリではコードベース・レイアウトが生産性・品質・保守性ともにベストな方法である、と評価しています。
- ただし、画面遷移のみStoryboardに依存している状態はあまり「見通し」がよくないので、改善の余地がありそうです。
アーキテクチャー・パターン
ポリシー
- 当初は標準的なCocoa MVCで作っていました。当然FatViewController化しました。
- アプリの規模が大きくなり、またエンハンスメントを重ねるごとに改修が困難になってきました。
- このため、MVVMへのリアーキテクチャーを進めました。
- 一気に行わず、新規機能や大規模改修に合わせて実施。
- ライブラリは使わず、設計パターンだけを適用。
- MVVMアーキテクチャーを選定した理由:
- Clean Architectureなどの複雑なアーキテクチャーよりも、MVVMへの変更の方が改修規模が小さかったため。
- メンバーのスキルレベルに差があり、MVVMより複雑なアーキテクチャーを全員が習熟することは困難であったため。
(具体的な実装例は追って別記事を投稿する予定です。)
評価
- 保守性・拡張性が上がったと評価しており、今後も継続する予定です。
ライブラリ管理
ポリシー
- ライブラリに起因するトラブルは結構多いので、ライブラリ導入は必要最小限に止めるようにしています。
- ライブラリ管理ツールとしてCarthageを使っています。
- CocoaPodsよりもビルドが早いのでメリットを感じて導入しました。
評価
- ライブラリに起因するトラブルは開発計画を大きく狂わせるので、今後も必要最小限に止める方針に変わりないと思います。
- Carthageについては、2020年9月現在でXcode 12でビルドエラーになるというクリティカルな問題が発生しているので、CocoaPodsに近日中に乗り換えるかもしれません。。。
- 大規模アプリにおいては、ライブラリ管理ツール、導入ライブラリ共に、その選定は慎重にならざるを得ないです。
コードレビュー
ポリシー
- IBM Cloud上にGitLabを導入しました。
- コードレビューは99.9%実施しています。
- GitLabのマージリクエスト上でレビューコメントをやりとりするスタイルです。
- レビューアーは原則として複数人としています。
- 一次レビューはメンバー
- 最終レビューはリーダー
- コードレビューの目的は以下の通りと宣言しており、その観点で気になったことがあればレビューコメントを書くようにしています。
- システムのメンテナンス性、リーダビリティ、理解のしやすさを改善すること。
- ブラックボックステストでは検出しづらい不具合(例:メモリーリーク)を発見すること。
- 知識を共有すること。
- 一応、コーディングガイドラインはありますが項目は最低限です。
- 「強制アンラップは理由がない限りは使わないようにしよう」「インデントは合わせよう」「Xcodeの設定を合わせよう」程度です。
- 私見ですが、ガチガチに縛ろうとして規約を増やしたところで、読むことが大変になるだけで、完璧に守ることは困難です。
- 規約で縛るよりも、上のような「目的」を共有することの方が大事では、と考えます。
参考リンク:
[Xcode] 実務的Tips: チーム内でXcodeの設定項目を合わせて不要な修正差分が出ないようにする評価
- 品質・保守性には大きな効果が認められ、コードレビューを行わないという選択肢は今後もありません。
- 以下の点は要改善ポイントです。
- リーダーの負荷が高いこと(レビューに追われて自分の担当分の実装時間が削られる)。
- リーダーがボトルネックになること。
- リーダーが不在だとマージできない。
- 先行タスクのマージリクエストがレビュー未完了だと後続作業が滞る。
テスト
ポリシー
- 1年ぐらい、XCUITestによるテストの自動化に取り組みました。→詳細は「参考リンク」の記事を参照
- しかしながら、機能改修時のメンテナンスコストがあまりにも大きかったです。
- コード修正は1時間でも、既存のテストの修正に半日以上かかることもザラ。。。
- このため、現在では以下のような手法を取っています。
- ロジック部分のテストはXCTest。
- UI部分のテストは実動作で確認。
- APIについては、Node.jsベースのスタブツール(ローカルサーバー)を自作し、正常系/異常系レスポンスのテストを行う。
- 以上は開発チームとしてのテストで、業務観点での総合テストは別途QAチームがテストシナリオに即して行う。
参考リンク:
XcodeのUIテストフレームワーク「XCUITest」のTips評価
- 私たちのプロダクトではUIの改変が多いため、XCUITestは費用対効果の観点で合いませんでした。
- MVVMパターンへのリアーキテクチャーとの「合わせ技」で、なるべくView/ViewControllerには画面表示以外のロジックは書かないようにし、ViewModelのXCTestを増やすように取り組んでいるところです。
CI/CD
ポリシー
- 以下の目的で、CI/CDを取り入れています。
- Sprintの途中であっても、マージされた機能はいち早くQAエンジニアに渡してテストしてもらいたい。
- あるいは、オーナーやデザイナーに動かしてもらい、フィードバックしてもらいたい。
- 現状は、JenkinsでGitLabのブランチを監視して、fastlaneをキックしてビルドを走らせ、TestFlightにアップロードしています。
- テストは自動で走らせるものの、失敗はSlackに自動postするだけで、デプロイは止めません。
- 緊急のバグ修正時などはテストコードの修正は後回しにするケースもあるので…
評価
- アプリの規模が大きくなるに応じてビルド時間が長くなるので、大規模アプリでは自動デプロイは必須です。
後書き
- OSも開発環境も絶えず進化している中で、大規模なアプリを、迅速かつトラブルなくリリースし続けることは容易ではない、と感じます。
- 「早いサイクルで小さい改善を積み重ねる」をモットーに、まだまだ模索を続けて行こうと考えています。
- 投稿日:2020-09-20T14:27:47+09:00
ARCoachingOverlayViewでユーザーにデバイスをうごかしてもらって、ARKitに世界情報をあたえる
ARKitがWorldTrackingを確立するには、デバイスを動かして、ARKitに視点を与える必要があります。
ARCoachingOverlayViewでユーザーにガイダンスを出して、デバイスをうごかしてもらいます。
1、ARCoachingOverlayViewを初期化します。
let coachingOverlay = ARCoachingOverlayView()ARCoachingOverlayViewの設定
coachingOverlay.goal = .anyPlane //情報を取る目標設定 // 可能な値 case anyPlane case horizontalPlane case tracking case verticalPlane coachingOverlay.activatesAutomatically = true //Tracking状態が不十分になると、再度コーチングするか // セッションとデリゲートを設定 coachingOverlay.session = sceneView.session coachingOverlay.delegate = self // Viewとして扱う coachingOverlay.frame = sceneView.bounds sceneView.addSubview(coachingOverlay)2、ARCoachingOverlayViewDelegateを設定
コーチングの状態によって呼ばれるDelegate functionは以下
func coachingOverlayViewWillActivate(_ coachingOverlayView: ARCoachingOverlayView) { // コーチングが表示される前に呼ばれる。ここでコーチング中操作できないButtonなどを非表示にする。 } func coachingOverlayViewDidDeactivate(_ coachingOverlayView: ARCoachingOverlayView) { // コーチングが終了したあとに呼ばれる。ここでコーチング中操作できないButtonなどを再表示する。 }これでARSessionをrunすると動くはずです。
必要な情報が得られたら、コーチングは消えます。
このステップをやったほうが、正確なワールドトラッキングが得られます。
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-20T11:17:08+09:00
XCTSkipの使い方まとめ
はじめに
Xcode11.4からXCTestフレームワークに新しく導入されたプロパティの一つに「XCTSkip」があります。
XCTSkipを使用することで明示的に「スキップ」の結果を出力することができます。
特に統合テストにおいては、必要条件や依存関係が必ずと言っていいほどあるかと思います。
例えばアプリケーションがiPad特有の機能を持っている場合はiPhoneではテストできませんし古いOSでは利用できないAPIを用いる場合テスト対象は必然と絞られます。そんな条件付きの実行が必要なテストを行う際に「XCTSkip」が活躍します。
中の実装は「こちら」をご参照ください。
なぜ「スキップ」があると良いのか?
以前まで(Xcode11.4より前)は下記の2択しかありませんでした。
- テストを合格にする
- テストを不合格にする
もし一部の条件下では必ずテストが成功するようにしていた場合、検証に合格していないプログラムが機能するということになりますし、テスト失敗とした場合は何が悪いのかの判断ができず無駄に時間を消費してしまいます。
XCTSkipを利用することでResult Bundlesを見た際にテスト結果のより詳細な情報がわかるようになりテスト全体の把握が容易になります。
Xcode11よりResult Bundlesへのデータの集約方法も変更されたのですが、そこら辺の詳細に関しては別の機会に記事を書きたいと思います。
使い方
使い方を紹介するために簡単なサンプルを作成しました。
テキストを音声で読み上げるとてもシンプルなものです(あまり良い例が思い付かず優しい目で見てください)import AVFoundation class Speech { private var nonMaleJPVoices: [AVSpeechSynthesisVoice] { return AVSpeechSynthesisVoice.speechVoices() .filter { $0.language == "ja-JP" && $0.gender != .male } } private func letsSpeaking() { let synthesizer = AVSpeechSynthesizer() let utterance = AVSpeechUtterance(string:"こんにちは") utterance.voice = nonMaleJPVoices.first synthesizer.speak(utterance) } }
letsSpeaking()
をコールすると「こんにちは」と音声が発せられます。
nonMaleJPVoices
では使用可能な音声オブジェクトのうち「言語が日本語」かつ「男性以外」の音声情報を渡す実装になっています。今回は
nonMaleJPVoices
の正当性をテストしてみたいと思います。
nonMaleJPVoices
の要件は以下の2つです。
- 言語が日本語
- 男性以外
実際に書いたテストは下記です。
class SpeechTests: XCTestCase { func testNonMaleJPVoicesContainOnlyJapaneseLanguage() throws { XCTAssertFalse(Speech().nonMaleJPVoices.contains{ $0.language != "ja-JP" }, "nonMaleJPVoices must not contain any language other than Japanese.") } func testNonMaleJPVoicesContainOnlyNonMaleGender() throws { XCTAssertFalse(Speech().nonMaleJPVoices.contains{ $0.gender == .male }, "nonMaleJPVoices must not contain a male voice.") } }上から順に
nonMaleJPVoices
は言語が日本語の要素を持つAVSpeechSynthesisVoice
の配列であることnonMaleJPVoices
は音声の性別が男性以外の要素を持つAVSpeechSynthesisVoice
の配列であることを確かめるためのテストです。
良さそうに見えるのですが、一つ注意が必要です。
AVSpeechSynthesisVoiceGender
はiOS13より追加されたプロパティなのでそれより前のOSバージョンでは取得ができません。
iOS12ではgender
へのアクセスができないのです。その場合テストはどうなるでしょう?
まず、機能の実装にはOSバージョンの条件分岐が入ります。
internal var nonMaleJPVoices: [AVSpeechSynthesisVoice] { if #available(iOS 13.0, *) { return AVSpeechSynthesisVoice.speechVoices() .filter { $0.language == "ja-JP" && $0.gender != .male } } return AVSpeechSynthesisVoice.speechVoices() .filter { $0.language == "ja-JP" } }すると
男性以外
という条件を含むのはiOS13以上
となります。先ほどのテストに戻りますがこのままではテストとしては不十分なので修正をする必要があります。
class SpeechTests: XCTestCase { /// 修正が必要 func testNonMaleJPVoicesContainOnlyNonMaleGender() throws { XCTAssertFalse(Speech().nonMaleJPVoices.contains{ $0.gender == .male }, "nonMaleJPVoices must not contain a male voice.") } }どのように修正するのがよいでしょうか?
iOS13未満では必ずテストが成功するようにしますか?ここで活躍するのがXCTSkipです。
性別に関してテストすべきなのは
iOS13以上の時だけ
でありそれより下のOSではテスト不要なのでスキップするのが適切です。早速修正しましょう!
class SpeechTests: XCTestCase { func testNonMaleJPVoicesContainOnlyNonMaleGender() throws { guard #available(iOS 13.0, *) else { throw XCTSkip("AVSpeechSynthesisVoiceGender tests can only run on iOS 13.0+") } XCTAssertFalse(Speech().nonMaleJPVoices.contains{ $0.gender == .male }, "nonMaleJPVoices must not contain a male voice.") } }実際にiOS12系の端末でテストを実行してみるとこうなります。
何やら見慣れないマークが表示されていますがこれがスキップしたことを示すマークです。Report Navigatorよりスキップの詳細を見ることができます。
使い方はとても簡単ですね...!
スキップの種類
XCTSkipを開始する関数は2種類あります。
XCTSkipIf(::file:line:)
条件式が真の場合にスキップします。
func testExample() throws try XCTSkipIf(UIDevice.current.userInterfaceIdiom == .pad, "this tests are not for iPad only") // test for other than iPad ... }XCTSkipUnless(::file:line:)
条件式が偽の場合にスキップします。
func testExample() throws try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad, "this tests are for iPad only") // test for iPad only... }XCTSkip
先に関数は2種類と説明しましたが、XCTSkip構造体を直接投げることも可能です。
その場合はガードと組み合わせる使い方がおすすめでWWDC20の「XCTSkip your tests」のセッションでも同じように紹介されていました。
func testNonMaleJPVoicesContainOnlyJapaneseLanguage() throws { guard #available(iOS 13.0, *) else { throw XCTSkip("this tests can only run on iOS 13.0+") } // test for iOS13+ ... }まとめ
テストを書いていく中で特定の条件下では実行できないテストというのが必ずあると思います(特に統合テスト)
そんなときにXCTSkipを使うことでテストの実行結果をより正確にモデル化することが可能となります。
テストの結果についてより具体的に詳細を把握するためにもぜひスキップしてみてはいかがでしょうか!!
- 投稿日:2020-09-20T10:14:01+09:00
Int型を拡張して3の倍数か判定するメソッドを作成する
この記事について
この記事は「Swiftである程度のことはできるようになったけど、
extension
はコピペまたはUITableView
くらいでしか使ったことない〜。けど可読性も上がるし拡張が使えるとかっこいいよな!」という人が書いています。例として3の倍数か判定するメソッドを作成していますが、型の拡張を使いこなせることを目標としています。ご意見やアドバイス等があればコメントよろしくお願いします。extension(拡張)とは
extensionとは既に存在するクラス,構造体,列挙型およびプロトコルに新しいメソッドやコンピューテッドプロパティを追加するできる機能のことです。Xcodeでは可読性を上げるためにクラスを拡張していることがあります。
ソースコード
extension Int { var isMultipleOf3: Bool { if self%3 == 0 { return true } else { return false } } } 11.isMultipleOf3 //false 12.isMultipleOf3 //trueBool型を返すコンビューテッドプロパティとして
isMultipleOf3
を定義しています。
- 投稿日:2020-09-20T08:29:39+09:00
【Swift】循環参照 丸わかり?
循環参照
循環参照とは?
お互いのインスタンスを参照しあい、メモリが解放されない現象。
「解放」...もうここ使わないから、あとはご自由にどうぞ、というイメージ。?なぜNG?
メモリ リーク
を引き起こす。
最終的にメモリが足りなくなり、アプリが落ちてしまう。メモリ リーク
メモリの解放忘れが原因で、永久にメモリが消費され続けること。
再現性が低く 見つけるのが困難で、悪質なバグの一つ。循環参照 を 防ぐには?
弱参照
をする。
weak
とunowned
を使う。(-> unknowned ではない...!)unowned: 所有されていない
unknown: 知られていない、不明な
unknowned -> こんな言葉ないなぜ防げるのか
弱参照を行うと、参照カウントは変わらないため。
weak
は「このインスタンスへの参照は、カウントしないよ」
という制限を、メモリ管理機能(= Automatic Reference Counting) に伝える。参照カウント
「プロパティや変数、定数からの インスタンスへの参照」が いくつあるかをカウントするもの。
参照の種類
- 強参照:
strong
-> デフォルト- 弱参照:
weak
- 非所有参照:
unowned
強参照
されたインスタンスは、参照カウントが 1 増やされます。
弱参照
・非所有参照
を行うと、参照カウントは変わりません。weak と unowned の違い
インスタンスが破棄された場合、弱参照(= weak) 元には
nil
が代入されます。非所有参照(= unowned) では、変数に
nil
が代入されません。
( -> インスタンス解放後に変数にアクセスすると エラー)改めまして、「循環参照」とは??
循環参照とは、インスタンス間でお互いを強参照しあった場合に参照カウントが0にならず、メモリ上にインスタンスが残り続けてしまう状態のこと。
永久にメモリが消費され続ける、「メモリリーク」という現象を引き起こす恐れがある。
それを防ぐために、
weak
やunowned
を使用して弱参照
を行う。おしまい。
- 投稿日:2020-09-20T01:57:34+09:00
【Swift】AudioKitを使ってみた(2020.9)
はじめに
【2020.9.21】 Oscillators and the Physics of Sound に書き漏れがあったので追記しました。
サウンドファイルの再生じゃなくて、音そのものを発信(発振?)させるのってどうすればいいんだろうとググっていて、AudioKitというライブラリを見つけました。
で、AudioKit Tutorial: Getting Startedというチュートリアルが、順を追って丁寧に解説されてるので動かしてみました。
ただページそのものがかなり古い(2016/バージョンは3.6)ので、公式ページにある最新版のAudioKit Playgrounds (zip)(バージョンは4.9.5)で動かしてみたときの変更点を備忘録としてまとめておきます。DLはこちらから。
実行環境
- Xcode 11.6
- macOS Catalina(10.15.6)
- 公式ページのAudioKit Playgrounds (zip)(バージョンは4.9.5)
Getting Started
まず AudioKit Playgrounds (zip)は最新版を使うので、チュートリアルページのリンクではなく、公式サイトのDLページから入手します。ファイル名は「AudioKitPlaygrounds-4.9.5.zip」になります。
これを解凍してXcodeで開いた後に・・・
Click the + button in the bottom left-hand corner of the Navigator view. Select the New Playground… option, name it Journey, and save it in the Playgrounds folder the other playground files are stored in.
ナビゲータービューの左下隅にある+ボタンをクリックします。 [新しいプレイグラウンド…]オプションを選択し、「ジャーニー」という名前を付けて、他のプレイグラウンドファイルが格納されているプレイグラウンドフォルダに保存します。
と書いてありますが、ナビゲータービューの左下隅にある+ボタンでは「New Playground…」なんて出てこないので、ナビゲータービュー上で右クリックすると「New Playground Page」が出てきますのでここから追加できます。
Xcodeのバージョンの問題かしらん?Playgroundってあんまり使ってないんで。。。さらに以下のような注釈がありますが・・・
This project will fail unless you build the project at least once using Product / Build, or ⌘-B. Once built, run the playground again and you’ll hear your playground emit 10 seconds of a beeping sound. You can use the Play/Stop button at the bottom left of the playground window within the Debug Area to stop or repeat the playground.
このプロジェクトは、Product / Buildまたは⌘-Bを使用してプロジェクトを少なくとも1回ビルドしない限り失敗します。 作成したら、遊び場をもう一度実行すると、遊び場から10秒間のビープ音が聞こえます。 デバッグ領域内のプレイグラウンドウィンドウの左下にある[再生/停止]ボタンを使用して、プレイグラウンドを停止または繰り返します。
Note: If the playground fails to execute, and you see errors in the Debug Area, try restarting Xcode. Unfortunately, using playgrounds in combination with frameworks can be a little error-prone and unpredictable. :[
注:プレイグラウンドの実行に失敗し、デバッグ領域にエラーが表示された場合は、Xcodeを再起動してみてください。 残念ながら、フレームワークと組み合わせてプレイグラウンドを使用すると、エラーが発生しやすくなり、予測できなくなります。 :[
私の環境でも結構出たので、新規のPlaygoundファイルを作ったら「⌘-B」を押し、実行してエラーが出たときは、Xcodeを再起動したり、AudioKitPlaygrounds.xcodeprojに既に作成済みのPlaygroudファイルを実行してから、再度自分で書いたPlaygroundを実行したりしてました。
Oscillators and the Physics of Sound
このコードをそのまま実行すると、以下のエラーが出ます。
error: 02Oscillators.xcplaygroundpage:21:1: error: use of unresolved identifier 'AKPlaygroundLoop' AKPlaygroundLoop(every: 0.5) { ^~~~~~~~~~~~~~~~最新バージョンでは、AudioKitとAudioKitUI(おそらくUI系)と二つのフレームワークに分割されていて、'AKPlaygroundLoop'は AudioKitUI にあるので、
import AudioKitUIを追加すればOKです。以降、'AKPlaygroundLoop'が出てくる箇所ではimportを忘れずに。
【参考】AudioKit/AudioKit/iOS/AudioKit/User Interface/AKPlaygroundLoop.swift
【2020.9.21追記】
rampTimeもエラーになります。
error: 02Oscillators.xcplaygroundpage:18:12: error: value of type 'AKOscillator' has no member 'rampTime' oscillator.rampTime = 0.2 ~~~~~~~~~~ ^~~~~~~~これは rampDuration に変更されていうようなので、以下のように変更します。
//---rampTimeは、rampDurationに変更 oscillator.rampDuration = 0.2【参考】AudioKit Reference AKOscillator Class Reference
Sound Envelopes
import AudioKitUIを追加すればOKです。
Additive Sound Synthesis
1.まず以下を追加。
import AudioKitUI2.Live ViewにUIを表示するので、Live Viewを表示する必要があります。「Alt+Cmd+Return」で表示/非表示を切り替えます。
【参考】How to create live playgrounds in Xcode
- AKPlaygroundView クラスを使ってUIを作成していますが、これがエラーになって動かない。
AudioKitPlaygrounds.xcodeprojに既存の他のファイルを見てみると、AKLiveViewController を使っているようなので、こちらで書き換えました。クラス名を含めた変更点は3つ。
・AKPlaygroundView >> AKLiveViewController
・override func setup() >> override func viewDidLoad()
・addSubview() >> addView()GitHubで、AudioKit/AudioKit/iOS/AudioKit/User Interface/を見ると、どちらのクラスも存在しているので AKPlaygroundView で何故ダメなのかは不明。
4.Live View にUIが表示されるようになるが、音が鳴らない。
オシレーターを生成するための「createAndStartOscillator()」という関数があって、サンプルコードでは生成時に「oscillator.start()」を実行しているけど、「AudioKit.start()」の後に実行しないと音が鳴らないようです。
なので、createAndStartOscillator()にある「oscillator.start()」をコメントにして、「AudioKit.start()」のあとに以下のコードを追加すればOK。
//AKOscillator.start()は、AudioKit.start()の後でないと音が鳴らない oscillators.forEach { $0.start() }Polyphony
1.まず以下を追加。
import AudioKitUI2.AKPlaygroundView を AKLiveViewController に変更。Additive Sound Synthesis の項を参照。
Sampling
コードの変更点は、特になし。ただサンプルコードで使っている.wavファイルは、自分でDL(リンクあり)して該当Playground配下の「Resources」フォルダに入れておく必要があります。
まとめ
AudoKit、面白そうだけど、入門的な記事は古いものしか見当たらなかったので、まとめてみました。参考になれば幸いです。自分で書いたコードではないので、あえてコード全文は載せていません。
他にいい参考記事あったら教えてください。
シンセサイザーの音声合成的な知識が必要なので使いこなすにはハードル高そうですが、画面のキャラクタタップしたら「どう森」みたいにインチキ言語を喋るとかさせたら面白いかも(笑)。