20211201のiOSに関する記事は5件です。

Flutterの開発環境構築方法(Mac)

はじめに 本記事は エムティーアイ Advent Calendar 2021 の1日目の記事です。 Flutterの開発環境を構築する方法を紹介します。 ちなみに業務ではFlutterを一切触っていません。 昨日行われた FlutterKaigi のハンズオンで初めてFlutterを触り、その準備時に行ったことを残します。 環境 OS:macOS Big Sur 11.6 ハード:MacBook Air (M1, 2020) Flutter:2.5.1 開発環境の構築 公式ドキュメントに沿ってFlutterの開発環境を構築します。 Flutter SDKのダウンロード 以下のページから、使いたいバージョンのFlutter SDKをダウンロードします。 特に決まりがなければ、Stable channelの最新を使うのがいいと思います。 ダウンロードが完了したら解凍します。 場所はどこでもいいですが、本記事では公式ドキュメント通り ~/development フォルダを作成して、そこに展開します。 以下はStable channelの 2.5.1 を解凍する例です。 $ cd ~ $ mkdir development $ cd development/ $ unzip ~/Downloads/flutter_macos_2.5.1-stable.zip Flutter SDKのパスを通す Flutter SDKを展開したら、パスを通します。 私はBashを使っているので .bash_profile に以下を追記します。 .bash_profile + export FLUTTER_HOME="${HOME}/development/flutter" + if [ -d "${FLUTTER_HOME}" ]; then + export PATH="${FLUTTER_HOME}/bin:$PATH" + fi .bash_profile を再読み込みし、 flutter --version でバージョンが出力されたらOKです。 $ source ~/.bash_profile $ flutter --version Flutter 2.5.1 • channel stable • https://github.com/flutter/flutter.git Framework • revision ffb2ecea52 (2 months ago) • 2021-09-17 15:26:33 -0400 Engine • revision b3af521a05 Tools • Dart 2.14.2 どうやらDartもFlutter SDKに同梱されているようです。 flutter doctorを実行する flutter コマンドが使えるようになったら flutter doctor を実行して、他にインストールすべき依存関係がないか確認します。 太字で×が付いたタスクがあればそれを実行します。 $ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 2.5.1, on macOS 11.6 20G165 darwin-arm, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3) [!] Xcode - develop for iOS and macOS ✗ CocoaPods not installed. CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins To install see https://guides.cocoapods.org/using/getting-started.html#installation for instructions. [✓] Chrome - develop for the web [!] Android Studio ✗ Unable to find bundled Java version. [✓] Android Studio (version 2020.3) [✓] VS Code (version 1.60.0) [✓] Connected device (1 available) ! Doctor found issues in 2 categories. 最初は4つ×があったのですが、2つ解決したので2つのみ×が出力されています。 でも解決した2つとも覚えているので、それらの対応を紹介します。 ちなみに私はXcodeとAndroid Studioをインストール済でした。 未インストールの人は、インストールから行ってといわれると思います。 Android SDK Command-line Toolsがインストールされていない 以下のエラーが出力された場合、Android SDK Command-line Toolsがインストールされていません。 [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3) ✗ cmdline-tools component is missing Run `path/to/sdkmanager --install "cmdline-tools;latest"` See https://developer.android.com/studio/command-line for more details. Android Studioから「Android SDK Command-line Tools (latest)」をインストールすればOKです。 Androidのライセンスに同意していない 以下のエラーが出力された場合、Androidのライセンスに同意していません。 [!] Android toolchain - develop for Android devices (Android SDK version 30.0.3) ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses エラーの内容通り flutter doctor --android-licenses を実行し、必要なライセンスにすべて同意します。 $ flutter doctor --android-licenses 3 of 7 SDK package licenses not accepted. 100% Computing updates... Review licenses that have not been accepted (y/N)? y Accept? (y/N): y 再度コマンドを実行して「All SDK package licenses accepted.」が出力されればOKです。 $ flutter doctor --android-licenses All SDK package licenses accepted.======] 100% Computing updates... CocoaPodsがインストールされていない 以下のエラーが出力された場合、CocoaPodsがインストールされていません。 [!] Xcode - develop for iOS and macOS ✗ CocoaPods not installed. CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that responds to your plugin usage on the Dart side. Without CocoaPods, plugins will not work on iOS or macOS. For more info, see https://flutter.dev/platform-plugins To install see https://guides.cocoapods.org/using/getting-started.html#installation for instructions. 私はrbenv + BundlerでCocoaPodsを管理したいので、こちらのエラーは無視しました。 Flutterによる開発ではCocoaPodsをグローバルインストールするのが一般的なのでしょうか? もし知っている人がいたら教えてほしいです。 Android Studioに同梱されているJDKが見つからない 以下のエラーが出力された場合、Android Studioに同梱されているJDKが見つかりません。 [!] Android Studio ✗ Unable to find bundled Java version. 結論からいうと、こちらのエラーは解決できませんでした。 解決方法をご存知の人がいたら教えてほしいです。 参考までに試したことを残しておきます。 どこかのタイミングでAndroid Studioに同梱されているJDKのパスが変わったのが原因だと思い、その対応を行いました。 - /Applications/Android\ Studio\.app/Contents/jre/jdk/Contents/Home + /Applications/Android\ Studio\.app/Contents/jre/Contents/Home 私はAndroidアプリ開発以外でJavaを使わないため、 .bash_profile で JAVA_HOME にAndroid Studioに同梱されているJDKをセットしてパスを通します。 以前の jdk を含んだパスを指定します。 .bash_profile + export JAVA_HOME=/Applications/Android\ Studio\.app/Contents/jre/jdk/Contents/Home + if [ -d "${JAVA_HOME}" ]; then + export PATH="${JAVA_HOME}/bin:$PATH" + fi このままだとパスが見つからないので、以前のパスにシンボリックリンクを貼ります。 $ cd /Applications/Android\ Studio\.app/Contents/jre $ ln -s ../jre jdk $ ln -s "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin" jdk $ cd - これで JAVA_HOME をセットしてパスを通せたのですが、それでも解決されませんでした。なぜでしょうか…。 Android Studioプラグインのインストール Android StudioにFlutterのプラグインをインストールします。 プラグインの「Marketplace」タブを開き、「flutter」で検索すると簡単にインストールできます。 Dartのプラグインが必要だといわれるので、「Install」を押下します。 Android Studioを再起動し、「Installed」タブに「Dart」と「Flutter」があればOKです。 ちなみにどちらのプラグインもOSSで開発されています。 おまけ: Flutter SDKの管理ツール 本記事ではFlutter SDKを手動で管理していますが、それだと複数のプロジェクトで異なるバージョンを使いたいときに困ります。 Flutterには fvm や asdf などの管理ツールがあり、これらを使うことで手動管理のデメリットを解消できます。 TwitterでFlutter SDKをどのように管理しているかアンケートを取ってみました。 結果はfvmが最も多かったです。 ただasdfも流行ってきているとのことなので、各管理ツールのメリットやデメリットを調査し、プロジェクトに合ったツールを選定するのがよさそうです。 おわりに まだ不十分なところもありますが、Flutterの開発環境が構築できました! FlutterでUIを気持ちよく組み立てていきましょう 以上 エムティーアイ Advent Calendar 2021 の1日目の記事でした。 参考リンク https://twitter.com/the_uhooi/status/1465571548463464451?s=20 1. Flutter 環境構築 · FlutterKaigi/atomic_design_handson Wiki flutter doctorで「Unable to find bundled Java version.」となる場合の解決法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Factory Method パターンについてまとめてみた【Swiftでデザインパターン攻略 #1】

