본문 바로가기
문법관련/Swift

메소드 (꼼꼼한 재은 씨의 Swift : 문법편)

by print_soo 2022. 4. 2.

메소드는 일종의 함수로서, 클래스나 구조체, 열거형과 같은 객체 내에서 함수가 선언될 경우 말한다. 

즉, 메소드는 특정 타입의 객체 내부에서 사용하는 함수라고 할 수 있다. 

 

<함수와 메소드의 차이점>

이 둘의 차이점은 구현 목적이 가는 독립성과 연관성에 있다. 함수는 독립적인 기능을 구현하기 위해서 만들어지는 것이지만, 메소드는 하나의 객체 내에 정의된 다른 메소드와 서로 협력하여 함수적인 기능을 수행한다.


메소드는 크게 인스턴스 메소드타입 메소드로 구분된다.

객체의 인스턴스를 생성해야 사용할 수 있는 인스턴스 메소드, 객체의 인스턴스를 생성하지 않아도 사용할 수 있는 타입 메소드이다. 

따라서 인스턴스 메소드는 객체 타입 자체로는 호출할 수 없고 반드시 인스턴스를 생성한 후에 호출할 수 있다는 점에서 타입 메소드와 다르다.

 

[인스턴스 메소드]

인스턴스 메소드는 클래스, 구조체, 열거형과 같은 객체 타입이 만들어내는 인스턴스에 소속된 함수이다. 인스턴스 메소드는 객체 타입 내부에 선언된다는 점을 제외하고 일반 함수와 선언하는 형식이 완전히 동일하다. 

 

다음 예제를 통해서 구조체와 클래스에서 인스턴스 메소드를 정의해보자.\

struct Resolution {
    var width = 0
    var height = 0

    func desc() -> String {
        let desc = "이 해상도는 가로 \(self.width) X 세로 \(self.height)로 구성됩니다."
        return desc
    }

}



class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?

    func desc() -> String {
        if self.name != nil {
            let desc = "이 \(self.name!) 비디오 모드는 \(self.frameRate)의 프레임 비율로 표시됩니다. "
            return desc

        } else {
            let desc = "이 비디오 모드는 \(self.frameRate)의 프레임 비율로 표시됩니다."
            return desc
        }
    }

}

위의 예제에서 desc() 함수가 바로 구조체와 클래스의 멤버인 메소드이다. 정확히 말하면 인스턴스 메소드이다. 

인스턴스 메소드와 일반 함수의 선언 형식은 같지만 다음 세가지 항목에서 차이가 있다. 

1. 구조체와 클래스의 인스턴스에 소속된다는 점
2. 메소드 내에서 정의된 변수와 상수뿐만 아니라  클래스 범위에서 정의된 프로퍼티도 모두 참조할 수 있다는 점
3. self 키워드를 사용할 수 있다는점. 

여기서 우리가 살펴봐야하는 건 3번이다. 구조체 Resolution에서 선언된 desc() 메소드는 내부 프로퍼티를 이용하여 문장을 반환하는 역할을 한다. desc 상수에서 내부 프로퍼티의 값을 사용할 때 self라는 키워드가 있는 것을 확인할 수 있다. 

 

이 키워드는 인스턴스 메소드 내에서 프로퍼티를 읽어올 경우 사용하게 된다. 프로퍼티 앞에 붇은 self 키워드는 클래스나 구조체 자신을 가리킨다. 정확히는 클래스나 구조체의 인스턴스 자신을 가리킨다고 할 수 있다. 

 

따라서 self.width를 해석해보면 '클래스나 구조체 자신의 인스턴스에 속한 width 프로퍼티'라는 의미가 된다. 

 

프로퍼티에 반드시 self 키워드를 붙이지 않더라도 swift 자체에서 잘 처리를 해주는데 만약 메소드 내부에 프로퍼티와 동일한 이름을 가진 변수나 상수가 있다면 반드시 self 키워드를 사용해야한다. 

struct Resolution {
    var width = 0
    var height = 0

    func judge() -> Bool {
        let width = 30
        return self.width == width
    } //false

}

judge 메소드가 하는 역할은 지역 상수인 width를 선언하고 self.width(멤버변수 - 구조체 내부에 선언된 변수)와 width(지역 변수 - 메소드 내부에 선언된 변수) 값을 비교해서 그 결과를 반환한다. 

 

또 다른 예제를 살펴보자. 이 예제는 Counter라는 클래스인데 특정 동작이 발생한 횟수를 카운트하는데 사용되는 객체이다. 

class Counter {
    
    var count = 0
    
    func increment() { // 카운트 1 증가
        self.count += 1
    }
    
    func incrementBy(amount: Int) { // 입력된 값만큼 카운트 증가
        self.count += amount
    }
    
    func reset() { // 카운트 초기화
        self.count = 0
    }
}

let c = Counter()

c.increment() 
print(c.count) // 1
c.incrementBy(amount: 10) // 11
print(c.count)

인자값이 있는 메소드를 호출할 때는 함수와 동일한 규칙이 적용된다. 즉, 호출 시 인자값 앞에 인자 레이블을 붙여주어야 한다. 

 

주의할 점이 하나 있는데, 구조체나 열거형의 인스턴스 메소드 내부에서 프로퍼티의 값을 수정할 때는 반드시 'mutating'이라는 키워드를 추가해야한다.  또 구조체나 열거형을 상수로 할당 받으면 mutating 메소드를 호출할 수 없다. 

 

아래의 예제를 통해서 이해해보자. 

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(x deltalX: Double, y deltaY: Double) {
        self.x += deltalX
        self.y += deltaY
    }
}

var point = Point(x: 10.5, y: 12.0)
point.moveByX(x: 3.0, y: 4.5)
print("새로운 좌표는 \(point.x), \(point.y)") //새로운 좌표는 13.5, 16.5

구조체, 열거형과는 달리 클래스의 인스턴스 메소드에서는 프로퍼티를 수정할 때 별도의 키워드가 필요하지 않다. 클래스에 정의된 모든 인스턴스 메소드는 인스턴스 내의 프로퍼티를 원하는 대로 바꿀 수 있다.

 

[타입 메소드]

타입 메소드는 인스턴스를 생성하지 않고도 객체 타입 자체에서 호출할 수 있는 메소드를 말한다.

 

선언 방식은 타입 프로퍼티와 동일하게 static과 class 키워드를 사용한다. 

class Foo {
    class func fooTypeMethod() {
        print("푸푸")
    }
}


let f = Foo()
f.fooTypeMethod() //에러
Foo.fooTypeMethod() //푸푸

타입 메소드를 사용할 때는 주의할 부분이 있다. 타입 메소드를 사용하여 객체의 값을 변경하면 해당 객체 타입을 사용하는 모든 곳에서 변경된 값이 적용되기 때문에 타입 메소드를 선언하고 사용할 때는 반드시 이러한 영향 범위를 고려해야한다. 

 

또한 타입 메소드에소는 인스턴스 프로퍼티를 참조할 수 없다. 타입 메소드 자체에 인스턴스가 존재하지 않기 때문이다.