【プログラミング初心者】Swift基礎~メソッド・関数~

はじめに

今回はSwiftでのメソッド作成方法についていくつか紹介します。

メソッド・関数とは

関数

そもそも関数がどういうものか説明します。

簡単にいうと処理の塊です。

プログラムにはmain関数と呼ばれるプログラムを実行した時最初に呼ばれる処理があります。
(iOSアプリをSwiftで開発する場合は内部で定義されているので見れませんが)
極論をいうとこのmain関数さえあれば関数を使わなくてもプログラムは書けます。

ですが現実的に数万行に及ぶ処理がずらずらと書かれていても処理を把握することは困難です。
そのため関数に処理を分けてあげて関係する処理を分割してあげています。

さらに同じ処理をする場合は関数に書くことで重複するコードを書かなくて済みます。

関数は数学で使っていた関数と似ており、何かInputを与えて処理の結果をOutputとして返します。
y = ax + bという1次関数があります。
これはxに何かInputを与えた結果yというOutputを得るという処理です。
これをプログラムで書くと以下になります。

let a = 5
let b = 10
func linearFunction(x: Int) -> Int {
    return a * x + b
}

let y = linearFunction(x: 3)

この何かしら処理をするlinearFunctionのようなものを関数と呼びます。

引数

関数には必須ではありませんが引数を設定できます。
上記の例で言うとx: Intが引数です。

引数は関数へのInputです。
この与えられた引数を使って処理を制御します。
もちろん引数なしで制御可能な場合は定義しなくても問題ありません。
必要とする場合は型アノテーションが必須となります。

与えられた引数はletつまり定数として定義されます。
従って変更することはできません。
あくまで与えられた値を使って何か処理をするという用途になります。

また複数指定することができます。
複数指定する場合は以下のようになります。

func function(arg1: Int, arg2: Int, arg3: String) {
}

戻り値・返り値

戻り値は関数からのOutputです。
関数が値を返してくる値を戻り値や返り値と呼びます。
戻り値なしの場合は不要ですが、戻り値がある場合は-> 戻り値の型というように戻り値の型を明示する必要があります。

func function(arg1: Int, arg2: Int, arg3: String) -> Int {
}

戻り値を設定した場合は必ず何かしらの値を返さなければなりません。

戻り値はreturn 値という構文で返します。
returnが呼ばれると後続の処理が呼ばれないので注意してください。

func function(arg1: Int) -> Int {
    return arg1 + 1
    print(arg1) // 実行されない
}

戻り値なし場合以下のように値なしreturnできます。

func function() {
    return
}

補足ですが戻り値の場合暗黙的にVoidという戻り値が返っています。
これは何もないというような意味です。
なので上記の例は以下と同じ意味です。

func function() -> Void {
    return
}

メソッド

メソッドというものもあります。
メソッドも関数の一種で、クラスが持つ関数のことです。

基本的にSwiftはクラス内で関数を定義しているため、メソッドという言葉を使うことが多いです。
ですが関数といっても間違いではないので問題ありません。

定義の仕方は関数と同じです。

呼び出し方

関数の場合、以下のようにそのまま直接呼び出すことができます。

var returnValue = function()

print()なども関数なのでそのまま呼び出すことができます。

メソッドの場合、オブジェクト.関数名()という形で呼び出します。

let object = Object()
let returnValue = object.function()

クラス内で自分のメソッドを呼び出す場合は以下のようにします。

let returnValue = self.function()

selfは自分自身のオブジェクトを示します。他の言語だとthisとかになったりします。
実はSwiftの場合selfを省略しても言語上問題ありません。

class SampleClass {
    func function() -> Int {
        return 1 + 1
    }

    func test() {
        let selfValue = self.function()
        let noSelfValue = function()
    }
}

これはチームのコーディング規約に則って付けるか付けないか決めてください。

Swiftメソッド記法

Swiftは言語として少し特殊で引数の書き方がいくつかあります。
よく使う例を紹介していきます。

デフォルト値指定

引数にデフォルト値を指定することができます。
デフォルトを設定した引数は引数なしで呼び出すことができます。

func function(arg1: Int = 0, arg2: Int = 0) {
    print("arg1: \(arg1), arg2: \(arg2)")
}

self.function()                    // arg1: 0, arg2: 0
self.function(arg1: 10)            // arg1: 10, arg2: 0
self.function(arg2: 10)            // arg1: 0, arg2: 10
self.function(arg1: 10, arg2: 10)  // arg1: 10, arg2: 10