はじめに 本記事では、GoFの23種類のデザインパターンの1つでもある、FactoryMethodパターンについて書いてみました。 FactoryMethodパターンとは 概要 「インスタンスの生成をサブクラスに実行させ、柔軟にインスタンス生成を行うためのパターン」のことです。 インスタンスを生成する側は、インスタンスについて直接意識しなくても、利用することができます。(Protocolを介しているため) Wikipediaより このパターンの進化前(?)のパターンで、Factoryパターン(SimpleFactory)というのがあります。 Factoryパターンは、1つのファクトリの中で、オブジェクトの種類の選択を動的に行うことが特徴です。 それに対し、FactoryMethodパターンでは、生成するオブジェクトごとに、Factoryクラスを用意することで、コードの冗長化や柔軟性を高めていることが特徴です。 SwiftにおけるFactoryMethodパターン 個人的な意見として、Swiftはenum型が強すぎて、Factoryパターンのほうが使用頻度が高い気がします。 Factoryパターンについては、下記をご覧ください。 実装してみた 今回のケースは、 従業員が職種別に存在する会社がある。 その会社の社員を職種で判断し、仕事(Task)を実行させたいと思います。 import UIKit // MARK: Product(インスタンス化される側)のプロトコル protocol Employee: AnyObject { var name: String { get set } var department: String { get } func executeTask() } // MARK: Creator(インスタンス化する側)のプロトコル protocol Factory: AnyObject { func create(name: String) -> Employee func createEmployee(name: String) -> Employee func registerEmployee(_ employee: Employee) } // MARK: Productを実装した具象クラス class Developer: Employee { var name: String var department: String { return "開発部" } init(name: String) { self.name = name } func executeTask() { print("\(department)の\(name)は、コーディングを行います。") } } // MARK: Creatorを実装した具象クラス class DeveloperFactory: Factory { var employeeList: [Employee] = [] func create(name: String) -> Employee { let employee = createEmployee(name: name) registerEmployee(employee) return employee } func createEmployee(name: String) -> Employee { return Developer(name: name) } func registerEmployee(_ employee: Employee) { employeeList.append(employee) } } // MARK: 実行 let factory = DeveloperFactory() let emp1 = factory.create(name: "山田") emp1.executeTask() // 開発部の山田は、コーディングを行います。 let emp2 = factory.create(name: "田中") emp2.executeTask() // 開発部の田中は、コーディングを行います。 冒頭でも述べたように、生成するインスタンスごとに、Factoryクラスを追加するのが特徴であるため、もし営業部を追加したいということであればこんな感じで追加します。 class SalesPerson: Employee { var name: String var department: String { return "営業部" } init(name: String) { self.name = name } func executeTask() { print("\(department)の\(name)は、得意先への訪問を行います。") } } class SalesPersonFactory: Factory { var employeeList: [Employee] = [] func create(name: String) -> Employee { let employee = createEmployee(name: name) registerEmployee(employee) return employee } func createEmployee(name: String) -> Employee { return SalesPerson(name: name) } func registerEmployee(_ employee: Employee) { employeeList.append(employee) } } // MARK: 実行 let salesFactory = SalesPersonFactory() let emp3 = salesFactory.create(name: "佐藤") emp3.executeTask() 以上!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]async/awaitを使ってGitHubAPIを叩く(UIKit版)

概要 Swift5.5から新しく登場したasync/awaitによる非同期処理の学習でGitHub APIを叩いてリポジトリを検索する機能を実装したので記事にします。 開発環境 Swift 5.5 Xcode 13.1 サンプルプロジェクト GitHubにPushしました。気になる方はご覧ください。 https://github.com/ken-sasaki-222/AsyncAwaitTrainingInUIKit レスポンスをもとにModelを作成 { "total_count": 223653, "incomplete_results": false, "items": [ { "id": 44838949, "node_id": "MDEwOlJlcG9zaXRvcnk0NDgzODk0OQ==", "name": "swift", "full_name": "apple/swift", "private": false, "owner": { "login": "apple", "id": 10639145, "node_id": "MDEyOk9yZ2FuaXphdGlvbjEwNjM5MTQ1", "avatar_url": "https://avatars.githubusercontent.com/u/10639145?v=4", "gravatar_id": "", ... }, "html_url": "https://github.com/apple/swift", "description": "The Swift Programming Language", ... 今回はサンプルプロジェクトなのでfull_nameと、descriptionだけ取得します。 SearchResponseModel.swift import Foundation struct SearchResponseModel: Decodable { var items: [Item] } struct Item: Decodable { var full_name: String var description: String? } 実装 処理順序 依存関係逆転の原則(DIP)を利用して今回は開発しています。 DataStore SearchRepositoryDataStore.swift import Foundation class SearchRepositoryDataStore { private let baseUrl = "https://api.github.com/" private let shared = URLSession.shared private let decoder = JSONDecoder() func searchRepositories(query: String) async throws -> SearchResponseModel { let queryString = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) let urlString = baseUrl + "search/repositories?q=\(queryString ?? "")" guard let url = URL(string: urlString) else { throw NSError(domain: "error", code: -1, userInfo: nil) } let request = URLRequest(url: url) let (data, _) = try await shared.data(for: request) let response = try decoder.decode(SearchResponseModel.self, from: data) return response } } APIに通信するDataStoreです。検索結果をRepositoryに返します。 async throwsと書くことでエラーが発生する可能性のある非同期関数を意味します。エラー処理はthrowで返します。非同期処理を行う箇所でtry awaitを書くことで同期的な書き方ができます。 これまでの@escapingでの非同期処理に比べて、onFailre()などの意識しないといけない箇所が減ったので可読性だけでなく安全性も高くなっています。 Repository SearchRepositoryInterface.swift import Foundation protocol SearchRepositoryInterface { func searchGitHubRepository(searchText: String) async throws -> [Item] } SearchRepository.swift import Foundation class SearchRepository: SearchRepositoryInterface { private let searchRepositoryDataStore = SearchRepositoryDataStore() func searchGitHubRepository(searchText: String) async throws -> [Item] { do { let response = try await searchRepositoryDataStore.searchRepositories(query: searchText) let items = response.items return items } catch(let error) { throw error } } } SearchRepositoryDataStoreへ検索ワードを渡してdo catchで値を受け取ります。受け取った結果はasync throwsでViewControllerに返します。 ポイントは非同期関数を呼ぶ箇所でawaitしている点です。これまではクロージャーで受け取っていた箇所が整理されて可読性が高まっています。 ViewController SearchViewController.swift import UIKit class SearchViewController: UIViewController { @IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var tableView: UITableView! private var searchRepository: SearchRepositoryInterface private var indicator = UIActivityIndicatorView() private var items: [Item] = [] init(searchRepository: SearchRepositoryInterface) { self.searchRepository = searchRepository super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { self.searchRepository = RepositoryLocator.getSearchRepository() super.init(coder: coder) } override func viewDidLoad() { super.viewDidLoad() setUpSearchBar() setUpTableView() setUpIndicator() } private func setUpSearchBar() { searchBar.delegate = self } private func setUpTableView() { tableView.delegate = self tableView.dataSource = self } private func setUpIndicator() { indicator.center = view.center indicator.style = .large indicator.color = .lightGray view.addSubview(indicator) } private func refreshTableView() { self.items = [] tableView.reloadData() } private func showSearchTextIsEmptyAlert() { let alert = UIAlertController(title: "確認", message: "リポジトリ名を入力してください", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "やり直す", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } private func showSearchResponseErrorAlert() { let alert = UIAlertController(title: "エラー", message: "検索に失敗しました", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "やり直す", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } @IBAction func tapRefreshButton(_ sender: UIBarButtonItem) { refreshTableView() searchBar.text = "" } } extension SearchViewController: UISearchBarDelegate { func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { searchBar.showsCancelButton = true return true } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { searchBar.showsCancelButton = false view.endEditing(true) } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { indicator.startAnimating() refreshTableView() if let searchText = searchBar.text { if searchText == "" { showSearchTextIsEmptyAlert() indicator.stopAnimating() } else { Task { do { var items = try await searchRepository.searchGitHubRepository(searchText: searchText) print("items:", items as Any) if items.count == 0 { let emptyItem = Item(full_name: "検索結果0件", description: "") items.append(emptyItem) self.items = items tableView.reloadData() indicator.stopAnimating() } else { self.items = items tableView.reloadData() indicator.stopAnimating() } } catch(let error) { print("error:", error.localizedDescription) indicator.stopAnimating() showSearchResponseErrorAlert() } } } } } } extension SearchViewController: UITableViewDelegate, UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) let name = cell.viewWithTag(1) as? UILabel let description = cell.viewWithTag(2) as? UILabel name?.text = items[indexPath.row].full_name if description?.text != "" { description?.text = items[indexPath.row].description } else { description?.text = "not description text." } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } } UISearchBarの検索ボタンタップでテキストが空文字でなければ検索を開始するシンプルな機能です。 ポイントは非同期処理を開始する箇所をTaskで囲んでいる点です。同期処理から非同期処理を呼ぶ場合、Taskで囲む必要があります。囲んだ後は同期的に書いてtry awaitで非同期処理を呼べばOKです。 Repositoryのasync throwsから返ってきた結果をdo catchで受け取ってそれぞれ処理します。 検索結果画面 おわりに 今回はasync/awaitを使ってGitHub APIを叩きました。 これまで@escapingで書いてたときのクロージャラッシュに比べると、安全性が向上し可読性が高くなった印象です。また同期的に書ける点も嬉しいですね。 ご覧いただきありがとうございました。 こうしたほうがいいや、ここはちょっと違うなど気になる箇所があった場合、ご教示いただけると幸いです。 おまけ データに依存するので完全なテストではないですが、DataStoreの動作確認レベルでUnitTestを書きました。 SearchRepositoryDataStoreTests.swift import XCTest class SearchRepositoryDataStoreTests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } func test_searchRepositoryDataStore() async throws { let dataStore = SearchRepositoryDataStore() let searchText = "Swift" let response = try await dataStore.searchRepositories(query: searchText) let items = response.items print("Success search repository dataStore.") print("items:", items) XCTAssert(items.count > 0) } func testPerformanceExample() throws { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ここでもasync throwsとtry awaitが登場してますね。これまでのXCTestExpectationがなくても非同期テストを実行できます。 お知らせ iOSアプリ開発案件を募集しています!(副業) ▼おおよその稼動可能時間 - 月の稼動30-40時間 - 平日の夜19:00以降 - 土曜日終日稼働可 Twitter DMでご依頼お待ちしております!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift5 全予約語 (106語) の解説

