Swift言語仕様基礎
Caselterableに準拠する理由
allCasesで全要素にアクセス可能
👉 数を返したり、全要素にアクセスして何かしらの整形等可能
/// For example, the CompassDirection
enumeration declared in this example
/// conforms to CaseIterable
. You access the number of cases and the cases
/// themselves through CompassDirection.allCases
.
///
/// enum CompassDirection: CaseIterable {
/// case north, south, east, west
/// }
///
/// print("There are (CompassDirection.allCases.count) directions.")
/// // Prints "There are 4 directions."
/// let caseList = CompassDirection.allCases
/// .map({ "($0)" })
/// .joined(separator: ", ")
/// // caseList == "north, south, east, west"
swiftというかiOSについてだけどライフサイクル問題はたまに忘れるから資料をメモ
-
viewDidLayoutsubviews
https://appleharikyu.jp/iphone/?p=1271 -
layoutSubviews
https://scleapt.com/swift_viewdidlayoutsubviews/
https://sasata299.hatenablog.com/entry/2014/05/14/094559 -
layoutIfNeededで明示的にlayoutSubviewsを呼び出す
https://snoozelag.hatenablog.com/entry/2017/05/22/181048 -
UIViewのライフサイクル
https://qiita.com/shoheiyokoyama/items/2f76938dffa845130acc
Dateわかりづらいからきらい..めんどい
- Localeとかen_US_POSIXの話について触れられている
- DateFormatterはインスタンス生成コストが高いから一度インスタンス化されたものを使います
- String→Date変換は必ずen_US_POSIXで行う
- Formatterでロケール・タイムゾーンを設定する
- 日付文字列へ変換時にローカライズされるべき箇所をハードコードしない
- 日付の期間を表示するにはDateIntervalFormatterを使用する
これも良さそう
どうしてもJSTで今日の0時0分を取得したい場合はこうするのか?
何時間前とか何日前とかを取得する場合
UTCでいうと日本時間はUTC+9時間
DateFormatterは生成コストが高い
SwiftのコレクションタイプってArray使いがちでSetってわすれてしまうよね
Reduceって苦手だけどこの人の記事はくそわかりやすかった
コンテンツの持つ高さを取得する際にintrinsiccontentsizeを自分はよく使っていた
例えばlabelの高さを設定するときにintrinsiccontentsizeを使って、そのlabel自身が持つ高さを設定していた
問題
でもこれが改行コードの入ってない文字列の場合、そのlabel自身の持つ高さは1行だから本来取得したい高さを取得でない
*表示したい文字列データに改行コードが入っている場合は問題ない。その高さ分にはできる
対応策
sizeThatsFitを使って、幅を指定
その幅で強制的に折り返される時のコンテンツの高さを取得&設定する
mockingbirdでの定義だけど
- mock
Create test doubles of Swift and Objective-C types.
PropertyWrapper
「内部変数を定義して、外部からは computed property 経由でアクセスさせる」というコードは、よく見るコードになっています。このようなコード(boiler plate と呼ばれます)を減らすことがこの propertyWrapper の目的です
このような制御をすることを “wrap する”というふうに表現されていて、そのような仕組みを Swift 言語としてサポートするのが、property wrapper です。
@NoWrapInt という propertyWrapper 付きの 変数 value は、実は、value という変数は存在せずに、propertyWrapper 内に用意された wrappedValue に保存されているということです。
propertyWrapper を使って value を定義したことで、value という変数そのものへのアクセスではなく、さらに内部の wrappedValue へのアクセスに切り替わっていることがポイントです。
なお、wrappedValue のタイプは、propertyWrapper で修飾している変数のタイプと同じであることが必要です。
property wrapper “WrapIntLowerLimit” を指定した CheckWrapInt クラスのインスタンス varInt 中の プロパティ value にアクセスすると、
value.wrappedValue へのアクセスになっていました。
これが、property wrapper の基本的な意味でした。
プロパティ名に $ をつけてアクセスすると、それは、projectedValue へのアクセスになります。”それだけ”です。
projectedValue へのアクセスを意味する
Type は、自由に設定できる
単にSwift Evolution読むだけだとしんどいから、この人みたいに写経するのもいいかも
@State という property wrapper では、 source of truth の場所を確保しデータを保存するようにしています。その上で projectValue として、その場所へのリファレンスを渡します。@Binding は、その保存場所へのリファレンスを保持する property wrapper で、保存場所へアクセスすることで、直接保存場所の値を変更することができます。
> roperty Wrapper とは「プロパティの振る舞いを変更・追加し、簡単に再利用」するための仕組みであると言えます。
Property Wrapper は projectedValue という名前のプロパティを宣言することで対象のプロパティに振る舞いを追加することができます。
swift-evolution読んだ方が早いな(読み切ってはない
Introduction
Motivation
Lazyは必要なタイミングでインスタンスが生成されば良くて、必要のないタイミングでやってしまうのが嫌で使っていたけど、それはつまりオプショナルで定義することを避けられるってことか
で、同じことを自前でしようとすると以下のようにたくさんのボイラープレートが必要なる
struct Foo {
private var _foo: Int?
var foo: Int {
get {
if let value = _ foo { return value }
let initialValue = 1738
_foo = initialValue
return initialValue
}
set {
_foo = newValue
}
}
]
Proposed Solution
PropertyWrapper
We propose the introduction of property wrappers, which allow a property declaration to state which wrapper is used to implement it. The wrapper is described via an attribute:
@Lazy var foo = 1738
This implements the property foo in a way described by the property wrapper type for Lazy:
@propertyWrapper
enum Lazy<Value> {
case uninitialized(() -> Value)
case initialized(Value)
init(wrappedValue: @autoclosure @escaping () -> Value) {
self = .uninitialized(wrappedValue) ← @Lazy var foo = 1738が呼び出された時に実行される
}
var wrappedValue: Value {
mutating get {
switch self {
case .uninitialized(let initializer):
let value = initializer()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
self = .initialized(newValue)
}
}
}
_fooはpropertywrapperのインスタンスそのもの
_foo.wrappedValueとfooはwrappedValueにアクセスしている
A property wrapper type provides the storage for a property that uses it as a wrapper. The wrappedValue property of the wrapper type provides the actual implementation of the wrapper, while the (optional) init(wrappedValue:) enables initialization of the storage from a value of the property's type. The property declaration
propertywrapperを使うことで宣言時にその振る舞いも持たせることができる感じかな
@はattributeで属性っていう意味だし
I suppose that propertyWrapper decide the property behavior on the declartion.
@Lazy var foo = 1738
translates to:
private var _foo: Lazy<Int> = Lazy<Int>(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
明示的なイニシャライズ
The backing storage property can also be explicitly initialized. For example:
extension Lazy {
init(body: @escaping () -> Value) {
self = .uninitialized(body)
}
}
func createAString() -> String { ... }
@Lazy var bar: String // not initialized yet
_bar = Lazy(body: createAString)
The property wrapper instance can be initialized directly by providing the initializer arguments in parentheses after the name. The above code can be written equivalently in a single declaration as:
@Lazy(body: createAString) var bar: String
PropertyWrapperにおけるイニシャライザ以外のAPI
The Lazy property wrapper has little or no interesting API outside of its initializers, so it is not important to export it to clients. However, property wrappers can also describe rich relationships that themselves have interesting API. For example, we might have a notion of a property wrapper that references a database field established by name (example inspired by Tanner):
KeyPath
概要
公式ドキュメントによれば
> A key-path expression refers to a property or subscript of a type.
valueではなくてプロパティやsubscriptへアクセスする
*ただしこれは別に新しい考えというわけではなくて少し違うけどKeyPath自体はobjective-C時代から存在してた
ただ当時はKeyPathという型がなくてstringでアクセスする必要があったのでtypoによる不具合の可能性があった
Swiftではこれをプロパティでアクセスできるので安全になっただけ
どういうことかというと、「変数を利用してvalueへアクセス」の場合はもちろん変数名を呼び出せばそのバリューにアクセスできる
let lastName = fuga
let fullName = "hoge \(lastName)"
これと比べてプロパティへアクセスするというのはそのプロパティ自体を保持する。以下を見ればわかるとおりPersonのageプロパティ自体を指している
class Person {
let familyName: String
let lastName: String
let age: Int
init(familyName: String, lastName: String, age: Int) {
self.familyName = familyName
self.lastName = lastName
self.age = age
}
}
let person = \Person.age
print(person) // \Person.age
KeyPathを使ったValueの取得方法
At compile time, a key-path expression is replaced by an instance of the KeyPath class.
To access a value using a key path, pass the key path to the subscript(keyPath:) subscript, which is available on all types. For example:
公式ドキュメントに書かれてる通り、subscript(keyPath:) が全ての型で利用できるのでこれを使えばいい
let person1 = Person(familyName: "hoge", lastName: "fuga", age: 20)
print(person1[keyPath: \.age])
またオブジェクトの定義内でKeyPathを使う場合は以下のようになる
class Person {
let familyName: String
let lastName: String
let age: Int
init(familyName: String, lastName: String, age: Int) {
self.familyName = familyName
self.lastName = lastName
self.age = age
}
func getAge() -> Int {
return self[keyPath: \.age]
}
}
let person1 = Person(familyName: "hoge", lastName: "fuga", age: 20)
print(person1.getAge())
@dynamicMemberLookupと組み合わせることでより直感的にできる
→ 参照: https://zenn.dev/link/comments/4d19a1e557f534
メリット
- Objective-C時代と比べて安全に使える
- ネストしている他のオブジェクトのプロパティにもアクセスできる
class Child {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
class Parent {
let name: String
let child: Child
init(name: String, child: Child) {
self.name = name
self.child = child
}
}
let child = Child(name: "fuga", age: 20)
let father = Parent(name: "hoge", child: child)
各親は子の情報を持つ
let childAgeKeyPath = \Parent.child.age
print(father[keyPath: childAgeKeyPath]) // 20
もし仮に違うKeyPathを渡してしまってもコンパイルエラー出て弾いてくれるのもSwiftでのKeyPathのいいところ
let ageKeyPath = \Child.age
print(father[keyPath: ageKeyPath]) // Key path of type 'KeyPath<Child, Int>' cannot be applied to a base of type 'Parent'
Advance
まずは簡単な使い方の紹介
ハードコーディングになってるけど、たとえば以下のようにageを取り出すこともできる
func getAge(for object: Child, keyPath: KeyPath<Child, Int>) -> Int {
return object[keyPath: keyPath]
}
print(getAge(for: child, keyPath: \.age))
ただこれだとParentにもageプロパティが追加されてそれを知りたいとなった時や他のvalueも取得したいとなった時に対応できない
なのでジェネリクスを使って
func getValue<T, Value>(for object: T, keyPath: KeyPath<T,Value>) -> Value {
return object[keyPath: keyPath]
}
print(getValue(for: child, keyPath: \.age))
print(getValue(for: father, keyPath: \.child.name))
値の変更の場合
func setValue<T, Value>(for object: inout T, keyPath: WritableKeyPath<T, Value>, newValue: Value) {
object[keyPath: keyPath] = newValue
}
setValue(for: &child, keyPath: \.age, newValue: 21)
print(getValue(for: child, keyPath: \.age))
またそもそも書き方がここまでと違うけどgetter/setterを使って以下のような書き方もできる
struct Child {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
struct Parent {
var name: String
var child: Child
init(name: String, child: Child) {
self.name = name
self.child = child
}
}
let child = Child(name: "fuga", age: 20)
let father = Parent(name: "hoge", child: child)
struct Accessor<T> {
private(set) var object: T
init(_ object: T) {
self.object = object
}
subscript<Value>(keyPath: WritableKeyPath<T, Value>) -> Value {
get {
return object[keyPath: keyPath]
}
set {
object[keyPath: keyPath] = newValue
}
}
}
var accessor = Accessor(father)
print(accessor[\.child.age])
accessor[\.child.age] = 23
print(accessor[\.child.age])
Userdefaultsのkeyに対応する値をpublisherで監視する場合
そのpublisherはNSObjectのメソッドで、KVO準拠のプロパティと連携する
どういうことかというと
serDefaultsは、set(_:forKey:)で指定されたキーに対してKVO準拠として機能するということ
そのため、set(..., forKey: "test")で行われた更新を観察したい場合、ObjCキーパスの"test"を観察する必要がある
参照: https://developer.apple.com/forums/thread/133906
対応方法としては@objcをつければいい
参照: https://developer.apple.com/documentation/swift/using-key-value-observing-in-swift
なぜならobjective-cのメソッドを実行することになるのに、監視対象となるとプロパティがそのランタイム時に見つからないから
@objcをつけてあげて公開してあげることでobjective-cのランタイム時にそのプロパティが使われるようになる
参照: https://stackoverflow.com/questions/69638458/swift-combine-could-not-extract-a-string-from-keypath-swift-keypath
メリットについてまた考えてみる
protocolを使った抽象化についてかな
2章1節: Protocol-oriented Programming とは
文章はこう始まっている
何が Protocol-oriented Programming なのかは明確に述べられていません。
Protocol-oriented Programming という用語は Object-oriented Programming (オブジェクト指向プログラミング) との対比によって生み出されたものだと、筆者は考えています。Swift におけるプロトコルは抽象化の道具です
この節ではオブジェクト指向プログラミングとの対比で Protocol-oriented Programming が書かれている
オブジェクト指向プログラミングにおける抽象化といえば
- クラスの 継承
- ポリモーフィズム
Swift は 値型 中心の言語です。 参照型 と異なり、 値型 は原理的に 継承 することができません。そのため、 Swift ではクラスの 継承 ではなくプロトコルが抽象化の主役になります
プロトコルによる不適切な抽象化
プロトコル方変数を使うのはよく見る
でもプロトコル型変数に値を代入すると Existential Container に包まれる
そしてそれは
- メモリを余分に消費する
- Existential Container に包んだり取り出したりする実行時のオーバーヘッドが発生する
Existential Container のオーバーヘッドは 値型 とプロトコルに起因するものです。 参照型 であるクラスの 継承 ではそのようなオーバーヘッドは発生しません。
照型 のインスタンスは変数に直接格納されるわけではありません。
インスタンスはメモリ上の別の場所に存在し、その場所を指し示すアドレスが変数に格納されます。
一般的には、 64 ビット環境においてアドレスは 8 バイト( 64 ビット)で表されます。そのため、 参照型 の変数はどのような型でも 8 バイトの領域しか必要としません。
値型に適したポリモーフィズム(値型 ではどのようにコードを抽象化するか)
以下の2種類
- サブタイプポリモーフィズム(サブタイピング)❌
func useAnimal(_ animal: Animal) {
print(animal.foo())
}
オーバーヘッドが発生する
- パラメトリックポリモーフィズム
func useAnimal<A: Animal>(_ animal: A) {
print(animal.foo())
}
上記の説明
実行時に起こること(サブタイプポリモーフィズム)
どちらも同じように使うことができ、実行結果も同じ
useAnimal(Cat())
useAnimal(Dog())
けど実行時に内部的に行われていることはまったく異なる
問題点1: サブタイピング の場合、 useAnimal に引数を渡すときにインスタンスを Existential Container に包む必要があります。
// サブタイプポリモーフィズム
useAnimal(Cat()) // Existential Container に包む
useAnimal(Dog()) // Existential Container に包む
結果的にオーバーヘッドの発生
問題点2: さらにuseAnimal の中で animal.foo() を呼び出す箇所では、 animal が Cat か Dog か、または別の型のインスタンスかは実行時までわかりません。 Cat の foo を呼び出すか Dog の foo を呼び出すかは実行時に決定されます(動的ディスパッチ)
// サブタイプポリモーフィズム
func useAnimal(_ animal: Animal) {
print(animal.foo()) // 動的ディスパッチ
}
動的ディスパッチ には当然実行時のオーバーヘッドがありますが、 Existential Type を経由してメソッドを呼び出すオーバーヘッドはそれだけではありません。クラスの継承の場合は vtable を介してメソッドを見つけるだけなので大したオーバーヘッドではないですが、 Existential Type を経由してメソッドを呼び出す場合は、一度 Existential Container からインスタンスを取り出すオーバーヘッドも発生します。
実行時に起こること(パラメトリックポリモーフィズム)
パラメトリックポリモーフィズム ≒ ジェリック関数?
型パラメータ という概念を導入して一つの関数として実装できるようにしたのがジェネリック関数
// Cat 用の useAnimal
func useAnimal(_ animal: Cat) {
print(animal.foo())
}
// Dog 用の useAnimal
func useAnimal(_ animal: Dog) {
print(animal.foo())
}
とする必要がなくジェネリック関数を使えば
// ジェネリック関数として実装された useAnimal
func useAnimal<A: Animal>(_ animal: A) { // A が Cat や Dog などを表す
print(animal.foo())
}
概念的には、ジェネリック関数は 型パラメータ に Cat や Dog などの具体的な型を当てはめ、それらの関数がオーバーロードされているのと(ほぼ)同じです。上記のような useAnimal 関数を一つ実装すれば、 Cat 用の useAnimal や Dog 用の useAnimal を個別実装してオーバーロードするのと同じように振る舞います。
しかも、サブタイピングと違って、概念上だけでなく実行時の挙動もそれと似たものになる
コンパイラが 特殊化 (最適化の一種)を行った場合、ジェネリック関数としての useAnimal のバイナリに加えて、型パラメータに Cat や Dog などの具体的な型を当てはめた useAnimal のバイナリも生成されます。そして、 useAnimal に Cat インスタンスを渡す箇所では Cat 用の useAnimal が、 Dog インスタンスを渡す箇所では Dog 用の useAnimal が呼び出されるようにコンパイルされます。
なのでサブタイピングと違うと言える
useAnimal に Cat インスタンスや Dog インスタンスを渡す際に Existential Container に包む必要はありません。 Cat インスタンスは Cat 用 useAnimal に、 Dog インスタンスは Dog 用 useAnimal にそのまま直接渡されます。
// パラメトリックポリモーフィズム
useAnimal(Cat()) // Cat のまま直接渡される
useAnimal(Dog()) // Dog のまま直接渡される
useAnimal 関数の中で animal.foo() を呼び出す箇所についても、サブタイピングとは違って Cat 用の useAnimal の中では animal が Cat であることがわかっているので、コンパイル時に Cat の foo を呼び出せば良いと決定できます( 静的ディスパッチ )。そのため、実行時にメソッドを選択するオーバーヘッドが発生しません。
// パラメトリックポリモーフィズム
func useAnimal<A: Animal>(_ animal: A) {
print(animal.foo()) // 静的ディスパッチ
}
パラメトリックポリモーフィズム を用いると、実行時のオーバーヘッドを発生させずにコードを抽象化することができます。
上記のコードでは、 Cat 用の useAnimal と Dog 用の useAnimal が一つのジェネリック関数として抽象化されていますが、実行時のオーバーヘッドはありません。 値型 中心の Swift においては、 値型 と組み合わせたときにオーバーヘッドの大きい サブタイピングポリモーフィズム よりも、 値型 であってもオーバーヘッドの発生しない パラメトリックポリモーフィズム の方が適しています。 実際、 Swift の標準ライブラリで用いられている ポリモーフィズム のほとんどが パラメトリックポリモーフィズム です。
ただし、いつでも パラメトリックポリモーフィズム を用いれば良いというわけではありません
→まさに自分は結論 パラメトリックポリモーフィズムを使った方が効率がいいんだなって思った
制約としてのプロトコル
サビタイプポリモーフィズム よりも パラメトリックポリモーフィズム を優先するということを、プロトコルを中心にして言い替えると次のようになります。
プロトコルを「型として」ではなく「制約として」使用することを優先する
“Protocol-Oriented Programming in Swift” の中では特別そのことは強調されていません。しかし、本節で述べた内容や標準ライブラリにおけるプロトコルの利用例を見る限り、これこそが Swift のプロトコルの使い方ついて最も重要なことだと筆者は考えています。
Protocol-oriented Programming とはを読んでの感想
protocolには型としての側面と制約としての側面があり
それがそれぞれサブタイピングとパラメトリックポリモーフィズムに該当する
クラス中心の言語ではオブジェクト指向プログラミングに則って
サブタイピングでの抽象化は効果的だけど
swiftのような値型中心の言語では同じやり方ではオーバーヘッドが生まれてしまうため
サブタイピングよりもパラメトリックポリモーフィズムを優先的に使ったほうが良さそう
2章2節: 「型として」・「制約として」のプロトコルの使い分け
型としてのプロトコルでしかできないこと
// 型としてのプロトコル
func useAnimals(_ animals: [Animal]) {
...
}
// 型としてのプロトコル
useAnimals([Cat(), Dog()]) // ✅
// 制約としてのプロトコル
func useAnimals<A: Animal>(_ animals: [A]) {
...
}
// 制約としてのプロトコル
useAnimals([Cat(), Dog()]) // ⛔ コンパイルエラー
useAnimals([Cat(), Cat()]) // ✅ [Cat] を渡す( A は Cat)
useAnimals([Dog(), Dog()]) // ✅ [Dog] を渡す( A は Dog)
👉 一般的に Swift の型システム上では、プロトコル型はそのプロトコル自体に適合しません。
Generalized Existential と型消去
associatedtype を持ったプロトコルを型として使うことはできない
*例えば以下で言えば、associatedtypeで定義したFoodが具体的に何であるか決まってない。具体的な情報がないため、それ自体を型として使用することはできない。Animal プロトコルだけを見ても、Food が何であるか(Fish なのか、Bone なのか、または他の何かなのか)は分からない、ということ
protocol Animal {
associatedtype Food
func eat(_ food: Food)
}
struct Fish {}
struct Bone {}
struct Cat: Animal {
func eat(_ food: Fish) {
print("cat")
}
}
struct Cat: Animal {
func eat(_ food: Fish) {
print("cat")
}
}
struct Dog: Animal {
func eat(_ food: Bone) {
print("dog")
}
}
var animals: [Animal] = [Cat(), Dog()] // ❌ Animal プロトコルを具体的な型として使用しようとしていますが、それは Food の具体的な型が不明であるため許可されていない
しかしTypeErasureを使えばできる
struct AnyAnimal: Animal {
private let _eat: (Any) -> Void
init<A: Animal>(_ animal: A) {
_eat = { food in
guard let food = food as? A.Food else { return }
animal.eat(food)
}
}
func eat(_ food: Any) {
_eat(food)
}
}
var animals: [AnyAnimal] = [AnyAnimal(Cat()), AnyAnimal(Dog())]
制約としてのプロトコルでしかできないこと
Self-requirement
Equatable プロトコルでは Self-requirement が使われています。
protocol Equatable {
static func == (
lhs: Self, // Self-requirement
rhs: Self // Self-requirement
) -> Bool
}
上記のコード中の Self が Self-requirement です。プロトコルの中に記述された Self は、そのプロトコルに適合した型を実装する際に、その型自体に置き換えられなければなりません。たとえば、 Int を Equatable に適合させる場合には Self は Int に、 String を Equatable に適合させる場合には Self は String に置き換えられます。
extension Int: Equatable {
static func == (
lhs: Int, // Self が Int に置き換えられる
rhs: Int // Self が Int に置き換えられる
) -> Bool { ... }
}
extension String: Equatable {
static func == (
lhs: String, // Self が String に置き換えられる
rhs: String // Self が String に置き換えられる
) -> Bool { ... }
}
👉 Self-requirement を持つプロトコルは型としては使用せず、制約として使うことだけが想定されています。
Self を持つプロトコルを型として使おうとするとコンパイルエラーになります
2章3節: プロトコルとリバースジェネリクス
制約としてのプロトコル ⇨ かけているものが明らかに ⇨ リバースジェネリクスという新しい概念提唱 ⇨ 簡易版としてOpaque Result Type がswift5.1に導入
制約としてのプロトコルに欠けていた抽象化
Kotlin では同様の目的でインタフェース( Swift のプロトコルのような役割を果たすもの)が iterator メソッド( Swift の makeIterator メソッドに相当)の戻り値の型として使用されています。
. // Kotlin
interface Iterable<out T> {
operator fun iterator(): Iterator<T> // 型として使われている
}
Swift では IteratorProtocol が制約として使われ、 Kotlin では Iterator インタフェースが型として使われているわけです。これは、両言語の特徴をよく表しています。 Kotlin は参照型中心の言語なので、 Iterator インタフェースを型として使ってもオーバーヘッドは大きくありません。
Kotlin は参照型中心の言語
へー
IteratorProtocol は Element という associatedtype を持っているので、現状の Swift では型として使うことができません。
*しかし、 Generalized Existential がサポートされれば IteratorProtocol も型として使うことが可能になります。
Opaque Result Type
これが Opaque Result Type です。この “Result” は処理の「結果」、つまり、戻り値を意味しています。隠蔽された不透明( Opaque )な戻り値の型なので Opaque Result Type です。
// リバースジェネリクス
func makeAnimal() -> <A: Animal> A {
Cat()
}
「何らかの Animal 」、つまり「ある Animal 」を返すわけです。英語で言えば「 some Animal 」です。次のように書ければよりわかりやすいはずです。
// Opaque Result Type
func makeAnimal() -> some Animal {
Cat()
}
Opaque Result Type は リバースジェネリクス を簡潔に書くためのシンタックスシュガーだと考えることができます。シンタックスシュガーなので、上記の二つのコード( リバースジェネリクス 版と Opaque Result Type 版の makeAnimal 関数)は全く同じことを意味します。
Opaque Argument Type
Opaque Result Type を使えば リバースジェネリクス を簡潔に記述できました。同じことは通常のジェネリクスについても言えるはずです。ジェネリックな引数を some を使って簡潔に書けるようにしようというのが Opaque Argument Type です。
例として、通常のジェネリクスで書かれた次のような関数 useAnimal を考えます。
// ジェネリクス
func useAnimal<A: Animal>(_ animal: A) {
print(animal.foo())
}
これを Opaque Argument Type で書いたコードが下記です。
// Opaque Argument Type
func useAnimal(_ animal: some Animal) {
print(animal.foo())
}
Opaque Result Type と リバースジェネリクス の関係と同じように、 Opaque Argument Type はジェネリクスのシンタックスシュガーです。上記の二つの useAnimal 関数はどちらの書き方をしてもまったく同じ意味になります。
ジェネリクスでしかできないこと
たとえば、 Animal のつがいを引数に受け取る関数 useAnimalPair を考えてみます。
// ジェネリクス
func useAnimalPair<A: Animal>(_ pair: (A, A)) {
...
}
つがいなので、引数には同種の Animal を渡さなければなりません( Cat と Dog ではいけません)。そのため、 pair の型は一つの型パラメータ A で書かれたタプル (A, A) となっています。
これを Opaque Type で書こうとするとどうなるでしょうか。
// Opaque Argument Type
func useAnimalPair( _ pair: (some Animal, some Animal)) { // これで良い?🤔
...
}
しかし、上記のコードは下記のコードと同じ意味になってしまいます。
// ジェネリクス
func useAnimalPair<A1: Animal, A2: Animal>(_ pair: (A1, A2)) { // これではダメ😵
...
}
some Animal を 2 回書いた場合、それらは異なる型を意味することになります。もし、最初の useAnimalPair 関数のように、同種の Animal を二つ引数にとりたい場合にはジェネリクスを使うしかありません。
このように、 Opaque Type ではなくジェネリクスでしかできないこともあります。 Opaque Type はジェネリクスでできることの一部を簡潔に書くための手段でしかありません。
Opaque Type と some
func useAnimals(_ animals: [some Animal]) {
...
}
この関数の引数 animals は、 Homogeneous な(同種の値しか格納できない) Array です。「ある( some > ) Animal 」の Array がある一種類の Animal のインスタンスしか格納できないことは、言語的に自然です。
確かにわかりやすい
シンタックスって言葉はよく聞くけど文法規則のことなのね
subscript
参照: https://qiita.com/happy_ryo/items/72b68859ed8ace9f5fb4
subscriptとはDictionary とか Array で hoge["fuga"] みたいな感じで、要素にアクセスするアレ。
Read/Write
class SubscriptSample {
var userNames: Array<String>
init(){
userNames = []
}
// subscript の受け取る引数は [] の中で渡すもの
subscript(index: Int) -> String {
get {
assert(userNames.count > index, "index out of range")
return userNames[index] + " さん 昨夜はお楽しみでしたね"
}
set(name) {
assert(0 > index || userNames.count >= index, "index out of range")
userNames.insert(name, at: index)
}
}
subscript(index: Int) -> Int {
assert(userNames.count > index, "index out of range")
return userNames[index].count
}
subscript(nameIndex index: Int) -> Int {
assert(userNames.count > index, "index out of range")
return userNames[index].count
}
subscript(index: Int, sub: Int) -> Int {
return 1
}
}
func test() {
let sample = SubscriptSample()
sample[0] = "happy_ryo"
sample[1] = "crazy_ryo"
// オーバーロードを利用して複数のsubscriptを定義しているので戻り値を明記する必要がある
let result1: String = sample[0]
let result2: Int = sample[1]
// 引数の型や数が同一でも外部引数名が異なれば、受け取り側での型指定は必要ない。
let result3 = sample[nameIndex: 1]
// Subscript では複数の引数を受け取ることも可能
let result4 = sample[10, 10]
print(result1)
print(result2)
print(result3)
print(result4)
}
test()
ReadOnly
class SubscriptSample2 {
let userName: String
init(name: String){
userName = name
}
// subscript の受け取る引数は [] の中で渡すもの
subscript(action: String) -> String {
return userName + " さん " + action
}
}
func test2() {
let sample = SubscriptSample2(name: "happy_ryo")
print(sample["昨夜はお楽しみでしたね"])
print(sample["今朝もお楽しみでしたね"])
}
attributeについて
@inline
In programming, function inlining is a compiler optimization technique that removes the overhead of calling certain methods by directly replacing calls to them with the method's contents, basically pretending that the method never existed in first place. This provides a great performance boost.
参照: https://swiftrocks.com/the-forbidden-inline-attribute-in-swift.html
どうやらinlineという言葉自体がプログラミングにおいて使われる用語で、コンパイラの最適化のために使用されるテクニックみたい
何をするかというとinlineのついたメソッドの呼び出しを実際のそのメソッドの処理の中身に置き換える。(そのメソッドの中身はあるけど、そのメソッド名自体はなかったことになる感じ)
According to the Apple engineers, the answer is basically never. Even though the attribute is available for public use and widely used in the Swift source code, it is not officially supported for public use. It was simply never meant to be publicly available, and to quote Jordan Rose: the underscores are there for a reason. Many known and unknown issues can arise if you use it.
@inline(__always)と@inline(never)があるけど基本的にはneverを使った方がいい
> In programming, inline expansion, also called inlining, is a compiler optimization technique that replaces a method call with the body of the said method.
The action of calling a method is hardly free. As we covered back in SwiftRocks's article about memory allocation, a lot of orchestration is done to transmit, store and mutate the state of your application when it desires to push a new stack trace to a thread. While for one side having a stack trace makes your debugging life easier, you might wonder if it's necessary to do this every time. If a method is too simple, the overhead of calling it might be seen as something not only unnecessary but also harmful for the overall performance of the app:
func printPlusOne(_ num: Int) {
print("My number: \(num + 1)")
}
print("I'm going to print some numbers!")
printPlusOne(5)
printPlusOne(6)
printPlusOne(7)
print("Done!")
A method like printPlusOne is too simple to justify a complete definition in the application's binary. We define it in code for clarity reasons, but when pushing this app for release it would arguably be better to get rid of it and replace everyone that is calling it with the full implementation, like this:
print("I'm going to print some number!")
print("My number: \(5 + 1)")
print("My number: \(6 + 1)")
print("My number: \(7 + 1)")
print("Done!")
This removed method-calling overhead may increase the performance of the app with the trade-off of slightly increasing the overall binary size, depending on how large the inlined methods are. This process is done automatically by the Swift compiler, with variable degrees of aggressiveness depending on which optimization level you are building for. As we covered in The Forbidden @inline Attribute in Swift, the @inline attribute can be used to ignore the optimization level and force the compiler to follow a particular direction, and that the act of inlining can also be useful for obfuscation purposes.
また@Inlinableの記事によれば
リリース時にinlineを実行してバイナリサイズが増えるがオーバーヘッドを減らしパフォーマンスを上げるみたいな選別は基本的にSwiftコンパイラが自動でやってくれる処理だけど@inlineを使って手動で行うこともできるっぽい
cases where the attribute can be useful in iOS projects
その1
The compiler will make its inlining decisions based on your project's optimization settings, but there are cases where you could want a method to go against the optimization setting. In these cases, @inline can be used to achieve the user's goals.
For example, when optimizing for speed, it seems like the compiler will have a tendence to inline even methods that are not short, resulting in increased binary sizes. In this case, @inline(never) can be used to prevent the inlining of a specific widely-used-long method while still focusing on fast binaries.
その2
Another more practical example is that you might want a method to be hidden from possible hackers for containing some sort of sensitive info, regardless if it will make your code slower or bigger. You can try to make the code harder to understand or use some obfuscation tool like SwiftShield, but @inline(__always) can achieve this without hurting your code. I've detailed this example below.
@Inlinable
参照: https://swiftrocks.com/understanding-inlinable-in-swift
Instead of gambling, Swift instead lets you make this decision for yourself. Introduced in Swift 4.2, the @inlinable attribute allows you to enable cross-module inlining for a particular method. When this is done, the implementation of the method will be exposed as part of the module's public interface, allowing the compiler to further optimize calls made from different modules.
@inlinable func printPlusOne(_ num: Int) {
print("My number: \(num + 1)")
}
とかその他にも色々書かれてるけど
Should I use @inlinable, or even @inline?
Unless you're building a framework with ABI/API stability, these attributes should be perfectly safe to use. Still, I would strongly suggest for you to not use them unless you know what you're doing. They are built to be used in very special cases that most applications will never experience, so unless you really have to, it may be better for you to see these as nothing more than a piece of cool Swift trivia you read at SwiftRocks.
って書いてあるから一旦スキップ。また必要になったら学ぶ
@dynamicMemberLookup
@dynamicMemberLookup, which instructs Swift to call a subscript method when accessing properties.
This subscript method, subscript(dynamicMember:), is required: you’ll get passed the string name of the property that was requested, and can return any value you like.
参照: https://www.hackingwithswift.com/articles/55/how-to-use-dynamic-member-lookup-in-swift
@dynamicMemberLookupをつけることで、プロパティにアクセスした時にsubscriptメソッドを呼び出せるようになる
@dynamicMemberLookupをつけたらsubscript(dynamicMember:)がマストで必須になって、リクエストされたプロパティ名のstringがsubscriptメソッドに渡される
adding that attribute to a class or struct enables us to add support for accessing any property on that type — regardless of whether that property actually exists or not.
the compiler won’t give us any kind of warning or error when there’s no declared property matching that name:
https://www.swiftbysundell.com/tips/combining-dynamic-member-lookup-with-key-paths/
ただし該当のプロパティが存在しなくてもエラーを出したりアラートを出したりするわけじゃないので要注意
Dynamic member lookup allows a type to opt in to extending member lookup ("dot" syntax) for arbitrary member names, turning them into a string that can then be resolved at runtime.
https://github.com/apple/swift-evolution/blob/main/proposals/0252-keypath-dynamic-member-lookup.md
Dynamic member lookupは、任意のメンバー名のルックアップ("ドット"構文)を拡張することを型に許可し、ランタイム時に解決可能な文字列に変換する。
例
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["name": "Taylor Swift", "city": "Nashville"]
return properties[member, default: "valueが存在しないです"]
}
}
let taylor = Person()
print(taylor.name) // "Taylor Swift"
print(taylor.city) // Nashville
print(taylor.favoriteIceCream) // "valueが存在しないです"
That will compile cleanly and run, even though name, city, and favoriteIceCream do not exist as properties on the Person type. Instead, they are all looked up at runtime: that code will print “Taylor Swift” and “Nashville” for the first two calls to print(), then an empty string for the final one because our dictionary doesn’t store anything for favoriteIceCream.
@dynamicMemberLookupのおかげでpersonに定義してなくても各プロパティアクセス可能
コンパイル後実行→ランタイム時に各プロパティを探しに行く
ただしこの時 “Taylor Swift” と “Nashville” はプリントされるけどfavoriteIceCreamはプリントされない
なぜならpropertiesディクショナリはfavoriteIceCreamを持ってないから
例2(オーバーロード)
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["name": "Taylor Swift", "city": "Nashville"]
return properties[member, default: ""]
}
subscript(dynamicMember member: String) -> Int {
let properties = ["age": 26, "height": 178]
return properties[member, default: 0]
}
}
Now that any property can be accessed in more than one way, Swift requires you to be clear which one should be run. That might be implicit, for example if you send the return value into a function that accepts only strings, or it might be explicit, like this:
let taylor = Person()
let age: Int = taylor.age
オーバーロードを使ってsubscript(dynamicMemberを2つ定義することは可能だけど
戻り値としてどっちが返されることを期待しているのか明示する必要がある
printAddress
ちなみに同様にオーバーロードでPersonに以下を定義した時
taylor.printAddress("555 Taylor Swift Avenue")が実行できた
→ 出力結果: "Hello! I live at the address 555 Taylor Swift Avenue."
定義を辿れなかったけど@dynamicMemberLookupを消すとコンパイルエラーになったから@dynamicMemberLookupに定義されてるものなのかもしれない
When that’s run, taylor.printAddress returns a closure that prints out a string, and the ("555 Taylor Swift Avenue") part immediately calls it with that input.
subscript(dynamicMember member: String) -> (_ input: String) -> Void {
return {
print("Hello! I live at the address \($0).")
}
}
例(JSON)
定義してないプロパティにアクセスできるという特性を利用してJSONオブジェクトのプロパティに直接アクセスできる点は便利かも。いちいちJSONデータに従ってアプリ側で定義しなくて済むから
@dynamicMemberLookup
struct JSON {
private var data: [String: Any]
init(data: [String: Any]) {
self.data = data
}
subscript(dynamicMember member: String) -> Any? {
return data[member]
}
}
安全性
ここまでみてきて便利だけど安全性は低そう
ランタイム時にアクセスするから注意して使わないといけないかも
役に立つとき
Again, that might seem like an odd feature for Swift to support, but it’s incredibly useful when writing bridging code between Swift and more dynamic languages — such as Ruby, Python, or JavaScript — or when writing other kinds of proxy-based code.
However, there is one more way to use @dynamicMemberLookup that can also be incredibly useful even within completely static Swift code — and that’s to combine it with key paths.
- SwiftとRubyやPython、JavaScriptのような動的言語間のブリッジコードを書くとき
- SwiftでKeyPathと組み合わせるとき
しかもこのときKeyPathを使っていても.みたいなことが必要なく xxx.xxxといったように自然にアクセスできる
形的には@dynamicMemberLookupとKeyPathを組み合わせることで
@dynamicMemberLookupは存在しないプロパティにアクセスしたときにエラーでわかるようになったし
KeyPathはtest.color.cgColorを見てわかる通り\.でのアクセスじゃなくて
.cgColorのように自然なアクセス方法に変わったという理解
subscript<Value>(dynamicMember keyPath: KeyPath<Object, Value>) -> Value {
object[keyPath: keyPath]
}
参照: https://www.swiftbysundell.com/tips/combining-dynamic-member-lookup-with-key-paths/
参照: https://qiita.com/s2mr/items/3989139b127054ae23a0
Objective-Cコードを含むSDKをSwiftアプリに組み込むときにすること
Spotify SDKを使うときに調べた内容
iOS SDK ドキュメント: https://developer.spotify.com/documentation/ios
-ObjC Linker Flagを設定
-ObjC Linker Flagとは
-ObjC Linker Flagとは
This allows us to compile the Objective-C code that is contained within the iOS SDK.
参照: https://developer.spotify.com/documentation/ios/getting-started#:~:text=Set -ObjC Linker Flag
iOS SDK内のObjective-Cコードをコンパイルするためのもの
bridging headerを設定
bridging headerとは
bridgin headerとは
bridging header, which will allow us to include Objective-C binaries inside of our Swift app.
Typically, this is named with the [YourApp]-Bridging-Header.h convention. Xcode may generate this for you, otherwise you will need to create this in the root directory of your project.
参照: https://developer.spotify.com/documentation/ios/getting-started#:~:text=enter -ObjC-,Add Bridging Header,-In the last
これを使うことでswiftで作られるアプリ内でObjective-Cのバイナリを含めることができる
基本的に、[アプリ名]-Briging-Header.hというファイル名がつけられてる
Xcodeが多分generateするけど、もしなければ自分で作る必要がある
bridgin headerの作成手順
Then you'll need to set the location of this bridging header by:
In the File Navigator, click on your project.
Click your project under Targets
Go to Build Settings
In the search box, enter Objective-C Bridging Header
Besides Objective-C Bridging Header, double click and enter [YourApp]-Bridging-Header.h
自分の場合はこの手順でやっても [YourApp]-Bridging-Header.hは生成されてなかったから
手動で作った
で、その中で今回でいえば #import <SpotifyiOS/SpotifyiOS.h>を書いた
*これは今回でいえば、SpotifyiOS SDK内のSpotifyiOS.hにObjective-C コードが含まれていてそれをswiftアプリに組み込むため #import <SpotifyiOS/SpotifyiOS.h>を書いてる
SceneDelegate vs AppDelegate
In general, the SceneDelegate class is an optional part of the iOS app architecture, and you do not need to use it in your app unless you need to manage multiple scenes. If you only have one scene in your app, you can use the AppDelegate class to manage the lifecycle of your app instead.
参照: https://dev.to/arkilis/scenedelegate-6h4
Xcodeでプロジェクトを作成したときにデフォルトだとSceneDelegateもAppDelegateもないのはこれが理由かな
要はマルチシーン対応を作るのかシングルシーン対応なのかは作り手が決めるもので勝手に決めるものではないから
ちなみにSceneとは何かについても書かれてる
A scene is a discrete unit of your app's user interface, such as a window or tab. The SceneDelegate class provides methods that are called at various points in the lifecycle of a scene, such as when a scene is about to be displayed, or when a scene is about to be destroyed.