引数のラベルを省略

以下のように引数前に_をつけて定義すると、メソッドコールのとき引数のarg1:というラベルを省略することができます。

func function(_ arg1: Int, arg2: Int) {
}

self.function(1, arg2: 1) // OK
self.function(1, 1) // NG
self.function(arg1: 1, arg2: 1) // NG

引数のラベルに別名を付ける

ラベルに別名をつけることができます。

func convert(from value: Int) -> String {
    return "\(value)"
}

let stringValue = self.convert(from: 1) // OK
let stringValue = self.convert(value: 1) // NG

補足ですが、この方法はSwfit特有のものではありますがよく使います。
よくfrom to withのような前置詞を使って別名を付けメソッド自体が英文として読めるように書くことが多いかと思います。

メソッドの引数について

以前変数がどのようにメモリに確保されるのかを説明しました。
メソッドの引数の場合はどうか見ていきます。

値渡し

先述しましたが引数はletとして新しく定義されているのと同義です。
そのため引数も変数もメモリ上の扱いは同じになります。

以下のように構造体とメソッドを定義し呼び出してみます。

struct Dog {
    var name: String
}

func update(with newName: String, dog: Dog) {
    dog.name = newName
}

var myDog = Dog(name: "ポチ")

print("更新前: " + myDog.name)
self.update(with: "ミケ", dog: myDog)
print("更新後: " + myDog.name)

この場合メソッド定義の部分でCannot assign to property: 'dog' is a 'let' constantとSwiftのエラーが発生し実行はできません。
これは引数がletで定義されているため発生したエラーです。
ですが、もし仮にSwiftがエラーを返さないと仮定します。
その場合であってもメソッド内のdog.nameは「ミケ」に更新されますが、元となっているmyDog.nameは更新されません。
これは構造体が値型のため引数として渡されたdogmyDogのコピーとなっているためです。
従って仮に実行できたとすると以下のような出力になります。

更新前: ポチ
更新後: ポチ

参照渡し

ではクラスの場合はどうでしょう?
以下のようにクラスとメソッドを定義し呼び出してみます。

class Cat {
    var name: String

    init(name: String) {
        self.name = name
    }
}

func update(with newName: String, cat: Cat) {
    cat.name = newName
}

var myCat = Cat(name: "タマ")

print("更新前: " + myCat.name)
self.update(with: "トラ", cat: myCat)
print("更新後: " + myCat.name)

これはエラーは発生しません。
クラスの場合、参照型のため変数がletで定義されていても変数の値そのものを書換えているわけではないのでこれを実行できます。
実行すると以下のように出力されます。

更新前: タマ
更新後: トラ

myCat.nameの値が更新されていますね。
参照型は引数にはmyCatへのアドレスをコピーとして渡しているため同じオブジェクトを参照しており、そのため内容の更新ができたというわけです。

このように引数にアドレス、つまり参照を渡しているものを参照渡しと言います。

値型を参照渡ししたいときは?

基本的にはやりません。
その場合は以下のように戻り値として新しいオブジェクトを返すように実装する方法が一般的です。

func update(with newName: String, dog: Dog) -> Dog {
    var newDog = dog
    newDog.name = newName
    return newDog
} 

let myDog = Dog(name: "ポチ")
let newDog = self.update(with: "ミケ", dog: myDog)
print(newDog.name) // ミケ

それでもどうしても構造体を参照渡ししたいということもあるかと思います。
その場合は以下の方法で実装できます。

func update(with newName: String, dog: inout Dog) {
    dog.name = newName
} 

var myDog = Dog(name: "ポチ")

print(myDog.name) // ポチ
self.update(with: "ミケ", dog: &myDog)
print(myDog.name) // ミケ

update実行前と後でmyDog.nameの値が変わっていることから参照渡しになったことがわかります。

引数がdog: inout Dogとなりました。
inoutを引数の型の前に付けると参照型という意味になります。

引数に渡す値は&myDogとなっています。
変数名の前に&を付けるとその変数のアドレスを意味します。
myDogはスタック領域に確保されているのでそのスタック上のアドレスが引数として渡されました。
その結果参照渡しとなりnameを更新できるようになりました。

最後に

今回はメソッドについて、またそのメソッドの定義の仕方や引数の性質について説明しました。

引数の定義の仕方はおそらくチームによって別れてくるところかもしれません。
どれでも書けるようにしておくのが無難と言えます。

今回の内容は以上です。

コメント

タイトルとURLをコピーしました