20210428のiOSに関する記事は4件です。

[SwiftUI]モディファイアとは(初学者向け)

モディファイアとは import SwiftUI struct ContentView: View { var body: some View { Text("Hello, Udemy") //以下.(ドット)で始まるコードがモディファイア .font(.title) .fontWeight(.medium) .foregroundColor(Color.blue) .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 上記コードのように.(ドット)〜を付け足すことでUIを変更するものをモディファイアと呼びます。 今回の場合は、Text("Hello, Udemy")に対してモディファイアを適応しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]正しいエラー処理を選ぶ

※この記事はSwift by Sundellの内容を日本語に翻訳したものです。 概要 Swiftの主な特徴の一つは、コンパイル時の安全性です。 これにより、開発者はより予測可能でタイムエラーが発生しにくいコードを書くことができます。 しかし、様々な要因によってエラーが発生することがあります。今回は、そのようなエラーを適切に処理する方法とそのために使用できるツールについて見ていきます。 「Handling non-optional optionals in Swift」という記事では、実際にはオプショナルではないオプショナル型の処理方法を紹介しています。この記事では、強制アンラップの代わりにguardと組み合わせてpreconditionFailure()を使用すること、その為の便利なAPIを提供するマイクロフレームワーク「Require」を紹介しました。 その投稿以来、多くの人がpreconditionFailure()とassert()の違いと、それがSwiftのthrowing機能とどのように関連するのかについて尋ねてきました。なので、この記事ではこれらの言語の特徴とそれぞれの使い時を詳しく見ていきます。 エラー処理方法一覧 Swiftでエラーを処理する方法は次のとおりです。 nilを返す、またはエラー列挙型 エラー処理の最も簡単な形式は、エラーが発生した関数から単純にnil(または戻り値の型として列挙型Resultを使用している場合は.error型)を返すことです。これは多くの状況で非常に役立ちますが、すべてのエラー処理に使いすぎると、APIの使用がややこしくなり、欠陥のあるロジックが隠れてしまうリスクがあります。 throw MyErrorを使用してエラーを投げる これには、呼び出し元がdo, try, catchパターンを使用して潜在的なエラーを処理する必要があります。または、呼び出し地点でtry?を使用してエラーを無視することもできます。 assert()およびassertionFailure()の使用 特定の条件が真であることを確認します。デフォルトでは、これはデバッグビルドでfatal errorを引き起こしますが、リリースビルドでは無視されます。したがって、assertが引き起こされた場合に実行が停止することは保証されていないため、重大なランタイム警告のようなものです。 precondition()とpreconditionFailure()の使用 asserrtとの主な違いは、リリースビルドであっても、これらは常に評価されることです(Ounchecked最適化モードを使用してコンパイルしている場合を除く)。つまり、条件が満たされない場合、実行が続行されないことが保証されます。 fatalError()の呼び出し これは、Xcodeで生成されたinit(coder :)の実装でUIViewControllerなどのNSCoding準拠のシステムクラスをサブクラス化するときに、おそらく見たことがあるでしょう。これを呼び出すと、プロセスが直接強制終了されます。 exit()の呼び出し コードを使用してプロセスに存在するexit()を呼び出します。これは、コマンドラインツールやスクリプトで、グローバルスコープ(main.swiftなど)を終了する場合に非常に便利です。 エラーが回復可能か不可能か エラー処理の方法を選択する際に考慮すべき重要なことは、発生したエラーが回復可能かどうかを判断することです。 たとえば、サーバーを呼び出していて、エラーレスポンスを受け取ったとします。それは、私たちがどれほど素晴らしいプログラマーであり、サーバーインフラがどれほど堅固であっても必ず起こることです。したがって、これらのタイプのエラーを致命的で回復不可能なものとして扱うことは、たいてい間違いです。代わりに、私たちが望んでいるのは回復して、おそらく何らかの形のエラー画面をユーザーに表示することです。 では、この場合にエラー処理の適切な方法を選択するにはどうすればよいでしょうか。上記のリストを見ると次のように、回復可能な手法と回復不可能な手法に分類できます。 回復可能 nilを返す、またはエラー列挙型 throw MyErrorを使用してエラーを投げる 回復不能 assert()およびassertionFailure()の使用 precondition()とpreconditionFailure()の使用 fatalError()の呼び出し exit()の呼び出し この場合、非同期タスクを扱っているので、次のように、戻り値nilまたはエラー列挙型がおそらく最良の選択です。 class DataLoader { enum Result { case success(Data) case failure(Error?) } func loadData(from url: URL, completionHandler: @escaping (Result) -> Void) { let task = urlSession.dataTask(with: url) { data, _, error in guard let data = data else { completionHandler(.failure(error)) return } completionHandler(.success(data)) } task.resume() } } 同期APIの場合、throwは最適な選択です。これは、APIユーザーに適切な方法でエラーを処理するように「強制」するためです。 class StringFormatter { enum Error: Swift.Error { case emptyString } func format(_ string: String) throws -> String { guard !string.isEmpty else { throw Error.emptyString } return string.replacingOccurences(of: "\n", with: " ") } } ただし、エラーを回復できない場合があります。たとえば、アプリの起動時に構成ファイルをロードする必要があるとします。その構成ファイルが欠落していると、アプリが未定義の状態になります。 したがってこの場合、プログラムの実行を続行するよりもクラッシュする方が適切です。そのためには、より強力で回復不可能なエラー処理を使用する方が適切です。 この場合、構成ファイルが欠落している場合に実行を停止するためにpreconditionFailure()を使用します。 guard let config = FileLoader().loadFile(named: "Config.json") else { preconditionFailure("Failed to load config file") } プログラマーエラーと実行エラー 重要なもう1つの違いは、エラーの原因がロジックの誤りもしくは構成の誤りなのか、またはエラーがアプリケーションのフローの正当な部分と見なされるべきかどうかです。基本的に、プログラマーがエラーを引き起こしたかどうか、または外部要因が原因であったかどうかです。 プログラマーのエラーから保護する場合、ほとんどの場合、回復不可能な手法を使用する必要があります。そうすれば、アプリ全体で異常な状況をコード化する必要がなくなり、優れた一連のテストにより、これらのタイプのエラーができるだけ早く検出されることが確認されます。 たとえば、使用する前にビューモデルをバインドする必要があるビューを構築しているとします。ビューモデルはコードではオプショナル型になりますが、使用するたびにラップを解除する必要はありません。ただし、ビューモデルが何らかの理由で失われた場合でも、本番環境でアプリケーションをクラッシュさせる必要はありません。デバッグでエラーが発生しても十分です。これは、アサートを使用する場合です。 class DetailView: UIView { struct ViewModel { var title: String var subtitle: String var action: String } var viewModel: ViewModel? override func didMoveToSuperview() { super.didMoveToSuperview() guard let viewModel = viewModel else { assertionFailure("No view model assigned to DetailView") return } titleLabel.text = viewModel.title subtitleLabel.text = viewModel.subtitle actionButton.setTitle(viewModel.action, for: .normal) } } assertionFailure()はリリースビルドでサイレントにエラーが発生するため、上記のgardステートメントにreturnする必要があることに注意してください。 終わりに この投稿が、Swiftで利用可能なさまざまなタイプのエラー処理手法の違いを明らかにするのに役立つことを願っています。私のアドバイスは、1つのテクニックに固執するだけでなく、状況に応じて最も適切なテクニックを選択することです。一般に、エラーを致命的なものとして扱う必要がない限り、ユーザーエクスペリエンスを妨げないように、可能な限りエラーからの回復を常に試みることをお勧めします。 また、print(error)はエラー処理ではないことを忘れないでください? 元の記事 Picking the right way of failing in Swift Swift by Sundell
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Proxymanを使う!

長年Web Debugging Proxy ToolとしてCharlesを使用してきた。 たくさんの恩恵を与えてもらったが最近、さまざまな不具合や使い勝手の悪さが目立ってきたように思う。 UIの見栄えやレイアウトも使い勝手が悪いと感じてきた DarkMode時のエディタのキャレットが見えないためどこを編集しているのかわからない 時折OSのネットワークのプロキシ設定が戻らないままになることがある(再現手順は取れてないので確証なし) そんな折、社内OJTチャンネルで「Charlesより使い勝手が格段に上」のProxymanなるツールがあることを知る。 Charlesより高機能でCharlesで行えることはほぼ全てカバーされている プロセス毎に見れるため便利! Scripting機能が便利! どの通信でどういう風にレスポンス弄ったか後からも見れる ホスト毎のネットワーク速度絞り込みが可能(Goodbye, Network Link Conditioner) UIが洗練されている。美しい。とにかく見やすいし親切。 バックグラウンドで常時起動していてもさほど気にならない。(ほぼCPU使用率1%未満) トラブルシューティングへの導線も見事。リアルタイムチャットでの即レスもあり、サポートが非常に手厚い印象。 Goodbye, Charles... Hello, Proxyman! ということでProxymanの機能や使い方を一部紹介したい。 使い方 導入 https://proxyman.io/ の右上からディスクイメージがダウンロードできる。 起動 アプリを起動すると初めにProxy Helper Toolのインストールが促される。 調べたらHTTP/HTTPSプロキシ構成のオーバーライドにnetworksetupを使用しているが、 これが起動/終了時のボトルネックになるのを防ぐために入れるヘルパーツールの模様。 Preferences > Advancedからいつでもアンインストールできるのでとりあえず入れる。 メイン画面。 プロセス毎に通信一覧が表示されるため非常に探しやすい。 HTTPS通信の場合は証明書をインストールして信頼することで該当のトラフィックについてはProxymanが複合化して表示/編集することが可能になる。 レスポンス表示領域に表示されている「Enable only this domain」「Enable all domains from "Proxyman"」ボタンを押すとインストール&信頼する画面が表示される。 「Install & Trust」を押すと完了。 もう一度同じ通信を行うとちゃんと内容が表示される 上記はmacOSアプリの設定方法でiOSアプリの場合はステップが異なり Certificate > Install Certificate on iOS で表示されたウインドウで証明書をインストールする必要がある。 表示されたウインドウの指示通りにインストールすれば問題なし。 複数のシミュレータで確認の必要がある場合、都度このステップを行う必要がある。 それでも動かない場合、「Troubleshooting」ボタンを押すと丁寧に設定方法が記されたページが表示される。トラブルシューティングへの導線が見事。 Breakpoint 特定の通信のリクエスト/レスポンス内容を編集する機能。 Charlesでもお馴染みの機能だが、UI/UXが秀逸。 Tools > Breakpoint > Rules でルールを作り、 Tools > Breakpoint > Breakpointsで実際にでデバッグする画面を表示することができる。 それぞれ機能毎に画面を表示することで下手にOne-Window化して複雑化するより確かにシンプルで良い。 メイン画面から対象の通信からコンテキストメニューを表示して追加すると楽。 適当な名称にしてDone。 因みにこのスクショ撮るためにサンプルで使ってるGoogle Apps ScriptのリクエストURLにはパラメータが付加されるが、順不同になるので最後の?より後ろの部分は*(ワイルドカード)で指定している。 通信を行ってみるとちゃんとRequestでもブレークする。右下の「Execute」を押して続行。 Responseでもブレークする。 レスポンス内容を編集してExecute(続行)するとEditedされたものがどれなのかとその編集内容が後からでも見れるので非常に便利! Scripting Javascriptを使って特定のリクエスト/レスポンスをフックして弄って返せるので 毎回Breakpointで止めて手動編集しなくて済む神機能!! Breakpointだとタイムアウトが短い場合に焦るのも解消できそう。 Scripting > Script List...で管理・編集できる。 Breakpointと同じように対象の通信からコンテキストメニューを表示してTools > Scripting...を選択することで追加することもできる。 追加するとデフォルトでこのようなスクリプトが入っている。 // Addons List: https://docs.proxyman.io/scripting/addons const { sayHello } = require("@addons/HelloWorld.js"); /// /// This func is called if the Request Checkbox is Enabled /// You can manipulate the Request Data here before the request hits on the server /// Ex: Add/Update/Remove: host, scheme, port, path, headers, queries and body (json, form, plain-text, base64 encoded string...) /// Use console.log(request) to see all available fields /// You can import a JSON file and use in the script. Action Menu -> Import File /// Use global object `sharedState` to share data between onRequest and onResponse. Ex: sharedState.data = "My-Data" /// function onRequest(context, url, request) { // console.log(request); console.log(url); // Update or Add new headers // request.headers["X-New-Headers"] = "My-Value"; // Update or Add new queries // request.queries["name"] = "Proxyman"; // Body // var body = request.body; // body["new-key"] = "new-value" // request.body = body; // Done return request; } /// /// This func is called if the Response Checkbox is Enabled /// You can manipulate the Response Data here before it goes to the client /// Ex: Add/Update/Remove: headers, statusCode and body (json, plain-text, base64 encoded string) /// Use console.log(response) to see all available fields /// function onResponse(context, url, request, response) { // console.log(response); // Update or Add new headers // response.headers["Content-Type"] = "application/json"; // Update status Code // response.statusCode = 500; // Update Body // var body = response.body; // body["new-key"] = "Proxyman"; // response.body = body; // Done return response; } これ見ただけで大体何ができるのかどうやって書くのかわかるのが凄いよな。 試しにレスポンス要素を削ってみる。 function onResponse(context, url, request, response) { // Update Body var body = response.body; var items = body["items"]; body["items"] = items.slice(0, 1); response.body = body; // Done return response; } スクリプト内容を変更して右下の「Save & Activate」しておき再度通信する。 ちゃんと削られてた素晴らしい!! Map Local/Map Remote Tools > Map Local... Tools > Map Remote... から管理追加が可能。 これもCharlesでお馴染み機能かもしれないけどとにかくUIが見やすくわかりやすい。 詳細な説明は割愛するが、Map Localの場合、HTTP Messageかローカルのjsonを直指定してMapすることができる。 Network Conditions これは地味に待ち望んだ機能かもしれない。 今まで私が知りうる限りネットワーク速度の絞り込みはAdditional Tools for Xcodeに含まれる標準ツールの一つ、「Network Link Conditioner」に依存していた。 ただ、これを使うとOS全体のネットワーク帯域が制限されてしまい、デバッグ以外に必要な通信も巻き添えを喰らうためSlack更新されないなど陸の孤島化していた。。。 Proxymanではホスト毎に帯域を絞れるのでデバッグ以外の通信帯域はそのままにデバッグが可能! Tools > Network Conditions...から管理・追加できる。 試しにscript.googleusercontent.comのみ制限する設定を追加する。 これも便利! ただし別プロセスでも同じホストの通信は制限されるので注意。 価格 トライアル版でも割と使えるかもしれない。 The Trial version is limited to 4 pinned domains, 4 matching rules for each advanced debugging tool (e.g. SSL Proxying List, Map Local, Map Remote, Breakpoint, Allow/Block List, Protobuf, Scripting, Network Conditions, Multiple Filters), and no new tabs. 私は仕事用と仕事用Macが使えない時の代打で使う個人Mac用に2台分を購入。 https://proxyman.io/pricing 価格も40%セーブされるしお得。 macOS版のライセンス持ってるとiOS版のpremium機能もアンロックできるらしいので今度試してみる。 If you have a valid macOS License, you can unlock Premium Proxyman for iOS. 1 seat can redeem 2 iOS Devices.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクリーンリーダーを使って、Android, iOSアプリを作れるだろう環境を作ってみた

背景 or 拝啓 この記事をご覧になっていらっしゃる方は「アクセシビリティ」という単語をご存じの方が多いのだろうと思う。しかしながら、まだまだこの単語とその重要性についてご存じないエンジニアの方も少なくないと聞く。私を含めたアクセシビリティ機能を必要としている者にとっては、もう少し認知度が上がってくれればなと切に願っている。せめて「バリアフリー」という単語程度には認知度が上がってくれれば。 さて、質の高いアクセシビリティ機能を実装するためには、その機能を必要としているユーザーからのフィードバックはとても重要である。英語圏で開発されたアプリケーションを日本語化する際、日本語が分かる人がレビューしたり提案したほうが質の高い日本語化ができるのに似ている。翻訳だけでは気が付かない日本語特有の使いやすさに気が付きやすいのは、やはり日本語を母国語としているか日本語がわかる人なのだと思う。 ということは、アクセシビリティ機能を必要としている人(以下、当事者と書く)からのフィードバックの質が上がれば、アクセシビリティ機能も向上していくだろう。 さて、ここからが問題である。フィードバックにはおそらくいくつかの段階がある。 なんとなく使いにくいな、という抽象的フィードバック 具体的に使いにくいところを示したフィードバック 具体的にどうなって欲しいかを示したフィードバック ソースコードがあるならば、それに対するパッチやプルリクエスト 3つ目と4つ目はアプリケーションやOS内部知識やプログラミング技術が必要なので、難易度は高い。が、もし当事者がこのような具体的フィードバックを送ることができるならば、アクセシビリティ機能はかなり向上する。そして、プロプライエタリなソフトウェアでこんな具体的フィードバックを送っている人がいらっしゃれば、その当事者は開発している会社で具体的な貢献をしているということになるだろう。 こんな状況を実現するためには、当事者はプログラミングができる必要がある。大規模なアプリケーションを一人で作成することはそもそも不可能だ。それに加えて、画面デザインなどは完全に視力がないならばなおさら難しいだろう。が、開発できる分野はまだまだある。そうやって、スマートフォンのアプリはどうやって作るのか、アクセシビリティ機能はどうやって実装するのか、どんな実装をするとアクセシビリティ的にまずいものができるのかを、具体的に知っていれば、より質の高いフィードバックを送ることができるだろう。 ということで、実際にAndroid/iPhoneのアプリを作っている方が複数人いらっしゃるので、私もその後追いということで、どこまでできるのかを確認してみようと思う。 これを読んで、自分もプログラミングしてみようかなと思われた当事者の方がいらっしゃれば嬉しい。また、アクセシビリティ機能に興味を持っていただいたエンジニアの方がいらっしゃれば嬉しい。 環境など 試している環境を書いておく。要するに、MacとWindowsのコンピュータがある、そんな環境だ。 コンピュータ: iMac (Retina 5K, 27-inch, 2020) OS: macOS Big Sur 11.3 Screen Reader: VoiceOver XCode: 12.4 Android toolchain - develop for Android devices (Android SDK version 30.0.3) Android Studio (version 4.1) Flutter (Channel stable, 2.0.3, on macOS 11.3 GNU Emacs 27.2 Homebrew 3.1.3 VMWare Fusion 12.1.1 Windows 10 バージョン Dev (OS ビルド 21364.1000) Screen Reader: JAWS 2020 JPN, NVDA 2020.4 Visual Studio Code バージョン: 1.55.2 (system setup) Git for Windowsに付属しているGit Bash Android SDKとFlutterはWindows側にもインストール 基本的に現段階の最新バージョンである。このバージョンでなければ動かないということではないし、動かなければ動くように修正されるべきである。 戦略 先人たちの知恵を最大限お借りしよう。どうやらWindowsでは、Visual Studio Codeを使って開発を行うのが、スクリーンリーダー使用時には良さそうである。また、コマンドライン系のOSの方が使いやすいだろうと私は信じているので、UNIX系のOSを使う、ということでmacOSである。これについてはWSLでも良いかもしれないが、iOS用のアプリも作るんだからmacOSを選択しておいたほうが無難なのではないか。 開発用のプラットフォームもたくさんあるんだけど、Flutterを使ってAndroidとiOSのアプリを両方リリースしている方がいらっしゃるので、これはもう従うのが早道だろう。ただ、私はVisual Studio Codeも興味あるが、長いことEmacsを使ってきた。ということで、Flutterの開発環境をEmacsにも設定しておく。 これで基本的なことはできるはずだ。では勧めてみよう。 macOS側のセットアップ 上記環境をせっせとセットアップすればよい。 XCode とりあえず、App StoreからXcodeをインストールする。特に悩まないと思う。 その他のツール 他はほとんどコマンドラインからインストールできるだろう。ターミナルを開いて、粛々とインストール。 $ xcode-select --install $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" $ brew install cocoapods emacs git android-sdk android-studio flutter たぶん上記な感じ。もう試せないい。 $ flutter doctor してみて問題がないかを確認してほしい。問題があったら修正。 Emacsの設定 ぶっちゃけ長いので他に譲りたい。lsp-modeを使えるようにして、これをベースにflutterとかdart-modeとかcompanyとかflycheckとかをセットアップする。正直好みでどうぞ。 ところで、Flutterで使われる言語はDartという言語である。Dartにもいろいろと親しみたい。Dart単一の勉強もしたい。そうすると、dart-mode単一で使ってもちゃんとlspが動いたほうがうれしい。 やってみると、Language Serverが見つからないとか言われてうまく行かない。探してみたらFflutterのインストールディレクトリ内にあった。 export PATH=/usr/local/Caskroom/flutter/2.0.5/flutter/bin/cache/dart-sdk/bin:$PATH みたいにしておくと、Dart言語で書かれたファイルを単一で開いてもちゃんと動くと思う。Flutterをアップデートしたら書き換えなければならないのが面倒だけど。うまいことシンボリックリンクとか使ってやればいいかな。 ひょっとすると、lsp-modeにしてLanguage Serverが動き出した瞬間にmacOSのセキュリティに引っかかってしまうかもしれない。適当に許可を与えれば動く。 Windows側の設定 スクリーンリーダーはインストールされているだろうか。まだならば、 NVDA日本語版 を確認してインストールする。JAWSは有償のソフトなのでこちらはオプションで。 Git for Windowsのセットアップ WindowsでもGitが使えると便利だからというのもあるが、これに付属しているMSYS2のサブセットがほしいのだ。これでMacがわにリモート接続できる。 Git for Windowsのページ を確認して、最新版をインストール。 Git Bashはインストールできただろうか。開いてみよう。ひょっとしたら、スクリーンリーダーによっては正しく読み上げが行われないかもしれない。 このページ とかを参考に、JAWSは設定する。NVDAはそのままでも動きそうだ。フォントをラスターにすると動くという情報もある。 Bashが起動したら、とりあえずMacに接続してみよう。<ホスト名>.localで接続できるはずだ。私はホスト名がsentaroなので、 $ ssh sentaro.local となる。macOS側でリモート接続を受けられるようにしておく必要がある。 Visual Studio Codeのインストール 参考サイトもたくさんあるので、それらを参考にインストールする。 ここらへん から希望のものをダウンロードしてインストールする。特別難しそうなところはないと思う。必要ならば日本語化をするのが良さそう。 Android開発環境のインストール 公式のページ にアクセスして、パッケージをダウンロードしてインストールする。 Flutterのインストール ここらへん を見て、インストールする。Git Bashを開いて、下記コマンドを実行するのもありなんだって。 ~$ cd /c/ c$ git clone https://github.com/flutter/flutter.git -b stable VSCodeの拡張機能をインストール 拡張機能のインストールに関しては参考サイトがたくさんあるので、そちらも参考にしてほしい。 簡単な手順はこんな感じ。 Ctrl+Shift+Xを押す。 拡張機能の検索ボックスにフォーカスが行くので、"flutte"と入力。 Tabキーを何度か押して検索された拡張機能のリストにフォーカスを置く。 上下矢印キーで、"flutter"を探す。 あったら、Tabキーを押して[インストール]ボタンを探して押す。 他にも入れたいプラグインがあったらここでごそごそ入れる。 最後に 長かった、本当に長い。まだアプリは一つもできてない。これが準備だとはね。 でもね、これで開発ができるのです。ほしいと思っているアプリを開発する準備はできたのです。次はアプリを開発するための学習と、なんとしてもアプリを作ってやろうという根気と執念と怨念(なにを恨むんだ?)です。 そして、ここまで読んでくださったソフトウェア業界の人事の方、ひょっとしたら当事者をエンジニアとして採用すると、十分に開発に貢献してくれるかもしれないですよ。執念は必要ですが、開発チームに加わっていける環境はなんとか整います。まだまだいろいろと改善されるべきですし、社会的環境も整わなければならないでしょう。でも、あなたの会社がその一歩を踏み出すことで、社会的環境は改善されるのです。どうか検討してみてください。 お・ま・け マイコン関連も同じようなものだよお。とりあえず趣味でやるなら、Arduinoも使える。SPresenseはハードウェア持ってないけど、要するにC++コンパイラだから使えるだろう。 やってみたいことができるだけハードル低く試せる時代になるために、もう少し頑張らないとね。 では、この先、開発編に続くのである。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む