ドキュメントから漏れている予約語もあるため、下記情報を統合してカウントしています。 * The Swift Programming Language (Swift 5) Lexical Structure * The Swift Programming Language (Swift 5) Summary of the Grammar * https://github.com/apple/swift/blob/main/utils/gyb_syntax_support/Token.py * https://github.com/apple/swift/tree/main/lib/Parse 宣言 (declarations) 型、変数、定数、メソッド、関数などの宣言部分で使用する予約語 actor [Swift 5.5~] Swift の型の種類の一つである Actor 型を宣言します。 actor SampleActor { var mutableValue = "mutable" let constValue = "const" func defaultFunc() {} nonisolated func nonisolatedFunc() {} nonisolated var nonisolatedComputedProperty: String { "computedProperty getter" } } Actor を使うことで、複数スレッドからの変更可能データに対する同時アクセスによって発生するデータの破損(data race)を防ぐコードをコンパイラのサポートを受けながら書けるようになります。 Actor の概要は Swift LanguageGuide > Concurrency > Actors に記載されています。実際のアプリ開発での使い所のイメージを掴んでみたい場合は WWDC21 Swift concurrency: Update a sample app が分かりやすくてオススメです。  詳細 Actor の特徴は並列処理の実行環境下でのデータの保護です。(より厳密に言うなら、actor の instance を通すことで、隔離された状態に同期的にアクセスする機能を提供するものです。) Actor の持つ変更可能なデータや method に対して同時に複数のタスクからアクセスされないように自動的に制御されます。他のタスクからアクセスされている場合はアクセス可能になるまで待機します。そのため、アクセスする場所では await でマークしておく必要があります。 await sampleActor.mutatingValue await sampleActor.defaultFunc() 変更不可なデータである定数や nonisolated が付与されている method/computed property は data race が発生しないことが保証されているため複数タスクから同時にアクセスしても問題ありません。そのため、 await を必要とせずにアクセスできます。この状態を「隔離されていない(non-isolated)」と表現します。これについては nonisolated の項目 に記載しています。 sampleActor.constValue sampleActor.nonisolatedFunc() sampleActor.computedProperty 関連: nonisolated, await async [Swift 5.5~] 1. function 宣言部の async 非同期の function を表します。 func listPhotos(inGallery name: String) async -> [String] { let result = // ... some asynchronous networking code ... return result } DB 操作やネットワーク処理などの非同期の処理を待つ、つまり、途中で関数内の処理を中断・再開するような関数を表します。非同期関数を呼ぶには await を使って結果を待つ(suspension point)ことを明示します。 let photoNames = await listPhotos(inGallery: "Summer Vacation") 2. async let 非同期関数の結果を受け取る変数を宣言します。let thumbnail = await downloadImage(...) とする場合と違い、実際に値を使う部分に到達するまで待機しません。 下記の例では thumbnail と authorImage の download が並列で実行され、値が使われる箇所(let photos = ...)で download の終了を待ちます。 async let thumbnail = downloadImage(url: bookImageUrl) // download 開始 async let authorImage = downloadImage(url: authorImageUrl) // download 開始 let photos = await [thumbnail, authorImage] // thumbnail と authorImage の download が終わるまで待機 関連: await await [Swift 5.5~] 1. await + fuction/method 非同期関数の終了を待つ部分(suspension point)を表します。 // download が終わるまで次の処理に進まない。 // (thread を一時的に解放して他の待機中のタスクが実行される) let photo = await downloadPhoto(named: name) show(photo) 2. for await-in loop AsyncSequence に対して loop を回す場合は sequence から要素が渡されるまで待機します。 for await i in Counter(howHigh: 10) { print(i, terminator: " ") } class 場所によって意味が異なります。 クラスの宣言や、メソッドやプロパティの前に指定することでクラスメソッド、クラスプロパティの宣言をします。 class Sample { class func f() {} class var classProperty: String { "classProperty" } } プロトコルの適用先をクラスだけに制限したい場合 protocol SampleProtocol : class {} class Sample : SampleProtocol {} deinit デストラクタの宣言をします。これは class でのみ有効です。 スーパークラスの deinit はサブクラスの deinit 実行後に自動的に呼ばれます。 class SuperClass { deinit { print("Super Class's deinit is called") } } class SubClass: SuperClass { deinit { print("Sub Class's deinit is called") } } var subClass: SubClass? = SubClass() subClass = nil 出力 Sub Class's deinit is called Super Class's deinit is called enum 列挙型の宣言をします。 enum SampleEnum { case a case b(Int) } extension 定義済みのクラス、構造体、プロトコル、列挙型に対して function または computed property を追加します。 extension Array { func f() {} } let intArray = [1] intArray.f() protocol の適合の追加や extension SampleClass: HogeDelegate { func f() {} } where で適応する条件を指定できます。 extension Array where Element == Int { func funcForIntArray() {} } intArray.funcForIntArray() fileprivate アクセス修飾子の一種です。 同じファイル内からのアクセスが可能です。 関連: public, private, internal, open 参考: The Swift Programming Language # Access Control func function の宣言をします。 import モジュールを読み込みます。 init コンストラクタを宣言します。 inout intout 指定された引数として渡された変数は、function 内での変更が適応されます。(表面的には参照渡しに似た動作になります) 具体的には下記の動作をします。 function の呼び出し時に、引数として与えられた値がコピーされ、そのコピーが function の中で使われます。 function の終了時に引数として与えられた変数にコピーが代入されます。 class SampleClass {} func inoutSampleForClass(sampleClass: inout SampleClass?) { sampleClass = nil } func withoutInoutSampleForClass(var sampleClass: SampleClass?) { sampleClass = nil } var sampleClass: SampleClass? = SampleClass() // inout なし withoutInoutSampleForClass(sampleClass) sampleClass // => SampleClass のインスタンス // inout あり inoutSampleForClass(&sampleClass) sampleClass // => nil func inoutSample(a: inout Int) { a = 100 } func withoutInoutSample(var a: Int) { a = 100 } var varInt = 1 // inout なし withoutInoutSample(varInt) varInt // => 1 // inout あり inoutSample(&varInt) varInt // => 100 参考: In-Out Parameters internal アクセス修飾子の一種です。 同じモジュール内からアクセスできます。 アクセス修飾子をつけなかった場合、デフォルトで internal となります。 関連: public, private, open, fileprivate 参考: The Swift Programming Language (Swift 3) # Access Control let 定数を宣言します。 関連: var open アクセス修飾子の一種です。アクセス制限の種類の中で最もアクセス制限の緩い指定です。 外のモジュールからのアクセス、継承・オーバーライドが可能です。 性質上、この制限を指定できるのは class または オーバーライド可能な class member のみです。 public との違いは、継承・オーバーライドの許可範囲 (外のモジュールからの継承・オーバーライド可能性) です。 関連: public, private, internal, fileprivate 参考: The Swift Programming Language # Access Control operator 独自の演算子を定義します。 演算子として採用できる文字については規定があります。詳細は iOS Developer Library の Language Reference > Lexical Structure > Operators をご覧ください。 関連: prefix, postfix, infix prefix operator ☁ prefix func ☁ (a: inout Int) -> Int { a *= a return a } postfix operator *** // 複数文字列も可能 postfix func *** (a: inout Int) -> Int { a *= a return a } infix operator ☁ func ☁ (left: Int, right: Int) -> Int { return left + right } var hoge = 2 ☁hoge // => 4 hoge*** // => 16 1 ☁ 2 // => 3 precedencegroup 左右に被演算子をとる演算子の優先度グループを定義します。 higherThan で、どのグループより優先度が高いか指定できます。lowerThan では、他のモジュールのどのグループより優先度が低いか指定できます。associativity と assignment も指定できます。 演算子を定義する構文 infix operator <#operator name#> : <#precedence group name#> +++が先に評価される場合 precedencegroup GroupA { associativity: none } precedencegroup GroupB { higherThan: GroupA associativity: none } infix operator ***: GroupA infix operator +++: GroupB func *** (left: Int, right: Int) -> Int { return left * right } func +++ (left: Int, right: Int) -> Int { return left + right } 1 +++ 1 *** 0 // => 0 関連: operator, associativity, assignment private アクセス修飾子の一種です (型、変数、定数、function の公開範囲の指定) 最も制限の強いアクセス修飾子で、下記の条件を満たす場合のみにアクセスできます。 同じファイル内 同じ型 class A { private var privateVar = "" } extension A { func f() { // ✅OK print(privateVar) } } class B { let a = A() func f() { // ❗️error: 'privateVar' is inaccessible due to 'private' protection level a.privateVar } } 関連: public, internal, open protocol プロトコルを宣言します。 public アクセス修飾子の一種です (クラス、変数、定数、メソッド、関数の公開範囲の指定) 同じターゲット(モジュール)外からアクセス可能になります。open と異なり、継承は禁止されます。 主に、ライブラリなどで API として公開するものに対して指定します。 関連: private, internal, open static static 変数や static function を宣言します。 protocol での宣言時 (下記参照) は class func と class var も意味に含みます。 protocol SampleProtocol { static func staticFuncInProtocol() static var staticVarInProtocol: Int { get } } class Sample { // `static func staticFuncInProtocol()` can be implemented by `static func` or `class func` class func staticFuncInProtocol() {} // ditto class var staticVarInProtocol: Int { 1 } } struct 構造体を宣言します。 subscript クラスや構造体に [] を実装します。 Objective-C の場合についてはクラスに [], {} を実装するに書いてみました。 実装例 class SubscriptSample { var hoge: Any? subscript(index: Int) -> String { get { return "Int もらいました" } set { hoge = newValue } } subscript(index: String) -> String { get { return "String もらいました" } // setter なくても良いです } subscript(index: AnyObject?) -> String { return "何かもらいました" } subscript(x: Int, y: Int) -> String { return "\(x), \(y)" } subscript() -> String { return "nothing" } } 使用例 let subscriptSample = SubscriptSample() var optionalSample: Int? = 1; subscriptSample[3] // => "Int もらいました" subscriptSample["a"] // => "String もらいました" subscriptSample[nil] // => "何かもらいました" subscriptSample[optionalSample] // => "何かもらいました" subscriptSample[1, 2] // => "1, 2" subscriptSample[] // => "nothing" typealias 型の別名を宣言(*1)、または、associated type に対して型を指定(*2)します。 型の別名を宣言(*1) typealias IntAlias = Int typealias Point = (Int, Int) 付属型に対して型を指定(*2) protocol P { associatedtype T } struct S: P { typealias T = Any } 参考: The Swift Programming Language (Language Reference -> Declaration -> Type Alias Declaration) The Swift Programming Language (Language Reference -> Declaration -> Protocol Associated Type Declaration) associatedtype associated type (付属型) の宣言をします。 protocol SampleProtocol { associatedtype AssociatedType // 付属型を宣言します func sampleFunc(param :AssociatedType) -> AssociatedType } struct SampleStruct: SampleProtocol { func sampleFunc(param: Int) -> Int { // 付属型が Int であると決定されます return param + param } } 参考: The Swift Programming Language (Language Reference -> Declaration -> Type Alias Declaration) The Swift Programming Language (Language Reference -> Declaration -> Protocol Associated Type Declaration) var 変数を宣言します。 Keywords used in statements break switch 文やループから抜けます。 for, while の前にラベルをつけることで抜けるブロックを指定できます。 詳細: document の Control Flow -> Labeled Statements var i = 0 firstLoop: while true { print("first loop: \(i)") while true { i += 1 print("second loop: \(i)") switch i { case 5: print("break firstLoop") break firstLoop default: break } } } print("finish: \(i)") first loop: 0 second loop: 1 second loop: 2 second loop: 3 second loop: 4 second loop: 5 break firstLoop finish: 5 関連: continue, fallthrough case 列挙子リストの宣言、switch 文内で条件分岐、switch 文内以外で if, for との併用により、パターンマッチングができます。 let optionalSample: Int? = 1; let optionalArraySample: [Int?] = [1, 2, nil, 3] if case let x? = optionalSample { print("optionalSample: \(x)") } for case let x? in optionalArraySample { print("optionalArraySample: \(x)") } optionalSample: 1 optionalArraySample: 1 optionalArraySample: 2 optionalArraySample: 3 参考: Swift 2: Pattern Matching with “if case”, The Swift Programming Language (Patterns -> Optional Pattern) 関連: enum continue 次のループ処理へ移動します。 break と同様にラベルをつけることで移動するループ処理を指定することができます。 var i = 0 firstLoop: while true { print("first loop: \(i)") if i != 0 { break } while true { i += 1 print("second loop: \(i)") switch i { case 5: print("continue firstLoop") continue firstLoop default: break } } } print("finish: \(i)") first loop: 0 second loop: 1 second loop: 2 second loop: 3 second loop: 4 second loop: 5 continue firstLoop first loop: 5 finish: 5 関連: break, fallthrough default switch 文内の条件分岐で、case に当てはまらなかった場合の処理の宣言をします。 関連: switch, case defer スコープを抜ける際に実行する処理を記述します。 func deferSample() { defer { print("in defer") } print("end of scope") } deferSample() end of scope in defer 参考: The Swift Programming Language (Statements -> Defer Statement) The defer keyword in Swift 2: try/finally done right do スコープを作成します。 catchを繋げることで、スコープ内で発生した例外をcatch文で処理することができます。 関連: catch, try else 条件分岐で使用します。 また、guard文では文法上必須となります。 関連: if, guard fallthrough switch 文の中で、マッチした case 文の次の case 文または default 文内の処理を実行します。 (厳密には、fallthrough の所属している case ブロックの中から抜け、所属する case 文の次の case 文または default 文内の処理を実行します) 使用例 let a = 1 switch a { case 1: print("1") fallthrough case 2: print("2") default: print("default") } C言語の場合(上記と同等の処理) switch (a) { case 1: printf("1\n"); case 2: printf("2\n"); break; default: printf("default\n"); } 1 2 補足 下記の補足です。 厳密には、fallthrough の所属している case ブロックの中から抜け、所属する case 文の次の case 文または default 文内の処理を実行します。 下記のように、fallthrough が呼ばれると、即座に case ブロックから抜けます。 そのため、その下の print("1-2") は実行されません。 switch 1 { case 1: defer { print("defer") } // ブロック(スコープ) から抜ける様子を観察 print("1-1") if true { fallthrough } print("1-2") case 2: print("2") default: print("default") } 1-1 defer 2 for 繰り返し処理を記述できます。 for-in for i in 1...10 { // 処理 } for _ in 1...10 { // 処理 } 関連: in, while guard 変数または定数が条件に一致するか評価し、一致していない場合、続く else のブロックが実行されます。 このブロックの中では必ず以下のいずれかを使用し、guard が記述されているスコープを抜けなくてはいけません。 return break continue throw fallthrough fatalError 等の Never 型を返す関数の呼び出し func guardSample1(value: Int?) -> String { guard let value = value, value > 10 else { // この中では必ずスコープを抜ける処理を書きます return "in else block" } // ここからは value がアンラップされ、また、10 より大きいことが保証されます return "\(value)" } guardSample1(nil) // => in else block guardSample1(10) // => in else block guardSample1(100) // => 100 func guardSample2(a: String, b: Int?) -> String { // 複数の変数・定数の評価もできます guard let intValue = Int(a), let b = b else { return "in else block" } // ここからは b がアンラップされます return "\(intValue + b)" } guardSample2("a", b: 1) // => in else block guardSample2("1", b: 1) // => 2 guardSample2("1", b: nil) // => in else block 関連: return, break, continue, throw if 続く条件を評価し、一致した(true)場合ブロック内を実行します。 unwrap する場合にも使用します。この構文を Optional binding と言います。詳細はこちら ([Swift] Optional 型についてのまとめ Ver2) に解説されていて分かりやすかったです。 func ifLetSample(value: Int?) { if let a = value { type(of: value) // Optional<Int>.Type type(of: a) // Int.Type } } 関連: else, guard in 文脈によって下記のように意味が変わります。 クロージャのボディの開始箇所を表します for ~ in の形で取り出す要素の配列を指定します 関連: for repeat C言語等の言語における do {...} while(...) の do の役割です。 関連: while return 返り値を指定します。 返り値は下記の 2 パターンです。 要素が一つの場合はその要素 それ以外の場合は tuple func sample() { return } var a = sample() // -> () 参考: Remove @noreturn attribute and introduce an empty Never type switch 条件分岐を行います。 switch (1, "a") { case (1, "b"): print("1, b") case (1, _): print("1, _") // ここがマッチします。 (_ はワイルドカード) case (1, "a"): print("1, a") // 上がマッチするので評価されません } 関連: case where マッチングの条件を追加します。 while 下記の2種類の繰り返し構文を記述できます。 while condition { statements } repeat { statements } while condition 関連: repeat Keywords used in expressions and types as 大きく分けて、2 種類の役割があります。 * キャスト class A {} let anyObj: AnyObject = A() let a = anyObj as! A // AnyObject から A にキャスト 型を明示すること let v = 1 as Double Any すべての型のインスタンス (関数型も含む) を表現します。 実態は空の protocol です。 参考: Type Casting for Any and AnyObject Import Objective-C id as Swift Any type catch 例外が投げられた際にブロック内が実行されます。 false 真偽値リテラルの一種で、偽を表します。 is ある型またはあるプロコトルを実装した型として振る舞えるかどうかを検査します。 1 is Int // -> true (1, 1) is AnyObject // -> false (1, 1) is (Int, Int) // -> true // プロトコルの検査 protocol SampleProtocol { } class SampleClass: SampleProtocol { } let sampleClassInstance = SampleClass() sampleClassInstance is SampleClass // true sampleClassInstance is SampleProtocol // true nil nil リテラルを表します。 Optional.None == nil // -> true rethrows 引数にとったクロージャが投げた例外を呼び出し元に対して更に投げます。 func sample(callback: () throws -> Int) rethrows { try callback() } super 親クラスを表します。 self インスタンスメソッド内などで単独で使用した場合、インスタンス自身を返します expression (式) に対して呼び出した場合、式が評価された値が返ります type (型) に対して呼び出した場合、自身の型が返ります インスタンスメソッド内などで単独で使用した場合、インスタンス自身を返します class Sample { var a: Int? func sampleMethod() -> Sample { a = 1 return self // 自身 (playground 上では Sample と見えますが、プロパティ a が変更されているので上で作成したインスタンスだと確認できます) } } expression (式) に対して呼び出した場合、式がそのまま返ります <#expression#>.self (1 + 1).self の返り値は (1 + 1) という式と同等です。 式がそのまま返るとは (1 + 1).self // (1 + 1) 余談 (1 + 1).self // 2 as Int ではない // 証明 (1 + 1).self + 1.0 // OK (1 + 1) + 1.0 // OK let exp = 1 + 1 // 2 as Int exp + 1.0 // Error (type mismatch) type (型) に対して呼び出した場合、自身の型が返ります <#type#>.self class Sample { } Sample.self // -> Sample.Type Sample.self.init() // -> Sample のインスタンス (= Sample.self は自身の型を返しています) 参考: The Swift Programming Language (Language Reference -> Expressions -> Postfix Self Expression) Self 自身の型を返します。 throw 例外を投げます。 throws メソッド、関数の宣言部に書き、例外が投げられる可能性があることを示します。 true 真偽値リテラルの一種で、真を表します。 try 例外が投げられる可能性のある関数・メソッドを実行します。 Keywords that begin with a number sign (#) #available OS 及びそのバージョンによる条件を表現します。 if #available(iOS 10.0, *) { print("iOS10 or later") } #colorLiteral 下記の構文で色を表現するリテラルを記述できます。 #colorLiteral­(­red­:­ <# expression #>­, ­green­:­ <# expression #>­,­ blue­:­ <# expression #>­,­ alpha­: <# expression #>­)­ 型を明示しない場合は、AppKit モジュールをインポートした場合は NSColor 型、UIKit モジュールをインポートした場合は UIColor 型として解釈されます。 #column #column が評価された場所の列番号 (Int) #if, #else, #elseif, #endif コンパイル時に指定のコードを含めるか否かを制御します。 条件例 #if <# Custom Flag に設定した名前 (設定せずにコンパイルすると無視される) #> print("DEBUG") #endif #if swift(>=3.0) print("Swift3.0") #endif #if arch(arm64) print("arm64") #endif #if os(OSX) print("OSX") #endif Swiftのバージョンを条件に制御する例 #if swift(>=3.0) print(">=Swift3.0") #elseif swift(>=2.0) print(">=Swift2.0") #else print("else") #endif 参考: * The Swift Programming Language > Conditional Compilation Block * CONDITIONAL COMPILATION #error 記載した行に対するエラーを compile 時に発生させます。 関連: #warning #file #file が評価された場所のパス(String)を返します。 ただし、今後の Swift のバージョンで動作の変更があるため #fileID か #filePath への置き換えが推奨されています。 Swift5.5時点では #filePath と同じ動作をしますが、今後のバージョンでは #fileID の動作へ変更されます。 // sample project の中の sampleApp.swift で使った場合 print("#file", #file) print("#filePath", #filePath) print("#fileID", #fileID) 出力 #file /Users/ezura/workspace/article/swift5/sample/sampleApp.swift #filePath /Users/ezura/workspace/article/swift5/sample/sampleApp.swift #fileID sample/sampleApp.swift 関連: #filePath, #fileID 参考: the swift programming language > Expressions > Literal Expression #fileID #fileID が評価されたファイルとそのモジュール名を module名/file名 の形式で返します(String型)。 // sample project の中の sampleApp.swift で使った場合 print("#fileID", #fileID) 出力 #fileID sample/sampleApp.swift 関連: #filePath, #file 参考: the swift programming language > Expressions > Literal Expression #filePath #filePath が評価された場所のパス(String)を返します。 // sample project 内の sampleApp.swift で使った場合 print("#filePath", #filePath) 出力 #filePath /Users/ezura/workspace/article/swift5/sample/sampleApp.swift 関連: #file, #fileID 参考: the swift programming language > Expressions > Literal Expression #fileLiteral 下記の構文でファイルリテラルを記述できます。 #fileLiteral(resourceName: "<# ファイル名 #>") 型を明示しない場合は URL 型として解釈されます。(Foundation モジュールがインポートされている場合のみ) また、ファイルが取得できなかった場合、実行時エラーとなります。 参考: * Modernizing Playground Literals * swift/test/Sema/object_literals_osx.swift #function #function が評価された場所の関数・メソッドの名前 (String) を表現します。 #imageLiteral 下記の構文で画像リソースを表現するリテラルを記述できます。 #imageLiteral(resourceName: "<# ファイル名 #>") 型を明示しない場合は、AppKit モジュールをインポートした場合は NSImage 型、UIKit モジュールをインポートした場合は UIImage 型として解釈されます。 #keypath key や keypath (string literal) を生成します。 class Object: NSObject { let value = 1 var matryoshka: Object? } #keyPath(Object.value) // "value" #keyPath(Object.matryoshka.value) // "matryoshka.value" let object = Object() object.matryoshka = Object() object.value(forKey: #keyPath(Object.value)) // 1 object.value(forKeyPath: #keyPath(Object.matryoshka.value)) // 1 関連: * #keypath の proposal * Swift3ではKVOにkeyPath()式を使っていくのが便利 #line #line が評価された場所の行番号 (Int) #selector Selector を生成します。 Swift3 から、以前の機能に加えて Objective-C で記述された class のプロパティの getter, setter メソッドの Selector を \#selector を用いて生成できるようになりました。(以前は \#selector が対応していなかったため、文字列で指定していました) @interface ViewController : UIViewController @property(assign) int num; @end #selector(getter: ViewController.num) #selector(setter: ViewController.num) 参考: * Using Swift with Cocoa and Objective-C (Swift 3) > Interacting with Objective-C APIs * Hannibal #selector #sourceLocation #line, #file, #filePath, #fileID の値を操作します。 値の変更: #sourceLocation(file: file path, line: line number) 変更をリセット: #sourceLocation() ただし、Playground 上ではリセット後の line の値が不正になるバグがあります。(Xcode13.1時点で確認) 参考:The Swift Programming Language > Line Control Statement #warning 記載した行に対する warning を compile 時に発生させます。 関連: #error #dsohandle #line, #file, #function と同様に、書かれている場所に関する情報を表すキーワードです。 自身が書かれているライブラリがロードされている場所のアドレスを表します。(アドレスなので、他のキーワードと違い、UnsafePointer型です) provides an UnsafePointer to the current dynamic shared object (.dylib or .so file) 引用: Modernizing Swift's Debugging Identifiers 特定の文脈でのみ予約語として使用 assignment 左右に値を取る演算子を宣言した際に、Optional Chaining 評価の一連の流れで演算するかを指定します。 true を指定すると、Optional Chaining 評価の一環として演算を行おうとします。false は既定の動作で、Optional Chaining の評価が終わってから、その評価結果と演算します。 オプショナルチェイニングでの畳み込み precedencegroup OperationFoldedIntoOptionalChaining { assignment: true } precedencegroup OperationOutsideOfOptionalChaining { assignment: false } infix operator ~~ : OperationFoldedIntoOptionalChaining infix operator ~^ : OperationOutsideOfOptionalChaining func ~~ (left: Int, right: Int) -> Int { print("~~") return left + right } func ~^ (left: Int?, right: Int) -> Int { print("~^") return left?.advanced(by: right) ?? -1 } let values = nil as [Int]? values?.count ~~ 5 // => nil values?.count ~^ 5 // => -1 /* ~~ 演算は、 最初に `values` の nil 判定、nil でなければ `.count ~~ 5` を評価 values.map { $0.count ~~ 5 } と同等。この例では `~~` 演算は実行されない。 ~^ 演算は、最初に `values?.count` を評価、続いて `その結果 ~^ 5` を評価 普通に `(values?.count) ~^ 5` としたのと同等。演算子の既定の動作。 */ 関連: infix, operator, precedencegroup associativity 左右に値を取る優先度グループを宣言した際に、結合方向を指定します。 指定できる値は下記の 3 種類です。 left right none: 結合方向を指定しないため、同じグループの演算子同士を並べて使用することができなくなります。 優先度グループを定義する構文 precedencegroup <#precedence group name#> { higherThan: <#lower group name#> associativity: <#left | right | none#> } 同優先度、結合方向 none precedencegroup MyAddition { associativity: none } infix operator +++ : MyAddition infix operator --- : MyAddition func +++ (left: Int, right: Int) -> Int { return left + right } func --- (left: Int, right: Int) -> Int { return left - right } 1 +++ 1 --- 1 /* error: adjacent operators are in non-associative precedence group 'MyAddition' 1 +++ 1 --- 1 ^ ~~~ */ 関連: left, right, none, operator, precedencegroup convenience init の前に記述することで、convenience initializer を宣言します。 関連: init dynamic Objective-C のランタイムを使用して値にアクセスします。 構文 dynamic <#var | let #> <# name #> 詳細 Objective-C runtime と pure Swift では、呼び出す処理を決定する方法 (method dispatch) が異なります。 Objective-C runtime の場合、実行時に Class に対して問い合わせて実行すべき処理を探していきます (message passing 方式)。 例えば、instance.methodA() を実行するとき、instance の持つ Class の情報に対して、"methodA" (呼び出したい function 名) があるか検索します。無かったらその Class の親 Class に対して問い合わせる、なかったら更にその親 Class に、といった処理を実行時に行います。 一方、Swift ではコンパイル時に「型に対してどんな method があるか」という情報を持つ table を作ります。直接呼び出す場合もありますが、継承関係がある場合など、実行時にしか呼び出す処理が決まらない場合はこの table を参照して処理を決定します。重要なのは、この table はコンパイル時に作られてから後で変更できないということです。 Objective-C の場合、Class の情報を書き換えることができます。つまり、実行時に method を増やしたり、method 名を変えたり、処理を入れ替えたりできます。(method swizzling と呼ばれる操作です) このような Objective-C の機能を使いたい場合、dynamic を指定することで、Swift の method table 上には載らず、Objective-C の機構を使って処理するようになります。 didSet Stored プロパティまたは変数の値が変更された際の処理を宣言します。 final 継承、オーバーライドを不可にします。 get 文脈によって意味が異なります。 computed property 内: プロパティにアクセスした際の処理 class A { var value: Int { get { return 1 } } } protocol 内で宣言した場合: 値が取得可能であることの宣言 protocol Sample { var value: Int { get } } class A: Sample { let value = 1 } class B: Sample { var value: Int { return 1 } } infix 左右に被演算子をとる演算子の処理を定義します。 infix operator ☁ func ☁ (left: Int, right: Int) -> Int { return left + right } 1 ☁ 2 // => 3 関連: operator indirect 列挙体を列挙子の中で再帰的に使えるようになります。つまり、Json のような入子構造を表現できます。 enum SampleEnum { case num(Int) indirect case indirectNum(SampleEnum) } または swift indirect enum SampleEnum { case num(Int) case IndirectNum(SampleEnum) } // 入子にできる SampleEnum.indirectNum( .indirectNum( .indirectNum( .num(1) ) ) ) 詳細: indirect を指定すると associated value を間接指定するようになります。 間接指定しない場合、associated value 分のメモリサイズが確定できないため、その列挙体のために確保するメモリサイズも決まりません。 間接指定する場合、associated value の場所(アドレス)を保持することになるので列挙体のサイズが確定できるようになります。 詳細: Swift Programming Language (Enumerations -> Recursive Enumerations) lazy 変数を初期化する際の値を遅延評価します。 通常 property は instance の作成時に値が評価されますが、下記のように lazy を指定すると該当の property に初めてアクセスした際に値が評価されます。 class Sample { lazy var lazyValue = Date() let defaultValue = Date() } let sample = Sample() sleep(10) // lazyValue 2021-11-18 05:14:47 +0000 print("lazyValue", sample.lazyValue) // defaultValue 2021-11-18 05:14:37 +0000 print("defaultValue", sample.defaultValue) 使い所としては、 1. 該当のpropertyに入る値の生成にコストがかかるため、実際に使うときまでその生成を遅延させたい場合 1. init 時に値が決まらないため、後で値を代入したい場合 があります。 2 番目に関しては少々トリッキーなので好みが分かれますが、下記のように IUO を避ける実装ができます。 class Sample { var iuoValue: String! // init 時に値が決まらないため IUO にしている // ↑のようなIUO を使うのを避ける実装。最初は`Never`(全ての型のサブタイプ扱い)を返すclosure を入れておき、このpropertyにアクセスする前には値を入れる前提。 lazy var lazyEval: String = { preconditionFailure() }() } left 演算子を定義した際に、左結合を指定します。 詳細: このページの associativity 項 関連: associativity, operator, right, none mutating 値型のオブジェクトにおいて、自身または自身のプロパティを書き換えるインスタンスメソッドに対して宣言する。 列挙体の場合 enum SampleEnum { case A, B, C case a, b, c mutating func upperCase() { switch self { case .a: self = .A case .b: self = .B case .c: self = .C default: break } } } 構造体の場合 struct SampleStruct { var x = 0 mutating func modifyX(x: Int) { self.x = x } mutating func reset() { self = SampleStruct() } } none 演算子を定義した際に、結合方向を指定しません。 詳細: このページの associativity 項 関連: associativity, operator, right, left nonisolated [Swift 5.5~] Actorの宣言部で使用します。 Actor の基本思想は「ミュータブルなデータ(状態)を actor 外から隔離(isolate)しデータの同期状態を守る」ことです。そのため、actor の method や mutable な property はデフォルトで隔離(isolate)対象となっています。 その対象としないことを指定する場合に nonisolated を付与します。 actor SampleActor { nonisolated func nonisolatedFunc() {} nonisolated var nonisolatedComputedProperty: String { "computedProperty getter" } } ミュータブルなデータ(状態)の保護状態を守るため、隔離対象外(nonisolated)の method/computed property は隔離対象の property や method へのアクセスが禁止されます。これに違反するとコンパイルエラーになります。 関連: actor nonmutating 値型のインスタンスメソッドが自身に変更を加えないことを宣言します。 使い所: computed property 内で定義する set はデフォルトで mutating 指定になります。iOS の API 内では下記のように setter の mutaing を無効にするために使用しています。 var value: Value { get nonmutating set } optional プロトコルで指定されたメソッドやプロパティの実装を任意とします。 override 親クラスのメソッドやプロパティをオーバーライドする際に宣言します。 postfix 独自の後置演算子を定義します。 postfix operator *** postfix func *** (a: inout Int) -> Int { a *= a return a } var hoge = 4 hoge*** // => 16 関連: prefix, infix, operator prefix 独自の前置演算子を定義します。 prefix operator *** prefix func *** (a: inout Int) -> Int { a *= a return a } var hoge = 4 ***hoge // => 16 関連: postfix, infix, operator Protocol Protocol のメタタイプを取得します。 let protocolMetatype: SampleProtocol.Protocol = SampleProtocol.self 関連: Type required サブクラスにイニシャライザのオーバーライドを強制します。 また、サブクラスでそのイニシャライザをオーバーライドする際には override ではなく、required を指定します。 right 演算子を定義した際に、右結合を指定します。 詳細: このページの associativity 項 関連: associativity, operator, left, none set 文脈によって意味が異なります。 computed property 内: プロパティにアクセスした際の処理 class A { var value: Int { get { return 1 } set {} } } protocol 内で宣言した場合: 値を受け渡し可能であることを宣言 protocol Sample { var value: Int { get set } } class A: Sample { var value = 1 } class B: Sample { var value: Int { get { return 1 } set {} } } Type クラス、構造体、列挙体のメタタイプを取得します。 class Sample { required init() {} } let metatype: Sample.Type = Sample.self let instance = metatype.init() 関連: Protocol unowned 弱参照の変数を宣言します。 weak と違い、参照している値よりも生存期間が短い、つまり、アクセスした際に参照先が解放されていない (nil となっていない) ことを前提とします。値が破棄されているとランタイムエラーになります。 // unowned = unowned(safe) unowned var safe: AnyObject // capture list 内で指定することが多いです { [unowned a] in /* ... */ } 関連: weak, unowned(safe), unowned(unsafe) unowned(safe) unowned へ修飾することで参照の動作を指定できます。 unowned(何も指定しなかった場合)は unowned(safe) 扱いとなります。 関連する指定に unowned(unsafe) があります。 // unowned = unowned(safe) unowned(safe) var safe: AnyObject // capture list 内で指定することが多いです { [unowned(safe) a] in /* ... */ } 関連: unowned(unsafe), unowned unowned(unsafe) unowned へ修飾することで参照の動作を指定できます。 unowned または unowned(safe) と違い、参照先が解放された際にアクセスしても nil と評価されません。つまり、解放されている場所を指し続けているためメモリに対して安全なアクセスをしません。 Objective-C の __unsafe_unretained と同様の動作です。 // like `__unsafe_unretained` unowned(unsafe) var unsafe: AnyObject { [unowned(unsafe) a] in /* ... */ } 関連: unowned(safe), unowned weak 弱参照の変数を宣言します。 unowned と違い、参照している値が nil になることを許容します。 willSet stored property または変数への代入が実行される前の処理を記述します。 関連: didSet その他 _ ワイルドカード switch (1, "a") { case (1, "b"): print("1, b") case (1, _): print("1, _") // ここがマッチします。 (_ はワイルドカード) } 関連: case 引数名の省略 引数が必要なinit class Sample { var a: Int init(param: Int) { a = param } } let sample = Sample(param: 1) 引数が不要なinit class Sample { var a: Int init(_ param: Int) { a = param } } let sample = Sample(1) 値を捨てる let a: Int (a, _) = (0, 1) a // -> 0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Snyk を使って iOS プロジェクトのライブラリ脆弱性を検査する

