🔥 연산자 메서드

818자
10분

Swift에서는 클래스와 구조체가 기존 연산자의 구현을 직접 제공할 수 있어요. 이를 기존 연산자의 오버로딩이라고 부릅니다.

아래 예제는 사용자 정의 구조체에 대해 산술 덧셈 연산자(+)를 구현하는 방법을 보여주고 있어요. 산술 덧셈 연산자는 두 개의 피연산자에 작용하므로 이항 연산자이며, 두 피연산자 사이에 나타나므로 중위 연산자랍니다.

예제에서는 2차원 위치 벡터 (x, y)를 나타내는 Vector2D 구조체를 정의한 후, Vector2D 구조체의 인스턴스를 더하기 위한 연산자 메서드를 정의하고 있죠:

struct Vector2D {
    var x = 0.0, y = 0.0
}
 
extension Vector2D {
    static func + (left: Vector2D, right: Vector2D) -> Vector2D {
        // 두 Vector2D 인스턴스의 x와 y 속성을 각각 더해서 새로운 Vector2D 인스턴스를 반환합니다.
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
}
swift

연산자 메서드는 Vector2D의 타입 메서드로 정의되며, 오버로딩할 연산자(+)와 일치하는 메서드 이름을 가지고 있어요. 덧셈은 벡터의 본질적인 동작이 아니므로, 타입 메서드는 Vector2D의 기본 구조체 선언이 아닌 Vector2D의 익스텐션에 정의되고 있죠. 산술 덧셈 연산자는 이항 연산자이므로, 이 연산자 메서드는 Vector2D 타입의 두 개의 입력 매개변수를 받고, 역시 Vector2D 타입의 단일 출력 값을 반환합니다.

이 구현에서 입력 매개변수는 + 연산자의 왼쪽과 오른쪽에 위치할 Vector2D 인스턴스를 나타내기 위해 leftright로 명명되었어요. 메서드는 새로운 Vector2D 인스턴스를 반환하는데, 이 인스턴스의 xy 속성은 더해질 두 Vector2D 인스턴스의 xy 속성의 합으로 초기화됩니다.

타입 메서드는 기존 Vector2D 인스턴스 사이의 중위 연산자로 사용될 수 있어요:

let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector는 (5.0, 5.0)의 값을 가진 Vector2D 인스턴스입니다.
swift

이 예제는 벡터 (3.0, 1.0)(2.0, 4.0)를 더해서 벡터 (5.0, 5.0)를 만들어 내고 있죠. 이를 그림으로 나타내면 다음과 같아요.

lecture image

전위 연산자와 후위 연산자

위의 예제는 이항 중위 연산자의 사용자 정의 구현을 보여주고 있어요. 클래스와 구조체는 표준 단항 연산자의 구현도 제공할 수 있습니다. 단항 연산자는 하나의 피연산자에 작용하는데요. 피연산자 앞에 위치하면 전위 연산자(-a와 같은)이고, 피연산자 뒤에 위치하면 후위 연산자(b!와 같은)랍니다.

전위 또는 후위 단항 연산자를 구현하려면 연산자 메서드를 선언할 때 func 키워드 앞에 prefix 또는 postfix 수정자를 작성하면 됩니다.:

extension Vector2D {
    static prefix func - (vector: Vector2D) -> Vector2D {
        // vector의 x와 y 속성에 각각 -를 적용한 새로운 Vector2D 인스턴스를 반환합니다.
        return Vector2D(x: -vector.x, y: -vector.y)
    }
}
swift

위 예제는 Vector2D 인스턴스에 대한 단항 마이너스 연산자(-a)를 구현하고 있어요. 단항 마이너스 연산자는 전위 연산자이므로, 이 메서드는 prefix 수정자로 한정되어야 합니다.

단순한 숫자 값의 경우, 단항 마이너스 연산자는 양수를 음수로, 음수를 양수로 변환해요. Vector2D 인스턴스에 대응하는 구현은 xy 속성 모두에 이 연산을 수행하죠:

let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative는 (-3.0, -4.0)의 값을 가진 Vector2D 인스턴스입니다.
let alsoPositive = -negative
// alsoPositive는 (3.0, 4.0)의 값을 가진 Vector2D 인스턴스입니다.
swift

복합 할당 연산자

복합 할당 연산자 는 할당(=)과 다른 연산을 결합하고 있어요. 예를 들어, 덧셈 할당 연산자(+=)는 덧셈과 할당을 하나의 연산으로 결합하죠. 복합 할당 연산자의 왼쪽 입력 매개변수 타입은 inout으로 표시해야 해요. 왜냐하면 매개변수의 값이 연산자 메서드 내부에서 직접 수정될 것이기 때문이죠.

아래 예제는 Vector2D 인스턴스에 대한 덧셈 할당 연산자 메서드를 구현하고 있어요:

extension Vector2D {
    static func += (left: inout Vector2D, right: Vector2D) {
        // left를 left + right의 결과로 설정합니다.
        left = left + right
    }
}
swift

이전에 덧셈 연산자가 정의되었기 때문에, 여기서 덧셈 과정을 다시 구현할 필요가 없어요. 대신, 덧셈 할당 연산자 메서드는 기존 덧셈 연산자 메서드를 활용하여 왼쪽 값을 왼쪽 값과 오른쪽 값의 합으로 설정하고 있죠:

var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original은 이제 (4.0, 6.0)의 값을 가집니다.
swift

동등 연산자

기본적으로 사용자 정의 클래스와 구조체는 동등 연산자의 구현을 갖고 있지 않아요. 동등 연산자는 같음 연산자(==)와 같지 않음 연산자(!=)로 알려져 있죠. 보통은 == 연산자를 구현하고, Swift 표준 라이브러리의 != 연산자 기본 구현을 사용하여 == 연산자의 결과를 부정합니다. == 연산자를 구현하는 방법은 두 가지예요: 직접 구현하거나, 많은 타입의 경우 Swift에게 구현을 합성해달라고 요청할 수 있죠. 두 경우 모두 Swift 표준 라이브러리의 Equatable 프로토콜을 준수하도록 추가해야 해요.

다른 중위 연산자를 구현하는 것과 같은 방식으로 == 연산자를 구현할 수 있어요:

extension Vector2D: Equatable {
    static func == (left: Vector2D, right: Vector2D) -> Bool {
        // 두 Vector2D 인스턴스의 x 값과 y 값이 각각 같으면 true를 반환합니다.
        return (left.x == right.x) && (left.y == right.y)
    }
}
swift

위 예제는 두 Vector2D 인스턴스가 동등한 값을 가지는지 확인하기 위한 == 연산자를 구현하고 있어요. Vector2D의 맥락에서 "같음"은 "두 인스턴스가 같은 x 값과 y 값을 가짐"을 의미하므로, 연산자 구현에서 이 논리를 사용하고 있죠.

이제 이 연산자를 사용하여 두 Vector2D 인스턴스가 동등한지 확인해볼 수 있어요:

let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
    print("These two vectors are equivalent.")
}
// "These two vectors are equivalent."를 출력합니다.
swift

많은 단순한 경우에는 합성 구현을 사용하여 프로토콜 채택하기에 설명된 대로 Swift에게 동등 연산자의 합성 구현을 제공해달라고 요청할 수 있습니다.

지금까지 연산자 메서드를 통해 기존 연산자를 오버로딩하여 사용자 정의 타입에 맞는 동작을 구현할 수 있다는 걸 배웠습니다. 전위/후위 연산자, 복합 할당 연산자, 동등 연산자 등 다양한 종류의 연산자를 직접 구현해보는 재미있는 경험이 되었으면 좋겠네요!