- 投稿日:2022-02-28T23:40:42+09:00
エラーが出て、 CocoaPods をインストールできない。「You don't have write permissions for the /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0 directory.」
1. やりたいことを確認する。 CocoaPods をインストールしたい。 CocoaPodsとは、定番のCocoaの依存管理マネージャーです。アプリ開発で煩雑となりがちな外部ライブラリの管理を、簡単な記述とコマンドだけ支援するものです。 こちらの記事を参考に、 CocoaPods をインストールしようとしているところ。 2. 課題(エラーの状況、不明点、エラーログ)を確認する。 ⚠️ cocoapods をインストールできない。 $ sudo gem install cocoapods Password: Fetching rexml-3.2.5.gem Fetching colored2-3.1.2.gem Fetching claide-1.1.0.gem Fetching atomos-0.1.3.gem Fetching ruby-macho-2.5.1.gem Fetching nanaimo-0.3.0.gem Fetching xcodeproj-1.21.0.gem Fetching nap-1.1.0.gem Fetching molinillo-0.8.0.gem Fetching gh_inspector-1.1.3.gem Fetching fourflusher-2.3.1.gem Fetching escape-0.0.4.gem Fetching cocoapods-try-1.2.0.gem Fetching netrc-0.11.0.gem Fetching cocoapods-trunk-1.6.0.gem Fetching cocoapods-search-1.0.1.gem Fetching cocoapods-plugins-1.0.0.gem Fetching cocoapods-downloader-1.5.1.gem Fetching cocoapods-deintegrate-1.0.5.gem Fetching ffi-1.15.5.gem Fetching ethon-0.15.0.gem Fetching typhoeus-1.4.0.gem Fetching public_suffix-4.0.6.gem Fetching fuzzy_match-2.0.4.gem Fetching concurrent-ruby-1.1.9.gem Fetching json-2.6.1.gem Fetching httpclient-2.8.3.gem Fetching algoliasearch-1.27.5.gem Fetching addressable-2.8.0.gem Fetching zeitwerk-2.5.4.gem Fetching tzinfo-2.0.4.gem Fetching minitest-5.15.0.gem Fetching cocoapods-1.11.2.gem Fetching i18n-1.10.0.gem Fetching activesupport-6.1.4.6.gem Fetching cocoapods-core-1.11.2.gem ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0 directory. You don't have write permissions(ディレクトリに対する書き込み権限がありません)というエラーが出て、CocoaPods をインストールできない。 ⚠️ なので当然、次のステップの pod コマンドを実行できない。 $ pod setup zsh: command not found: pod 3. 調査する(ググる、ドキュメントを読む) 調査① $ gem install foreman Fetching: thor-0.19.4.gem (100%) ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory. エラーログをみるとYou don't have write permissionsと書いてあります。 上記のようなエラーの原因ですが、結論からいうと、システムのrubyを利用しているため、権限不足でgemのインストールができない可能性が高いです。 ルート権限でシステムのRubyにインストールをしてもいいのですが、開発環境ではrbenvでrubyを管理することをおすすめします。 システムのrubyを利用しているため、権限不足でgemのインストールができない可能性が高いらしい。 → 上記の記事を参考にし、エラーに対して対策する。 もうちょっと調べておく。 調査② MacOS標準のrubyは管理者領域(/System/Library)にあり使いにくいです。homebrew + rbenv + rubyで、一般ユーザの領域にrubyの環境を作ることをお勧めします。 $ rbenv versions system 2.5.9 2.6.9 2.7.5 * 3.0.3 (set by /Users/xxxxxxxx/.rbenv/version) $ which ruby /Users/xxxxxxxx/.rbenv/shims/ruby $ ruby -v ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-darwin20] $ gem install cocoapods $ pod --version 1.11.2 homebrew + rbenv + rubyで、一般ユーザの領域にrubyの環境を作る。 調査③ /bin/zshの場合こちらのコマンドを打ち込んでください echo 'eval "$(rbenv init -)"' >> ~/.zshrc source ~/.zshrc source コマンドを実行するのを忘れないようにしよう。 4. 調査結果からの考察、対策を実行する 下記の記事を参考にし、エラーに対して対策する。 ??? gem や ruby が、どのパスを指しているのか、現状の確認をする。 $ which gem /usr/bin/gem $ which ruby /usr/bin/ruby ??? rbenv のインストールをする。 $ brew update $ brew install rbenv ruby-build ??? rbenv で管理されている ruby のバージョンを確認する。 $ rbenv versions * system ??? インストールできる ruby のバージョンを以下のコマンドで確認する。 $ rbenv install -l 2.6.9 2.7.5 3.0.3 3.1.1 jruby-9.3.3.0 mruby-3.0.0 rbx-5.0 truffleruby-22.0.0.2 truffleruby+graalvm-22.0.0.2 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions. ??? 最新の Ruby 安定版を確認し、インストールする。 現在の安定版は 3.1.1 です。 $ rbenv install 3.1.1 ??? インストール後もう一度以下のコマンドを実行する。 $ rbenv versions * system 3.1.1 ??? 3.1.1 を global で利用するように変更します。 $ rbenv global 3.1.1 ??? 3.1.1 に変更していることを確認する。 $ rbenv versions system * 3.1.1 (set by /Users/ryamate/.rbenv/version) ??? .zshrc を編集する。(追記する) $ vim ~/.zshrc # rbenv にパスを通す [[ -d ~/.rbenv ]] && \ export PATH=${HOME}/.rbenv/bin:${PATH} && \ eval "$(rbenv init -)" ??? source コマンドを実行する。 source ~/.zshrc 「source」コマンドは、ファイルに書かれたコマンドを現在のシェルで実行する、というコマンドです。 ??? cocoapods をインストールする。(成功!) $ sudo gem install cocoapods Password: Fetching nanaimo-0.3.0.gem Fetching colored2-3.1.2.gem Fetching claide-1.1.0.gem Fetching CFPropertyList-3.0.5.gem Fetching atomos-0.1.3.gem Fetching ruby-macho-2.5.1.gem Fetching xcodeproj-1.21.0.gem Fetching nap-1.1.0.gem Fetching molinillo-0.8.0.gem Fetching gh_inspector-1.1.3.gem Fetching fourflusher-2.3.1.gem Fetching escape-0.0.4.gem Fetching cocoapods-try-1.2.0.gem Fetching netrc-0.11.0.gem Fetching cocoapods-trunk-1.6.0.gem Fetching cocoapods-search-1.0.1.gem Fetching cocoapods-plugins-1.0.0.gem Fetching cocoapods-downloader-1.5.1.gem Fetching cocoapods-deintegrate-1.0.5.gem Fetching ffi-1.15.5.gem Fetching ethon-0.15.0.gem Fetching typhoeus-1.4.0.gem Fetching public_suffix-4.0.6.gem Fetching fuzzy_match-2.0.4.gem Fetching concurrent-ruby-1.1.9.gem Fetching httpclient-2.8.3.gem Fetching algoliasearch-1.27.5.gem Fetching addressable-2.8.0.gem Fetching zeitwerk-2.5.4.gem Fetching tzinfo-2.0.4.gem Fetching i18n-1.10.0.gem Fetching activesupport-6.1.4.6.gem Fetching cocoapods-1.11.2.gem Fetching cocoapods-core-1.11.2.gem Successfully installed nanaimo-0.3.0 Successfully installed colored2-3.1.2 Successfully installed claide-1.1.0 Successfully installed CFPropertyList-3.0.5 Successfully installed atomos-0.1.3 Successfully installed xcodeproj-1.21.0 Successfully installed ruby-macho-2.5.1 Successfully installed nap-1.1.0 Successfully installed molinillo-0.8.0 Successfully installed gh_inspector-1.1.3 Successfully installed fourflusher-2.3.1 Successfully installed escape-0.0.4 Successfully installed cocoapods-try-1.2.0 Successfully installed netrc-0.11.0 Successfully installed cocoapods-trunk-1.6.0 Successfully installed cocoapods-search-1.0.1 Successfully installed cocoapods-plugins-1.0.0 Successfully installed cocoapods-downloader-1.5.1 Successfully installed cocoapods-deintegrate-1.0.5 Building native extensions. This could take a while... Successfully installed ffi-1.15.5 Successfully installed ethon-0.15.0 Successfully installed typhoeus-1.4.0 Successfully installed public_suffix-4.0.6 Successfully installed fuzzy_match-2.0.4 Successfully installed concurrent-ruby-1.1.9 Successfully installed httpclient-2.8.3 A new major version is available for Algolia! Please now use the https://rubygems.org/gems/algolia gem to get the latest features. Successfully installed algoliasearch-1.27.5 Successfully installed addressable-2.8.0 Successfully installed zeitwerk-2.5.4 Successfully installed tzinfo-2.0.4 Successfully installed i18n-1.10.0 Successfully installed activesupport-6.1.4.6 Successfully installed cocoapods-core-1.11.2 Successfully installed cocoapods-1.11.2 Parsing documentation for nanaimo-0.3.0 Installing ri documentation for nanaimo-0.3.0 Parsing documentation for colored2-3.1.2 Installing ri documentation for colored2-3.1.2 Parsing documentation for claide-1.1.0 Installing ri documentation for claide-1.1.0 Parsing documentation for CFPropertyList-3.0.5 Installing ri documentation for CFPropertyList-3.0.5 Parsing documentation for atomos-0.1.3 Installing ri documentation for atomos-0.1.3 Parsing documentation for xcodeproj-1.21.0 Installing ri documentation for xcodeproj-1.21.0 Parsing documentation for ruby-macho-2.5.1 Installing ri documentation for ruby-macho-2.5.1 Parsing documentation for nap-1.1.0 Installing ri documentation for nap-1.1.0 Parsing documentation for molinillo-0.8.0 Installing ri documentation for molinillo-0.8.0 Parsing documentation for gh_inspector-1.1.3 Installing ri documentation for gh_inspector-1.1.3 Parsing documentation for fourflusher-2.3.1 Installing ri documentation for fourflusher-2.3.1 Parsing documentation for escape-0.0.4 Installing ri documentation for escape-0.0.4 Parsing documentation for cocoapods-try-1.2.0 Installing ri documentation for cocoapods-try-1.2.0 Parsing documentation for netrc-0.11.0 Installing ri documentation for netrc-0.11.0 Parsing documentation for cocoapods-trunk-1.6.0 Installing ri documentation for cocoapods-trunk-1.6.0 Parsing documentation for cocoapods-search-1.0.1 Installing ri documentation for cocoapods-search-1.0.1 Parsing documentation for cocoapods-plugins-1.0.0 Installing ri documentation for cocoapods-plugins-1.0.0 Parsing documentation for cocoapods-downloader-1.5.1 Installing ri documentation for cocoapods-downloader-1.5.1 Parsing documentation for cocoapods-deintegrate-1.0.5 Installing ri documentation for cocoapods-deintegrate-1.0.5 Parsing documentation for ffi-1.15.5 Installing ri documentation for ffi-1.15.5 Parsing documentation for ethon-0.15.0 Installing ri documentation for ethon-0.15.0 Parsing documentation for typhoeus-1.4.0 Installing ri documentation for typhoeus-1.4.0 Parsing documentation for public_suffix-4.0.6 Installing ri documentation for public_suffix-4.0.6 Parsing documentation for fuzzy_match-2.0.4 Installing ri documentation for fuzzy_match-2.0.4 Parsing documentation for concurrent-ruby-1.1.9 Installing ri documentation for concurrent-ruby-1.1.9 Parsing documentation for httpclient-2.8.3 Installing ri documentation for httpclient-2.8.3 Parsing documentation for algoliasearch-1.27.5 Installing ri documentation for algoliasearch-1.27.5 Parsing documentation for addressable-2.8.0 Installing ri documentation for addressable-2.8.0 Parsing documentation for zeitwerk-2.5.4 Installing ri documentation for zeitwerk-2.5.4 Parsing documentation for tzinfo-2.0.4 Installing ri documentation for tzinfo-2.0.4 Parsing documentation for i18n-1.10.0 Installing ri documentation for i18n-1.10.0 Parsing documentation for activesupport-6.1.4.6 Installing ri documentation for activesupport-6.1.4.6 Parsing documentation for cocoapods-core-1.11.2 Installing ri documentation for cocoapods-core-1.11.2 Parsing documentation for cocoapods-1.11.2 Installing ri documentation for cocoapods-1.11.2 Done installing documentation for nanaimo, colored2, claide, CFPropertyList, atomos, xcodeproj, ruby-macho, nap, molinillo, gh_inspector, fourflusher, escape, cocoapods-try, netrc, cocoapods-trunk, cocoapods-search, cocoapods-plugins, cocoapods-downloader, cocoapods-deintegrate, ffi, ethon, typhoeus, public_suffix, fuzzy_match, concurrent-ruby, httpclient, algoliasearch, addressable, zeitwerk, tzinfo, i18n, activesupport, cocoapods-core, cocoapods after 28 seconds 34 gems installed ??? その他確認 $ which ruby /Users/ryamate/.rbenv/shims/ruby $ ruby -v ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-darwin21] $ pod --version 1.11.2
- 投稿日:2022-02-28T16:33:46+09:00
[Swift]Delegateについて 初級編
この記事を書いた理由 Swiftを勉強し始めて、ずっと分かんなかったよ。 難しい。難しいよDelegateの理解。 というわけで、もしかしたら他にも同じ方いらっしゃるのではないかと思いDelegateについて備忘録的に私の理解を記載したいと思います。 Delegateは色々なされ方がありますが、まずは初級として概念的に理解していただければと思います。 まだ勉強中なので間違えていましたらご指摘いただけますと幸いです。 目次 1.Delegateとは? 2.どんな時にDelegateは必要なの? 3.メンドクサイから処理の一部を、他にやってもらう 4.エラーが出てしまう理由 5.protocolを使用する 6.まとめ Delegateとは? 結論から言うと、Delegateとは 「あるクラスから他のクラスに処理を任せる」 というデザインパターンのことです。 かみ砕くと、 「自身で行う処理の一部を、他にやってもらう」こと。 この一連の流れ をDelegateと言います。 まだ分かりにくいですよね・・・ 具体的に読みほどいていきましょう。 どんな時にDelegateは必要なの? 今回は簡単な計算結果をプリントする処理を見てみましょう。 まずは、以下のコードを理解してください。 コードの解説(折りたたみ) 下記は足し算結果を出す+結果をプリントするという2つの処理をするクラスを作成しています。 func printResultで、num1、num2の型を指定し、足すことを記載しています。 そしてprintを使用して、結果をプリントするようにしています。 ※AddはAddition(足し算)のことです。 足し算 class AddResultPrinter { func printResult(num1: Int, num2: Int) { let result = num1 + num2 print("\(num1)と\(num2)を足し算した結果は\(result)です。) } } AddResultPrinter().printResult(num1: 5, num2: 3) //「5と3を足し算した結果は8です。」とプリントされます。 計算は足し算だけではないので以下の引き算も見てみましょう。 コードの解説(折りたたみ) func printResultで、num1、num2の型を指定し、引くことを記載しています。 そしてprintを使用して、結果をプリントするようにしています。 ※SubはSubtration(引き算)のことです。 引き算 class SubResultPrinter { func printResult(num1: Int, num2: Int) { let result = num1 - num2 print("\(num1)と\(num2)を引き算した結果は\(result)です。) } } SubResultPrinter().printResult(num1: 5, num2: 3) //「5と3を引き算した結果は2です。」とプリントされます。 足し算、引き算見比べると2つしか違いはありません。 このとき、他の計算(掛け算/割り算)を追加する場合も同じようなコードをつらつら書かなければいけません。 別にこのまま書いても大丈夫です。 ただし、もっと簡単に書けないものか・・・ この時初めて「処理の一部を、他にやってもらう」ことはできないかという発想になります。 ーーーーーーーーーーーーーーーーーーーーーーーーーーー 欲張りなあなたへ⇩ 掛け算(折りたたみ) class MulResultPrinter { func printResult(num1: Int, num2: Int) { let result = num1 * num2 print("\(num1)と\(num2)を掛け算した結果は\(result)です。) } } 割り算(折りたたみ) class DivResultPrinter { func printResult(num1: Int, num2: Int) { let result = num1 / num2 print("\(num1)と\(num2)を割り算した結果は\(result)です。) } } ーーーーーーーーーーーーーーーーーーーーーーーーーーー メンドクサイから処理の一部を、他にやってもらう この時、それぞれの計算classの中で共通している処理はnum1とnum2の型指定と計算結果のプリントの2つですね なので、この2つの処理を1つにまとめて処理してもらいましょう。 イメージは以下のような感じ 計算側のコード(折りたたみ) AddCalculatorを作成し、足し算に専念するclassを作成。 次に、num1, num2、及び、num1 + num2の結果をInt型と指定した、calculateという処理を作成します。 最後に、プリントした際にこれは何算であるかをわからせるために calculationNameを作成しています。 (引き算も同様です。) //足し算 class AddCalculator { func calculate(num1: Int, num2: Int) -> Int { return num1 + num2 } func calculationName() -> String { return "足し算" } } //引き算 class SubCalculator { func calculate(num1: Int, num2: Int) -> Int { return num1 - num2 } func calculationName() -> String { return "引き算" } } プリント側のコード(折りたたみ) ResultPrinterを作成し、プリントするだけのClassを作成。 つくった足し算や、引き算classを使用できるように、プロパティcalculatorを作成し、代入できるようにする。(入れるものが無いので今回はAddcalculatorを代入) プリントを実行するprintResultメソッドを作成。calculatorに代入された名前と計算結果を引き継げるように name と result を作成。 class ResultPrinter { //以下を差し替えればよさそう。(入れるものが無いので、いまはAddCalculator()をいれている) var calculator = AddCalculator() func printResult(num1: Int, num2: Int) { let name = calculator.calculationName() let result = calculator.calculate(num1: num1, num2: num2) print("/(num1)と/(num2)を/(name)した結果は/(result)です") } } では、上記の組み合わせで実際に表示してみましょう。 let printer = ResultPrinter() printer.printResult(num1: 8, num2: 5) コンソールに「8と5を足し算した結果は13です」と表示されるはずです。 では、引き算もやってみましょう。 以下を記載してみます。 let printer = ResultPrinter() printer.printResult(num1: 8, num2: 5) let subCalculator = SubCalculator() printer.calculator = subCalculator printer.printResult(num1: 8, num2: 5) すると、printer.calculator = subCalculator の横に「Cannot assign value of type 'SubCalculator' to type 'AdditionCalculator'」とエラーが発生してしまいます。 エラーが出てしまう理由 上記で出てしまったエラーは、printer.calculatorはSubCalculator型ではないですよ~って言ってるわけです。 さかのぼって、プリント側のコード(折りたたみ)を見ると、var calculator = AddCalculator() と記載してますね。これが原因となります。 var calculator = SubCalculator() としてもいいのですが、今度はAddCalculatorが使用できなくなってしまいます。 これは、変数には同じクラスのインスタンスしかセットできないということが問題です。 しかし今は処理を分けたいので、同じメソッドを持っているクラスのインスタンスであれば以下のように切り替えをしたいです。 受け売りですが、例えるならばUSB端子の形状が同じなら、どんなメーカーの製品でもPCに接続できて欲しいといった状況です。 実は、それを実現するのがprotocolとなります! protocolを使用する protocolはDelegateには欠かせぬ存在です。 protocolを使用することで、「同じメソッドを持つクラスを同等に扱う」ことができます。 使用するためには、①protocolの作成 ②同等に扱いたいclassと計算処理を代入するプロパティへ、作成したprotocolを付与を行います。 以下を理解しましょう。 ①protocolの作成 protocol Calculator { func calculate(num1: Int, num2: Int) -> Int func calculationName() -> String } これは、上記2つのメソッドを兼ね備えているものは、Calculatorとして扱える。といった記述になります。 ②作成したprotocolの付与 //AddCalculatorにCalculatorプロトコルを付与 class AddCalculator: Calculator { func calculate(num1: Int, num2: Int) -> Int { return num1 + num2 } func calculationName() -> String { return "足し算" } } //SubCalculatorにCalculatorプロトコルを付与 class SubCalculator: Calculator { func calculate(num1: Int, num2: Int) -> Int { return num1 - num2 } func calculationName() -> String { return "引き算" } } //ResultPrinter内の、差し替えプロパティ(calculator)にもCalculatorを忘れずに付与 class ResultPrinter { var calculator: Calculator = AddCalculator() func printResult(num1: Int, num2: Int) { let name = calculator.calculationName() let result = calculator.calculate(num1: num1, num2: num2) print("/(num1)と/(num2)を/(name)した結果は/(result)です") } } 上記のように記述することで、切り替えができるようになります。 イメージは以下のような感じ 試しに、また以下を記載してみましょう。 let printer = ResultPrinter() printer.printResult(num1: 8, num2: 5) let subCalculator = SubCalculator() printer.calculator = subCalculator printer.printResult(num1: 8, num2: 5) すると、「8と5を足し算した結果は13です」「8と5を引き算した結果は3です」と正しく表示されるはずです。 これでデリゲートは完了しています。 まとめ 一番最初にDelegateとは「自身で行う処理の一部を、他にやってもらう」と記載しましたが、このようにprotocolを使用して、処理の一部を他に任せることをDelegateと言います。 勘違いして欲しくないのは、Delegate自体が特殊なコードとかではないことです。流れのことです。 長々書きましたが、必ず使用するのでprotocolとは?、Delegateとは?をしっかり理解しておくといいかもしれません。
- 投稿日:2022-02-28T10:55:32+09:00
URLProtocolでURLSessionの通信をハンドリングする
概要 例えば、通信などが起きたときに、そのログを残しておいたり、通信量を調べたい時があると思います。 そんな通信のハンドリングを行うために、URLProtocolを利用します。 通信をURLProtocol継承クラスに処理させる 通信をハンドリングするには、ハンドリングするためのURLProtocol継承クラスを用意します。ここではMyURLProtocolという名前にしました。 URLProtocol継承クラスの実装に関しては後ほど説明します。 URLProtocol継承クラスを用意したら、各種通信がURLProtocol継承クラスを通して行われるようにします。 URLSession.sharedの場合 もしURLSession.sharedしか使われていないのであれば、URLProtocol.registerClass を使うことで全てのURLSession.sharedの通信がURLProtocol継承クラスを通してハンドリングされます。 URLProtocol.registerClass(MyURLProtocol.self) 個別のURLSessionの場合 個別のURLSessionを作成して通信を行なっている場合、使用するURLSessionConfigurationの protocolClasses にURLProtocol継承クラスを登録するようにします。 let sessionConfiguration = URLSessionConfiguration.default sessionConfiguration.protocolClasses = [MyURLProtocol.self] let session = URLSession(configuration: sessionConfiguration) URLProtocolをオーバーライドしたクラスを用意する 各種通信をハンドリングするURLProtocol継承クラスを用意します。 通信が走るたびに、設定したURLProtocol継承クラスがURLSessionの通信をハンドリングします。 今回の目的はあくまで通信機能はそのまま動作させるようにするので、内部で実際の通信を行うURLSessionを持ち、各種URLProtocolのメソッドをオーバーライドしてstartLoadingの時には通信開始、stopLoadingの時はキャンセルするようにします。 class MyURLProtocol: URLProtocol { private var sessionTask: URLSessionDataTask? private lazy var session: URLSession = { [unowned self] in return URLSession(configuration: .default, delegate: self, delegateQueue: nil) }() override class func canInit(with request: URLRequest) -> Bool { true } override class func canInit(with task: URLSessionTask) -> Bool { task.currentRequest != nil } override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } override func startLoading() { sessionTask = session.dataTask(with: request) sessionTask?.resume() } override func stopLoading() { sessionTask?.cancel() session.finishTasksAndInvalidate() } } URLSessionDataDelegateの実装 URLSessionDataDelegateの実装では、各種イベントが起きた時にclientにイベントを通知します。 extension MyURLProtocol: URLSessionDataDelegate { func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { client?.urlProtocol(self, didLoad: data) } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { let policy = URLCache.StoragePolicy(rawValue: request.cachePolicy.rawValue) ?? .notAllowed client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: policy) completionHandler(.allow) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error = error { client?.urlProtocol(self, didFailWithError: error) } else { client?.urlProtocolDidFinishLoading(self) } } func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response) completionHandler(request) } func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { guard let error = error else { return } client?.urlProtocol(self, didFailWithError: error) } func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { client?.urlProtocolDidFinishLoading(self) } } まとめ URLProtocolを使ってのURLSessionのハンドリングについて説明しました。 各種URLSessionDataDelegateのfuncに各自必要な処理を書くことで、通信量の測定やログの書き込みなどができるかと思います。
- 投稿日:2022-02-28T07:53:23+09:00
文章の表現や文体をチェックしてくれるAIアプリを作った?
はじめに 文章を分析してアドバイスをしてくれるAIアプリをリリースしました。今回はその機能と使用した技術についての記事になります。ダウンロードは画像もしくはこちらのリンクからどうぞ。ちなみに画像のキャラクターがAIの「ホメロス」です。 ホメロスができること 文体分析 ユーザーが入力した文章を分析し、その文体を5段階で評価します。数字が大きいほど丁寧な文章であるということになり、例えばWikipediaの記事やニュースはレベル5と診断されます。 逆にレベルが低いものはくだけた表現ということになります。ブログ記事など、親しみが必要な文章は低いレベルで書くのがオススメです。広報のための公式アカウントなど、親しみやすさと丁寧さの両方が求められる場合にレベルを調節するという使い方もできます。 炎上対策 文体だけでなく、文中の表現も分析しています。いわゆる「炎上」につながるような過激なものが見つかった場合は注意を促すようにしています。SNSなど、投稿を行う前にチェックをしておくと思わぬトラブルを防ぐことが可能です。 また政治的な内容など、物議を醸すような文章に関してはより高いレベル、つまり丁寧な文章で書くように推奨します。正式な文体を使えば誤解されにくいためです。 表現の抽出 具体的にどんな言葉遣いや表現が検出されたか、という詳細を確認することができます。これで目的に応じた文体のレベルに書き換えることができます。論文や正式な書類を書くときに間違った言葉遣いをしていても修正できます、 技術的ポイント 言語モデルの課題 モバイルアプリへの言語モデルの導入として問題になるのは、容量と処理という2つの負担が大きいことです。特に後者は同じiPhoneでもどの機種でも機能するか、といったことにも気を配らなくてはなりません。 既存のWebにおけるAIサービスのように外部で処理を行えば以上の課題は解決されます。ただしこの場合、自分の文章がどこかに送信されてしまうことになります。機密性の高い文書はもちろんですが、プライベートな文章などはできるだけ安全性を確保したいものです。 結論として、ホメロスは処理を完全に内部だけで行うように設計しました。オフラインでも問題なく機能しますが一方でアプリ本体のサイズは記事執筆時点で10MB以下しかありません。これにはちょっとした工夫があります。 NLPフレームワーク iOSにおけるSwiftのフレームワークのひとつとしてNaturalLanguageというものが存在します。これはその名の通り自然言語処理を行なってくれるものです。iOSに最初から組み込まれている機能を利用するため、これは容量と処理にほとんど負担を与えません。またiOS自体に依存しているため、機種の違いによる問題も起こりません。 その代わり、全ての機能が日本語に対応しているわけではなく万能ではありません。今回利用した文章のトークン化は日本語でも問題なく行うことができます。 TextCheck.swift import NaturalLanguage let text = "ホメロスは人工知能で文章を分析するアプリです" func tokenize(text: String) { let tokenizer = NLTokenizer(unit: .word) tokenizer.string = text let tokens = tokenizer.tokens(for: text.startIndex ..< text.endIndex) var textTokens: [String] = [] for token in tokens { let tokenStart = token.lowerBound let tokenEnd = token.upperBound let text = text[tokenStart ..< tokenEnd] textTokens.append(String(text)) } print(textTokens) } tokenize(text: text) 実行結果は以下のようになります。精度としては十分なものかと思います。 ["ホメロス", "は", "人工", "知能", "で", "文章", "を", "分析", "する", "アプリ", "です"] これらの処理は内部で完結するので、外部のサーバーとやりとりする必要がないという利点もあります。このようにiOS内の言語モデルを活用することで課題を解決できるようになりました。 SwiftUI 本アプリはそのほとんどをSwiftUIで構築しています。iOS15への変化もあり、UIKitに頼らなくてもできることがかなり増えてきました。またMac版の開発を視野に入れた上での導入でもあります。 さいごに 簡潔な説明だけにとどめたので開発のきっかけや経緯などは省略しています。もし興味を持ってくださるならばそういった内容はnoteに投稿しているので、読んでいただけると嬉しいです。 このAIは記事執筆はもちろん、SNSの投稿の際にも役立ってくれます。いいなと思った方はシェア等していただけると幸いです。開発者としてさらによりよいものにしていくので、「ホメロス」をよろしくお願いします。
- 投稿日:2022-02-28T07:53:23+09:00
AIが文章の表現や文体をチェックしてくれるアプリを作った?
はじめに 文章を分析してアドバイスをしてくれるAIアプリをリリースしました。今回はその機能と使用した技術についての記事になります。ダウンロードは画像もしくはこちらのリンクからどうぞ。ちなみに画像のキャラクターがAIの「ホメロス」です。 ホメロスができること 文体分析 ユーザーが入力した文章を分析し、その文体を5段階で評価します。数字が大きいほど丁寧な文章であるということになり、例えばWikipediaの記事やニュースはレベル5と診断されます。 逆にレベルが低いものはくだけた表現ということになります。ブログ記事など、親しみが必要な文章は低いレベルで書くのがオススメです。広報のための公式アカウントなど、親しみやすさと丁寧さの両方が求められる場合にレベルを調節するという使い方もできます。 炎上対策 文体だけでなく、文中の表現も分析しています。いわゆる「炎上」につながるような過激なものが見つかった場合は注意を促すようにしています。SNSなど、投稿を行う前にチェックをしておくと思わぬトラブルを防ぐことが可能です。 また政治的な内容など、物議を醸すような文章に関してはより高いレベル、つまり丁寧な文章で書くように推奨します。正式な文体を使えば誤解されにくいためです。 表現の抽出 具体的にどんな言葉遣いや表現が検出されたか、という詳細を確認することができます。これで目的に応じた文体のレベルに書き換えることができます。論文や正式な書類を書くときに間違った言葉遣いをしていても修正できます、 技術的ポイント 言語モデルの課題 モバイルアプリへの言語モデルの導入として問題になるのは、容量と処理という2つの負担が大きいことです。特に後者は同じiPhoneでもどの機種でも機能するか、といったことにも気を配らなくてはなりません。 既存のWebにおけるAIサービスのように外部で処理を行えば以上の課題は解決されます。ただしこの場合、自分の文章がどこかに送信されてしまうことになります。機密性の高い文書はもちろんですが、プライベートな文章などはできるだけ安全性を確保したいものです。 結論として、ホメロスは処理を完全に内部だけで行うように設計しました。オフラインでも問題なく機能しますが一方でアプリ本体のサイズは記事執筆時点で10MB以下しかありません。これにはちょっとした工夫があります。 NLPフレームワーク iOSにおけるSwiftのフレームワークのひとつとしてNaturalLanguageというものが存在します。これはその名の通り自然言語処理を行なってくれるものです。iOSに最初から組み込まれている機能を利用するため、これは容量と処理にほとんど負担を与えません。またiOS自体に依存しているため、機種の違いによる問題も起こりません。 その代わり、全ての機能が日本語に対応しているわけではなく万能ではありません。今回利用した文章のトークン化は日本語でも問題なく行うことができます。 TextCheck.swift import NaturalLanguage let text = "ホメロスは人工知能で文章を分析するアプリです" func tokenize(text: String) { let tokenizer = NLTokenizer(unit: .word) tokenizer.string = text let tokens = tokenizer.tokens(for: text.startIndex ..< text.endIndex) var textTokens: [String] = [] for token in tokens { let tokenStart = token.lowerBound let tokenEnd = token.upperBound let text = text[tokenStart ..< tokenEnd] textTokens.append(String(text)) } print(textTokens) } tokenize(text: text) 実行結果は以下のようになります。精度としては十分なものかと思います。 ["ホメロス", "は", "人工", "知能", "で", "文章", "を", "分析", "する", "アプリ", "です"] これらの処理は内部で完結するので、外部のサーバーとやりとりする必要がないという利点もあります。このようにiOS内の言語モデルを活用することで課題を解決できるようになりました。 SwiftUI 本アプリはそのほとんどをSwiftUIで構築しています。iOS15への変化もあり、UIKitに頼らなくてもできることがかなり増えてきました。またMac版の開発を視野に入れた上での導入でもあります。 さいごに 簡潔な説明だけにとどめたので開発のきっかけや経緯などは省略しています。もし興味を持ってくださるならばそういった内容はnoteに投稿しているので、読んでいただけると嬉しいです。 このAIは記事執筆はもちろん、SNSの投稿の際にも役立ってくれます。いいなと思った方はシェア等していただけると幸いです。開発者としてさらによりよいものにしていくので、「ホメロス」をよろしくお願いします。
- 投稿日:2022-02-28T01:00:53+09:00
複数のテストバンドルに分かれたXCTestのカバレッジをマージする
概要 この記事では、XCTestのカバレッジデータをコマンドライン上で操作する方法に関して解説します。 カバレッジデータを操作するには専用のデータ(.xcresult)とコマンド操作(xccov, xcresulttools)が必要となります。 また、具体的な応用事例として 「複数に分かれたテストバンドルのカバレッジデータをマージして総カバレッジを出す」 というのを行います。 カバレッジの解析に必要な道具を揃える・理解する まず初めに、カバレッジをコマンドラインで操作するために必要な道具について解説します。 .xcresultファイルを取得する カバレッジデータを生成するにはXcodeでテストカバレッジ計測をオンにする必要があります。 まず、Scheme一覧のEdit SchemeからTestのタブを開いてチェックをつけます。 その後Cmd+Uでテストを実行すると、テストカバレッジが生成されるようになります。 テストカバレッジはProject Navigatorの「Show Report Navigator」から確認できます。 右クリックをして「Show in Finder」をクリックするとファイルがある場所に移ることができます。 テストカバレッジのデータは.xcresultというパッケージで保存されており、このファイルはコマンドラインからの扱えます。 解析するには以下の2つのコマンドを使用します。 役割は似ていて結構同じことができるのですが、少しずつ違いがあるためどちらも使えるようになっておいた方がいいでしょう。 xccov xcresulttool 2つともxcrunを経由して実行します。 xcresulttoolの使い方 ドキュメントは用意されていません(manページのみ)。何ができるかの詳細はhelpサブコマンドで調べていきます。 xcrun xcresulttool help helpサブコマンドを打つと使えるサブコマンド一覧が出力されるので、さらにそれらに対して--helpを実行して役割や使い方を調べていきます。 xcrun xcresulttool get --help getに対して使用可能なオプションが見つかるのでそれらを使って実行しています。 xcrun xcresulttool get --path sample.xcresult --format json このコマンドを実行するとjson形式でテストカバレッジのメタデータが出力されます。 xccov ドキュメントは用意されていません(manページのみ)。何ができるかの詳細はhelpサブコマンドで調べていきます。 xcrun xccov help helpサブコマンドを打つと使えるサブコマンドとそれらの使い方がわかるので、結果をもとに実行します xccov view --archive --file-list export.xcresult このコマンドはファイル単位でのカバレッジをリストで出力します。 .xcresultから.xccovarchiveと.xccovreportの生成 .xcresultを直接扱うこともできるのですが、より解析しやすくするため.xccovarchiveと.xccovreportの2つを生成します。.xccovarchiveにはファイルの各行に関する細かいカバレッジデータが含まれており、.xccovreportはメタデータを扱います。 作業は少し癖があって最初はとっつきにくいですが、慣れれば簡単です。 idを探し当てる 上述の2つを生成するためには.xcresult内にあるIDを取得する必要があります。 .xcresultはディレクトリで、その中にはInfo.plistと分割されたバイナリデータがあります。 バイナリデータはそのままでは読むことができないのでこれを読める形に復元します。 sample.xcresult ├── Data │ ├── data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== │ ├── data.0~5jnweIyI7KJZYMCxddH5dlK1FAr0xdWnIQCNLIKNRHATSI_gxPVdo2kkhDEgt14ciE9NBe7UEem64nYPiiicIA== │ ├── data.0~6ISB5n3exwjH0YZeXQqQ-AAavppAXZKqnIW5unTxqbGwhFic4mXBF_q4dYFN8D3p_li1EwkrJjPGFRE-l_oo7Q== │ ├── data.0~8X6LHAW4WpROz2od-hssStrzKjuJooIzdqLoV4BY_u33BC-TNAqYiEuomvjlkWn982zIWbX2GmiCmbtEA_tNiw== │ ├── data.0~DUsyWDayE2_xnv8PuhnvGOveEo-6e8Usv-j2DKz9ana8rR2JslxjMgR0jxOX9Iz2Y8BF9wQvxQlTzspJJv9w4g== │ ├── refs.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== │ ├── refs.0~5jnweIyI7KJZYMCxddH5dlK1FAr0xdWnIQCNLIKNRHATSI_gxPVdo2kkhDEgt14ciE9NBe7UEem64nYPiiicIA== │ ├── refs.0~6ISB5n3exwjH0YZeXQqQ-AAavppAXZKqnIW5unTxqbGwhFic4mXBF_q4dYFN8D3p_li1EwkrJjPGFRE-l_oo7Q== │ ├── refs.0~8X6LHAW4WpROz2od-hssStrzKjuJooIzdqLoV4BY_u33BC-TNAqYiEuomvjlkWn982zIWbX2GmiCmbtEA_tNiw== │ ├── refs.0~DUsyWDayE2_xnv8PuhnvGOveEo-6e8Usv-j2DKz9ana8rR2JslxjMgR0jxOX9Iz2Y8BF9wQvxQlTzspJJv9w4g== │ ├── refs.0~Ho4URfFpOlkF_yunDTGIHD26BYRVMIseiNWjFLYEDyBwSdrJ7PDkdieHERv22_n_2ektzRNJ2jMDGHr3o2QIaQ== └── Info.plist 以下のコマンドでsample.xcresultをjson形式で見ることができます。 xcrun xcresulttool get --path sample.xcresult --format json 出力結果の行数がかなり長いため、どこに何が書いてあるのか構造が理解しにくいでしょう。 .xcresultの構造がどうなっているのかを確認するためだけのサブコマンドも用意されています。 それを叩いて全体像を確認しましょう。 xcrun xcresulttool formatDescription get とはいえ、これはこれでかなり項目が多くわかりにくいと思います。 それぞれ何を示しているのかは、目的に応じて都度実際に記載されている内容と照らし合わせながら推測していくしかないです。 ここで必要とされている.xccovreportと.xccovarchiveを生成するためのIDだけでいえば、以下の項目を見れば良いです。 - CodeCoverageInfo * Kind: object * Properties: + hasCoverageData: Bool + reportRef: Reference? + archiveRef: Reference? このreportRefとarchiveRefにそれぞれのIDが記載されています。 それではsample.xcresultの該当する項目を見てみましょう。 ... ... "actionResult" : { "_type" : { "_name" : "ActionResult" }, "coverage" : { "_type" : { "_name" : "CodeCoverageInfo" }, "archiveRef" : { "_type" : { "_name" : "Reference" }, "id" : { "_type" : { "_name" : "String" }, "_value" : "0~_8vRmaabmeE6qxk5SPr5-zItlyCmdeOhHEOXQBtDHePMDaWSwSoV1O_YVbeEHQfau3uwQ1RRdKGPf6vVryQOSA==" } }, "reportRef" : { "_type" : { "_name" : "Reference" }, "id" : { "_type" : { "_name" : "String" }, "_value" : "0~U9Mb0J7rRAGvBun8pW62sZBA0Heg4CVvkgqWGHO3b7TdzHFcNva6h39z3F0poVgIlJYNFxqvO5ZakFc7MDpNJw==" } } }, ... ... 読み方がややこしいのですが、ぞれぞれ"archiveRef" > "id" > "_value"と"reportRef" > "id" > "_value" を見れば大丈夫です。 ここでは xcarchiveのID: "0~_8vRmaabmeE6qxk5SPr5-zItlyCmdeOhHEOXQBtDHePMDaWSwSoV1O_YVbeEHQfau3uwQ1RRdKGPf6vVryQOSA==" xcreportのID: "0~U9Mb0J7rRAGvBun8pW62sZBA0Heg4CVvkgqWGHO3b7TdzHFcNva6h39z3F0poVgIlJYNFxqvO5ZakFc7MDpNJw==" です .xccovarchiveのエクスポート xcresulttoolのexportコマンドでエクスポートします。pathにxcresultを指定して、idにxcresultから見つけてきたreportRefのidを指定します。xccovarchiveはディレクトリのため、typeにはdirectoryを指定する必要があります。 xcrun xcresulttool export --path sample.xcresult --id 0~_8vRmaabmeE6qxk5SPr5-zItlyCmdeOhHEOXQBtDHePMDaWSwSoV1O_YVbeEHQfau3uwQ1RRdKGPf6vVryQOSA== --type directory --output-path export_0.xccovarchive カレントディレクトリにexport_0.xccovarchiveが生成されます。 .xccovreportのエクスポート xcresulttoolのexportコマンドでエクスポートします。pathにxcresultを指定して、idにxcresultから見つけてきたreportRefのidを指定します。xccovreportはディレクトリのため、typeにはfileを指定する必要があります。 xcrun xcresulttool export --path sample.xcresult --id 0~U9Mb0J7rRAGvBun8pW62sZBA0Heg4CVvkgqWGHO3b7TdzHFcNva6h39z3F0poVgIlJYNFxqvO5ZakFc7MDpNJw== --type file --output-path export_0.xccovreport カレントディレクトリにexport_0.xccovreportが生成されます。 xccovarchiveとxccovreportからカバレッジ情報を取得する 続いて.xccovarchive・.xccovreportでできることを見ていきます。 特定のファイルに対して、行単位のカバレッジを取得する 以下のコマンドでカバレッジが見れるファイル一覧を出力します。 xcrun xccov view --file-list export_0.xccovarchive そのうちのどれかを使って以下のコマンドを打つと、ファイルの各行のカバレッジがどうなっているかが見れます。 アスタリスクがついている部分はカバレッジ計算の対象外となっている部分で、数値はそこを通った回数です。 Xcode上でCode Coverageを表示させた時と同じ形になっています。 1: * 2: * 3: * 4: * 5: * 6: * 7: * 8: * 9: * 10: * 11: * 12: * 13: * 14: * 15: * 16: * 17: * 18: * 19: * 20: * 21: * 22: * 23: * 24: * 25: * 26: * 27: * 28: * 29: * 30: * 31: * 32: * 33: * 34: * 35: * 36: * 37: * 38: 49 39: 49 40: 49 41: * 42: * 43: * 44: * 45: 49 46: 49 47: 49 48: * 49: * 50: 0 51: 0 52: 0 53: * 54: * 55: * 56: * 57: 0 58: 0 59: 0 60: 0 61: 0 62: 0 63: 0 64: 0 65: * 66: * 67: * 68: * 69: * 70: * 71: * 72: * 73: 49 74: 49 75: 49 76: 49 77: 49 78: 49 79: 49 80: * 81: * 82: * 83: * 84: * 85: * 86: * 87: 0 88: 0 89: 0 90: 0 91: 0 92: 0 93: 0 94: 0 95: 0 特定ターゲットのファイル毎のカバレッジを取得する 以下のコマンドでファイル毎のカバレッジを出力します。注意点としては指定するターゲットはパッケージ名です。 xcrun xccov view --files-for-target Hoge.framework export_0.xccovreport 以下のようにファイル毎のカバレッジを表形式で見ることができます。 ID Name # Functions Coverage -- -------------------------------------------------- ----------- ---------------- 0 /Path/Hoge/Sources/Class1.swift 8 48.72% (19/39) 1 /Path/Hoge/Sources/Class2.swift 2 100.00% (24/24) ファイル単位で出力できるということはカバレッジの計算対象外としたいファイルを除くこともできます。 表形式だと扱いにくいため、--jsonをつけてjson形式にするとプログラムから処理しやすくなるでしょう。 xcrun xccov view --files-for-target Hoge.framework export_0.xccovreport --json ターゲット全体のカバレッジを見る ターゲット全体のカバレッジを見る場合は以下のコマンドを実行します。 xcrun xccov view --only-targets export_0.xccovreport ファイル毎のカバレッジと同じように表形式で見れます。 ID Name # Source Files Coverage -- ---------------------- -------------- ----------------- 0 Hoge.framework 10 21.07% (326/1547) 1 HogeTests.xctest 4 32.18% (671/2085) 他にも、関数単位でのカバレッジを出力するなど色々できます。詳細は初めに書いた通り、helpサブコマンドで確認してください。 複数のカバレッジをマージして総カバレッジを確認する 複数のテストバンドルのカバレッジをまとめることもできます。 テスト対象が同じでテストバンドルが複数に分かれているケースなどで利用できます。 前述までで生成した複数の.xccovreport, .xccovarchiveに対して、mergeコマンドを使うことで実現できます。 以下のようにmergeコマンドの引数に生成したやつを全部指定してやれば大丈夫です。 xcrun xccov merge export_0.xccovreport export_0.xccovarchive export_1.xccovreport export_1.xccovarchive こうすると、merged.xcarchive, merged.xcreportがカレントディレクトリに生成されます。 「xccovarchiveとxccovreportからカバレッジ情報を取得する」で書いている内容と同じことをすれば、複数のテストバンドルの総カバレッジを取得できます。 複数のカバレッジデータを使っできることには、他にもdiffを取るなどもがあります。こちらもhelp サブコマンドで詳細が確認できます。 コマンドラインでの出力結果を応用する このようにカバレッジデータはコマンドライン上で細かく操作することが可能です。 例えば、 「複数のテストバンドルをマージして、不要なファイルを取り除いた上で通った行数/全行数として、行カバレッジの計算を行う」 などといったスクリプトも実装できます。 それ以外にも様々な応用が考えられます。 まとめ XCTestのテストカバレッジをコマンドライン上で出力できる 複数カバレッジのマージやdiffを取るなどできる スクリプトと組み合わせて様々な応用が可能 参考資料 Xcodeのテストとしてはこちら Running Tests and Viewing Results 公式の資料としてはWWDC2019のものを見るのが良いです Testing in Xcode 網羅的なドキュメントはあまり存在していませんが、日本語の資料としては以下が一番詳細に記載されていると思います。 メルペイでのxcresult活用事例 あとは各コマンドのヘルプと以下のようなライブラリの実装から読み解いていくのがいいです。 https://github.com/fastlane-community/xcresult https://github.com/ChargePoint/xcparse