Snyk(スニーク)とは? 1. 概要 Snyk(スニーク)はデベロッパーファーストのセキュリティプラットフォームです。Snykは、コードやオープンソースとその依存関係、コンテナやIaC(Infrastructure as a Code) における脆弱性を見つけるだけでなく、優先順位をつけて修正するためのツールです。Gitや統合開発環境(IDE)、CI/CDパイプラインに直接組み込むことができるので、デベロッパーが簡単に使うことができます。 (引用:https://qiita.com/advent-calendar/2021/snyk) 簡単にいうと脆弱性を見つけるためのツールです。 2. ロゴ ロゴはマスコット犬で「Patch(パッチ)」という名前がついています。 (セキュリティホールを修正するプログラムを「パッチ」というので、その辺から命名されたのかな?とか思ったり) (引用:https://snyk.io/blog/snyk-6th-birthday-nft-gifts/) 最近だと NFT で出たりと運営側も結構気に入っているみたいです!笑 iOSプロジェクトへの導入 1. Synkで出来ること iOSプロジェクトで使用する場合は、ライブラリの中身に脆弱性がないか確認してくれます。 現状は、CocoaPodsのみに対応しているようです。 We scan CocoaPods projects and examine your Podfile and Podfile.lock files. We then compare the specific versions of every direct and deep dependency in your project against our vulnerability database in order to build the project dependency tree accordingly. Swift and Objective-C projects managed by CocoaPods can be imported from any of the Git repositories we support. In order to test your projects, we analyze your Podfile and Podfile.lock files. (引用:https://docs.snyk.io/products/snyk-open-source/language-and-package-manager-support/snyk-for-swift-and-objective-c-cocoapods) 動作としては、プロジェクト内のPodfileとPodfile.lockからライブラリとバージョンを見て、Snykが管理する独自データーベースと照らしあわせて検査するようです。この説明から、仕組み上はCarthageやSPMも使えそうですが、まだ対応していないということでしょう。 ちなみに独自のデーターベースは公開されています。 2. 導入していく ① 登録 まずは公式サイトから登録を行ってください。 登録方法は公式が書いたQiitaを見てください。 ② 検査する対象の選択 登録すると検査するリポジトリの対象を選ぶ画面になります。 Github/Bitbucket/個別のリポジトリ の3つから選択可能です。 一番上の「Github」を選択するさらに詳細に設定を行えます。 ❶ リポジトリのアクセス範囲を選択 - リポジトリ全部(パブリック&プライベート) - リポジトリ全部(パブリックのみ) どちらかを選択することができます。チームに所属している場合、そちらのリポジトリも対象になってしまうようなので、個別にリポジトリ設定する方がミニマムに行えて良いです。 ❷ 自動で動作するオプションの設定 細かい設定として自動的に以下のことも設定できます。 - [ ] 新しいプルリクエストをにつき、新しい脆弱性をテスト - [ ] パッチ(脆弱性を修正する)プルリクエストの作成 - [ ] 古い依存関係をアップグレードするプルリクエストの作成 - [ ] ソースコードの脆弱性テスト (リポジトリのクローン、コードのアップロード。最大24時間のキャッシュ。) デフォルトですべてオンになっており、特に外す必要はないかと思います。 今回は1つ画面を戻って、個別のリポジトリ設定を選択していきます。 色々と選択肢はありますが、iOSはないので、脆弱性をチェックしたいリポジトリのパスを自分で貼り付けます。 リポジトリを追加すると自動で Snyk のプロジェクトに追加され、以下のような画面に移動します。 左側のタブで表示の設定などを切り替えることができます。 さらに中身を見てみると、脆弱性に問題がある場合は、詳細が表示されます。 脆弱性にもスコアがあり、スコアに応じてレベル分けがなされているようです。今回は、依存関係であるnanopbが325スコアでMEDIUMレベルの脆弱性と判定されたようです。 リポジトリを追加するだけ検査できるのはとてもお手軽です! 3. 脆弱性のテスト 試しに脆弱性のあるライブラリを読み込んで、テストを行ってみました。データベースからCocoaPodsで問題のあるライブラリを適当にピックアップします。 今回はGPAC4iOSというライブラリを入れてみます。 追加後は pod install して Podfile.lock を更新するのを忘れないでください。 そして、Snykの管理画面を見てみると... 見事に脆弱性のレベルが増えてます。中身を見ると このように詳細も見れました。 ちゃんと機能していることがわかり大変便利です! 終わりに 冒頭にも書きましたが、対応しているのがCocoaPodsだけなのが残念ですが、CarthageやSPMも仕組み上は可能そうなので、Github Actionsでこねくり回せばできなくはないように思います。 少し手間ではありますが、CarthageやSPMで提供されているライブラリは、大抵の場合はCocoapodsでも提供されているので、脆弱性をチェックするためだけのリポジトリを作っても良いかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む