iPhone アプリを自分でつくる 6.
今回の内容: 関数、 Type(構造体、クラス、enum)
関数
関数は機能をひとまとめにして再利用できるようにしたものです。
書き方は func 関数名() { 機能 } のようになります。
使う時は 関数名() となります。
なお名前は小文字で始まり、単語が連なる場合は二つ目以降の単語の頭を大文字で書きます。
下記のようにカレーライス関数 を作ってみました。
コンソールにprint() で材料を表示するだけですが…。
func curryRice() { // カレーライス関数をつくった
print("put carrot")
print("put onion")
print("put potato")
print("put beef")
print("put curry powder")
print("make rice")
}
curryRice() // カレーライス関数を使った
// コンソール表示 ----------
put carrot
put onion
put potato
put beef
put curry powder
make rice
関数は引数(ひきすう)をとることができます。
引数はParameterパラメータとも言います。
引数は関数名のあとのカッコを (name: String)のように 引数ラベル: 型を入れたかたちにします。そうすると関数の中で name として受け取るString型の引数を使って計算しますよ、という表明になります。
下の例では func lineStations(lines: [String])として文字配列を受け取る関数を設定しています。
呼び出す際に lineStations(lines: akitaLine) のようにして文字配列型の変数としてakitaLineを与えています。
var akitaLine: [String] = ["Tokyo", "Ueno", "Omiya"]
var kyushuLine: [String] = ["Hakata", "Shintosu", "Kurume"]
func lineStations(lines: [String]) {
for i in 0..<lines.count {
print("\(i + 1): \(lines[i]) station")
}
}
lineStations(lines: akitaLine) // ① akitaLine を引数とした
// コンソール表示 -----------------
1: Tokyo station
2: Ueno station
3: Omiya station
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
lineStations(lines: kyushuLine) // ② kyushuLine を引数とした
// コンソール表示
1: Hakata station
2: Shintosu station
3: Kurume station
使うときに引数の名前を書かないようにすることもできます。
printText() 関数を引数を使って書くと 通常は下記のようになります。
func printText(text: String) {
print(text)
}
printText(text: "Yoasobi") // 使うときに引数名が必要
このように、関数名がなにを必要とするのかわかる場合にわざわざ引数名を書かせたくないときがあります。
func printText(_ text: String) {
print(text)
}
printText("Yoasobi") // 使うときに引数名が必要ない
上の例のように引数ラベルの前にアンダーラインを記入すると、使うときには引数名を記入しないで呼び出すことになります。
print() 関数自体もそのようにしてつくられています。
( この場合、引数名を書いて呼び出そうとすると、「余分な argument アーギュメント:引数名がついていますよ」とのエラーが表示されます)
またアンダーラインの代わりに別の名前を書くと関数で使う名前と呼び出すときに使う名前を変えることができます。
func meet(at location: String) {
print("\(location)で会いましょう!")
}
meet(at: "鎌倉")
//
コンソール表示
鎌倉で会いましょう!
上のように関数の中と外でそれぞれ自然な言葉で使うことができます。
・"\(location)で会いましょう!"
・meet(at: "鎌倉")
自分でふたつの引数名を持つ関数を作成することはあまりないかもしれませんが、Swift 自体に設定されている関数では多く見られます。
値を返す関数
関数のカッコ() の後ろに -> String のように何型を返すのかを記入します。
値を返す関数は返す型を明記しないと翻訳するときにエラーとなります。
関数の中の最後に return 返す値 として返す値を設定します。
func janken() -> String {
let pattern = ["ぐー", "ちょき", "ぱー"] // 3つのじゃんけんパターンを配列で用意
var i = Int.random(in: 0..<2) // Intの持つランダム関数を使って0,1,2 のいずれかをi にセットする
var result = pattern[i] // 配列[index] の形で結果を決める
return result // 結果をreturn で返す
}
print(janken())
// コンソール表示
ぐー
下の例では略語を入れると空港名がString型で返ってきます。
※ switch place { case "CTS": "Chitose" のような記載がみられますが
return "Chitose" の省略形です。1行で書かれたreturn は省略できます。
値を返す関数を設定した場合は、必ずなんらかの(決められた型の)値を返すことが必要です。
func findAirport(place: String) -> String {
switch place {
case "CTS": "Chitose" // return "Chitose" の省略形、以下 default まで同様
case "NRT": "Narita"
case "HND": "Haneda"
case "NGO": "Centrair"
case "ITM": "Itami"
case "KIX": "Kansai"
case "FUK": "Fukuoka"
case "OKA": "Okinawa"
default: "Not registered"
}
}
let airport = findAirport(place: "CTS")
print(airport)
// コンソール表示
Chitose
引数を複数取る関数
引数の設定の数に制限はありません
func addAll(a: Int, b: Int, c: Int) -> Int { // 引数を3つ設定して Intを返すようにした
return a + b + c
}
let answer = addAll(a: 3, b: 4, c: 5)
// コンソール表示
12
返す値が複数ある関数
複数の値を返すにはTuple(タプル、チュープル)と呼ばれる形で返します。
func getPlayerName(player: String) -> (firstName: String, lastName:String) {
if player == "suzuki" {
return (firstName: "Ichiro", lastName: "Suzuki")
} else {
return (firstName: "Unknown", lastName: "name")
}
}
let player = getPlayerName(player: "suzuki")
print("Name: \(player.firstName) \(player.lastName)") // タプルを一つづつ表示
// コンソール表示
Name: Ichiro Suzuki
上の例ではタプルを (firstName: String, lastName: String)として返す値に設定。
タプルの内容を呼び出すときには、player.firstName とplayer.lastName というように変数に. ドットをつけて名前で呼び出しています。
なお、設定するときに名前をつけない場合は、自動的に0、1 というように番号が順番につけられます。なお上記では2つの要素でしたが、タプルの扱う数に制限はありません。
また受け取るときに変数自体をタプルにしておく方法もあります。
let (first, last) = getPlayerName(player: "suzuki") // 受け取る変数自体をタプルにした
print(first, last)
// コンソール表示
Ichiro Suzuki
Type(型)
Type(型) には struct、 class、enum の3種類があります。
え〜!、今まで String型、Int型、Double型、Array型とかたくさん型があったはずでは?!とおっしゃるあなた。
ご説明すると例えばString型は struct String としてstruct(構造体)で作られたものであり、Unicodeキャラクター(1文字)を文字列として扱う値(value)を持つdata型です。
String型値、Int型値、Double型値、Array型値、その他多くの値はstruct(構造体) で作られています。
また、SwiftUI では画面に表示するview もstruct で作成します。
このようにここで挙げる3つの型というのは、値や関数をまとめる機能を持つものとしての型を言います。
struct(構造体)
struct は構造体(structure)とよばれ変数や関数を中に持つオブジェクトです。
オブジェクトというのはなんらかのひとかたまり、まとまりをあらわすものです。
例えば1人の人間は顔の形、肌の色があり、走る、食べるという行動をするオブジェクトとしてとらえます。家というオブジェクトには屋根があり壁があり色があり部屋にはキッチンや風呂などの機能をもっているひとまとまりと考えます。
struct は変数と関数を合わせてひとまとまりとして性質、行動を表現します。
ひとつのstruct も簡単に1,000や10,000 のオブジェクトに増やしてそれぞれに個性を持たせて使うことができます。
それぞれのオブジェクトが独立して働いたり、影響しあったりしてひとつのアプリできていく、そんなイメージかと思います。
structの名前は大文字で始まるCamel case でつくります。
struct が持つ変数や定数はプロパティ(特性)と呼ばれます。
struct が持つ関数はメソッド(行動・方式)と呼ばれます。
struct Car {
var name: String
var color: String
var speed: Int
var type: String = "Sports" // type プロパティはここでは初期化(入力値がある)がされている
func estTime(distance: Int) -> String {
let hour = distance / speed
let remain = distance % speed
let minutes = Double(remain) / Double(speed) * 60
let result = "\(hour)h\(Int(minutes))m"
return result
}
}
var myCar = Car(name: "Countach", color: "Red", speed: 320) // イニシャライズしたものを変数myCarに代入した
print(myCar.name) // 変数myCar のnameプロパティを調べた
// コンソール表示
Countach
上の例ではCar 構造体は name, color, speed などのプロパティが入るのを待つ状態です。
var myCar = Car(name: "Countach", color: "Red", speed: 320) としてイニシャライズ(初期化、使える状態)され変数に入れられました。
このように関数を使用するときのようにオブジェクトをイニシャライズすることができます。
名前を確認するために、変数.name で表示させています。
またCarオブジェクトの持っている機能 estTime(distance: Int) を使って走行距離あたりの時間を確認することができます。
let time = myCar.estTime(distance: 1200) // myCar構造体が持つ estTime() メソッドを使用して時間を変数にいれた
print(time)
// コンソール表示
3h45m
struct Car をエクセルで表現してみます
※ 図は個人のイメージです。
struct の持つ関数で自分自身のプロパティを変えたいときがあります。
その場合は mutating func 名前() { のようにmutating を使用します。
これは struct はimmutable なので特別な処理が必要だからです。
struct Car {
var name: String
var color: String
var speed: Int
var type: String = "Sports"
func estTime(distance: Int) -> String {
let hour = distance / speed
let remain = distance % speed
let minutes = Double(remain) / Double(speed) * 60
let result = "\(hour)h\(Int(minutes))m"
return result
}
mutating func changeType() { // mutating func によって自分自身のプロパティ typeを変更する
type = "Super"
}
}
var myCar = Car(name: "Countach", color: "Red", speed: 320)
myCar.changeType()
print(myCar.type)
// コンソール表示
Super
Computed property コンピューティッドプロパティ
アクセスされたときに計算して最新の情報に自動更新するプロパティです。
他のプロパティが決まったときに計算によって値が算出できるときに使います。
struct Shain {
let name: String
var yukyuAsigned = 20
var yukyuTaken = 0
var yukyuRemain: Int { // 有休付与数から有休消化数を引いて算出できるのでコンピューティッドプロパティを使用した
yukyuAsigned - yukyuTaken
}
}
var inoue = Shain(name: "Inoue", yukyuTaken: 8)
print(inoue.yukyuRemain)
// コンソール表示
12 // 有休の残りは 12日ということがコンピューティッドプロパティでわかる
上の例では yukyuRemain は他のプロパティによって値が決まるものでしたが、
値を与えることもできます。その場合get と set を使用します。
・get は通常の計算: 他のプロパティの値で決定する(取ってくる)もの
・set は逆にこの値を決めることにより他の値を変化させるもの
です。
struct Shain {
let name: String
var yukyuAsigned = 20 // 有休付与
var yukyuTaken = 0 // 有休消化
var yukyuRemain: Int { // 有休残り
get {
yukyuAsigned - yukyuTaken
}
set {
yukyuAsigned = yukyuTaken + newValue
}
}
}
var inoue = Shain(name: "Inoue", yukyuTaken: 8)
inoue.yukyuRemain = 8
print(inoue.yukyuAsigned)
// コンソール表示
16
get とset は それぞれ getter、 setter と呼ばれます。
コンピューティッドプロパティは自動実行関数を持ったプロパティといえますが、あまり大きくしていくと読みにくくなるので、その場合は関数として分離する検討が必要になります。
struct のイニシャライザ
struct が初期化される(使われるとき)ときにはすべてのプロパティがイニシャライズされている(変数の値が決まっている)必要があります。
そのためにイニシャライザが用意されています。
struct が初期化されるために、どのプロパティをどんな名前を与えてくださいよ、またはプロパティの最初の設定はこうしておきますよ、ということを決定するものがイニシャライザ init です。
使い方をみていただくとメソッドと同じ形 init() { } つまり名前 init があって()カッコがあって { } 波括弧のなかに変数があって… となっています。(先頭にfuncの文字はありませんが。)
イニシャライザは作られるときに一回だけ自動実行されるメソッドといえます。
下の例ではinit(name: String) となっているので、name としてString を与えるとイニシャライズできることがわかります。
Camera構造体からすると、「作る時は nameをString型でくださいね」と言っていることになります。
またcompany = "Canon" が最初に決定されて作られることもわかります。
struct Camera {
var name: String
var company: String
init(name: String) {
self.name = name
company = "Canon"
}
}
var myCamera = Camera(name: "EOS")
print(myCamera.company)
self.name = name の selfはオブジェクト自身つまりCamera の var name、右のname は イニシャライズするときに Camera(name: "EOS") のようにタイトルをつけますよ、というname のことです。
name ばかりで少しわかりにくいので、下の例では、self.name = cameraName としました。
struct Camera {
var name: String
var company: String
init(cameraName: String) { // プロパティはname だが、init はcameraName とした
self.name = cameraName
company = "Canon"
}
}
var myCamera = Camera(cameraName: "EOS")
この場合、イニシャライズは Camera(cameraName: "EOS") となります。
カーソルをその単語においてみると繋がりが背景色でわかります。
なお init を使用したイニシャライザをしない場合でも、Swiftがmemberwise イニシャライザと呼ぶ自動実行イニシャライザを働かせて確実に初期化されるようにコントロールしています。
(この次に説明するclass には自動実行イニシャライザはありません)
参考までですが、上記の Camera(name: "EOS") をフルスペルで書くと下のように Camera.init(name: "EOS") となります。メソッドと同じように Camera(name: "EOS") として使えるように工夫されているのです。
var myCamera = Camera.init(name: "EOS")
static プロパティ staticメソッド
structにstatic プロパティやメソッドを入れるとインスタンス化しなくても即座に
アクセスできる。軽い、早い…です。
static(静的)プロパティ、メソッドは変化がなく値が決まっているもの、または他の変数に影響を与えたり受けたりしないものです。
アプリ立ち上げの最初にシステムが内容を覚えておいて、呼び出されるのを待っています。
struct AppInfo {
static let version = "1.2.1"
}
print(AppInfo.version)
クラス
クラスは最初にclass と書きますが、その他の書き方の構造的にはstruct と同じです。クラスは変数や関数を持つオブジェクトです。
・classの名前は大文字で始まるCamel case でつくります。
・class が持つ変数や定数はプロパティ(特性)と呼ばれます。
・class が持つ関数はメソッド(行動方式)と呼ばれます。
ここまではstruct と同じなのですが、大きな違いがふたつあります。
・class は継承する
・class は参照型である
ひとつめの継承ですが、クラスは他のクラスの機能をそのまま持たせた上で機能を追加することができるというのが継承です。
そして機能を追加したクラスを使ってさらに機能を追加したクラスをつくることができます。機能を追加して大きなパワーを持つ新たなクラスをつくることができるのです。なおひとつのクラスが継承できるのはひとつのクラスからだけです。
まずは通常のクラスを記入してみます。struct と同様の記入方法です。
// クラスは struct と同じ書き方で作ったり、呼び出したりできます。
class Game {
var score = 0 // ここでは 初期値が設定されているので init イニシャライザを使用していない
func showScore() {
print("Score is now \(score)")
}
}
var newGame = Game()
newGame.score += 10
newGame.showScore() //Score is now 10
なおクラスは自動実行イニシャライザを持たないので値が決まっていないプロパティがあるときは必ず init でイニシャライザをセットしておく必要があります。
// クラスは 値が決まっていないプロパティがあるときは必ず init を設定する
class Student {
var name: String
init(name: String) {
self.name = name
}
}
次は一つのクラスを継承して新しいクラスを作る方法です。
下のクラスTaxi とクラス Bus はクラス Car の子クラス(チャイルドクラス)です。Car は親クラス(スーパークラス)と呼ばれます。
継承方法は子クラスの名前の後に : Car のようにします。
// 下記は継承関係にあるクラスです
// クラス Car は クラスTaxi とクラスBus の 親クラス(スーパークラス)です。
// クラス Taxi はクラス Car の子クラス(チャイルドクラス)です。
// クラス Bus はクラス Car の子クラス(チャイルドクラス)です。
class Car {
let rideCapacity: Int
let fuel: String
init(rideCapacity: Int, fuel: String) {
self.rideCapacity = rideCapacity
self.fuel = fuel
}
func drive() {
print("This is able to ride \(rideCapacity) people.")
}
}
class Taxi: Car { // : Car として継承した
func putFuel() {
print("Please put in \(fuel).")
}
}
class Bus: Car { // : Car として継承した
func putFuel() {
print("Please put in \(fuel).")
}
}
let suzume = Taxi(rideCapacity: 4, fuel: "LP gas")
let hato = Bus(rideCapacity: 50, fuel: "Diesel")
suzume.drive() // drive() は親が持つメソッド
hato.drive()
suzume.putFuel() // putFuel() は子クラスにそれぞれ設定したもの
hato.putFuel()
// コンソール表示--------------------------------------------
This is able to ride 4 people.
This is able to ride 50 people.
Please put in LP gas.
Please put in Diesel.
上の例ではチャイルドクラスのTaxi 、Bus は親クラス Car のdrive() メソッドを継承して使用しています。また変数 fuel を受け継いでいるので、それぞれ変数として書かれていなくても使用できるところも大きな特徴です。
親クラスのメソッドの内容を書き換える(over ride オーバーライドする)こともできます。(引数を取る or 取らないなど、もとの使い方と違う場合にはover ride はせず通常のメソッドを記入する: 例えば func drive(private: Bool)メソッドをつくるなど)
class Van: Car {
override func drive() { // drive() をオーバーライドしている
print("This is \(rideCapacity) people with 1 wheelchair.")
}
func putFuel() {
print("Please put in \(fuel).")
}
}
let neko = Van(rideCapacity: 20, fuel: "gasoline")
neko.drive()
neko.putFuel()
// コンソール表示--------------------------------------------
This is 20 people with 1 wheelchair.
Please put in gasoline.
子クラスがイニシャライズが必要となる場合には、親クラスのイニシャライズ含めて全てのイニシャライズを行う。
init の順番と最後に親クラスを super.init() としてイニシャライズすることに注意します。
init(親のプロパティネーム、自分のプロパティネーム) {
self.自分のプロパティ = プロパティネーム
super.init(親のプロパティ)
のようになります。
class ElectricCar: Car {
let isHomePlug: Bool
init(rideCapacity: Int, fuel: String, isHomePlug: Bool) {
self.isHomePlug = isHomePlug
super.init(rideCapacity: rideCapacity, fuel: fuel)
}
}
let newCar = ElectricCar(rideCapacity: 4, fuel: "N/A", isHomePlug: true)
newCar.drive()
print(newCar.fuel)
print(newCar.isHomePlug)
// コンソール表示--------------------------------------------
This is able to ride 4 people.
N/A
true
ここまではクラスの継承についてでしたが、ここからクラスは参照型であるということについてのお話しです。
参照型を確かめるためにイニシャライズされたクラスを変数に入れます。
その変数を別の変数にコピーします。
class Idol {
var name = "Ikura"
}
var myIdol1 = Idol() // Idol() を初期化した
var myIdol2 = myIdol1 // コピーをつくった
myIdol2.name = "Momoe" // コピーの持つ名前を変えた
print(myIdol1.name)
print(myIdol2.name)
// コンソール表示
Momoe // 名前が同じになってしまった
Momoe
オリジナルの変数もとに戻そうとしてもこんどはその値で同じになってしまいます。
myIdol2.name = "Momoe"
myIdol1.name = "Ikura" // オリジナルをもとに戻してみた
print(myIdol1.name)
print(myIdol2.name)
// コンソール表示
Ikura // また名前が同じになってしまった
Ikura
このようにコピーしたプロパティはオリジナルと同じ値を持ちます。
しかしclass を struct に変更してみると意図するデータを変更ができます。
この動きがstruct は値型、classは参照型を示すものです。
値型は自分で自分の値を持ち歩いているのですが、参照型は自分の名前が書いてある住所メモしか持っていません。
参照型をコピーすると、その住所メモもいっしょにコピーされます。そして値を変更することは住所メモの先にある名前を変更することになるので、そのメモを持っている人はみんな名前が変わってしまうのです。
図は個人のイメージです
上のケースの場合、新たなイニシャライズを作成すれば別々の値をつくることになります。(別々の住所メモを持たせるということになります)
たた上のコードであると、新たなにイニシャライズを作ろうとすると、イニシャライザが設定されていないので断られてしまいます。①
イニシャライザを設定してから新たなオブジェクトをつくれば別のデータとして扱うことができます。②
他の方法として次のように、クラス自体にコピー機能を持たせれば、コピーをした際に新たにイニシャライズしたものを受け取ることができます。
class Idol {
var name = "Ikura"
func copy() -> Idol { // Idol型の値を返す コピーメソッドを作成
let idol = Idol() // コピーメソッドを作成してイニシャライズしたものができるようにした
idol.name = name
return idol
}
}
var myIdol1 = Idol()
var myIdol2 = myIdol1.copy() // コピーメソッドを使ってIdol クラスを作成した
// この時点ではmyIdol2 は"Ikura" であるが myIdol1 とは別に初期化されたもの
myIdol2.name = "Momoe"
print(myIdol1.name)
print(myIdol2.name)
// コンソール表示
Ikura
Momoe
デイニシャライザ Deinitializer
クラスにはデイニシャライザというものがありセットしておくことができます。
イニシャライザは初期化するときにどのような状態にするかをきめるものでしたが、デイニシャライザはクラスが使われなくなったときに働くものです。
クラスは参照型のためあちらこちらから参照されることがあり、その参照がなくなったときに使わなくなったと判断します。システムはクラスが作られたときから、いくつ参照されているか数え続け、参照がゼロになったらメモリを解放します。
もし参照先が循環してしまうとメモリが解放されないためメモリリークという状態を起こしパフォーマンスが低下につながります。
デイニシャライザをセットしてクラスの解放のタイミングをみることができます。
次の例では for ループで順番にイニシャライズされては解放されることがわかります。
例えば forループ 1 から 2 に変わったときに driver 1 は参照されなくなって deinitializerが働きます。
class Driver {
let id: Int
init(id: Int) {
self.id = id
print("Driver \(id) started.")
}
deinit {
print("Driver \(id) arrived!")
}
}
for i in 1...3 {
let driver = Driver(id: i)
print("Driver \(driver.id) driving.")
}
// コンソール表示
Driver 1 started.
Driver 1 driving.
Driver 1 arrived!
Driver 2 started.
Driver 2 driving.
Driver 2 arrived!
Driver 3 started.
Driver 3 driving.
Driver 3 arrived!
次の例では for ループ処理をしてdrivers の配列にappend しています。
ループ処理が全て終わってから配列が削除されるとき(.removeAll())にすべてのdriver の参照が終了して解放されます。
class Driver {
let id: Int
init(id: Int) {
self.id = id
print("Driver \(id) started.")
}
deinit {
print("Driver \(id) arrived!")
}
}
var drivers = [Driver]()
for i in 1...3 {
let driver = Driver(id: i)
print("Driver \(driver.id) driving.")
drivers.append(driver)
}
print("All arrived!")
drivers.removeAll()
// コンソール表示
Driver 1 started.
Driver 1 driving.
Driver 2 started.
Driver 2 driving.
Driver 3 started.
Driver 3 driving.
All arrived!
Driver 1 arrived!
Driver 2 arrived!
Driver 3 arrived!
Enum型(列挙型)
enum型 イナム、イーナム は区分を列挙しておく型です。
enumeration(ナンバリング)からとった名前です。
例えば Bool型を設定した変数には true または false の二者択一 の値が入り、それ以外はエラーとなるのでプログラム作成時のエラーを防ぐことができます。
それと同様に enum型を設定しておけば、そこには enum で列挙したメンバー(区分)しか受け付けないようになります。また、コードを読む際にも、区分すべてがまとめられているので、読みやすくなります。
このメンバー(区分)それぞれに名前や数値などの値をセットすることもできます。default では 0からの数値がナンバリングされます。
// じゃんけんの enum型
enum Janken {
case gu
case choki
case pa
}
var pon: Janken = .choki
pon = .gu
// - * - * - * -
// case は横並びで記入することもできます
enum Week {
case sunday, monday, tuesday, wednesday, thursday, friday, saturday
}
var day: Week = Week.monday
day = .saturday
if day == .sunday || day == .saturday {
print("I'm going to watch movies!")
} else {
print("I go to work...")
}
// コンソール表示
I'm going to watch movies!
具体的な使用方法がわかりにくいかもしれませんが、 enum はアプリを作っていく上でとても頼りになる存在となるので、細かな使い方について実際に使う際に説明していきます。
まとめ
関数
関数は機能をひとまとめにして再利用できるようにしたものです。
関数は引数(ひきすう)をとることができます。
値を返す関数をつくることができます。
Type(型)
struct(構造体)
struct は変数や関数を持つオブジェクトです。
struct は値型です。
クラス
クラス は変数や関数を持つオブジェクトです。
・継承する
・参照型である
ところが大きな違いです。
enum(列挙型)
enum を設定することで列挙したメンバー(区分)しか受け付けないようになるので間違いのないコードを書けるようになります。
次回第7回予定 クロージャ、エラーハンドリング、
プロトコル、エクステンション です。
よろしくお願いします。
この記事が気に入ったらサポートをしてみませんか?