티스토리 뷰

1. Instance Method

Method 는 특정 형식에 속한 함수입니다. 함수와 문법은 동일하지만 단지 구현하는 위치가 다르고 인스턴스를 통해 호출한다는 차이점 밖에 없습니다.

함수는 특정 형식에 연관되지 않은 동작을 구현하고 메소드는 특정 형식에 연관된 동작을 구현합니다. Instance Method 는 클래스, 구조체, 열거형에서 구현할 수 있습니다. 인스턴스 속성처럼 특정 인스턴스와 연관된 동작을 구현합니다. 보통 다른 인스턴스 멤버를 기반으로 구현하기 때문에, 인스턴스마다 실행 결과가 달라집니다. 

메소드 호출 문법은 함수 호출과 한가지 차이점을 가지고 있습니다. 함수는 함수 이름만으로 호출하지만 메소드는 인스턴스 이름을 통해 호출합니다.

// 문법
func name(parameters) -> ReturnType {
    Code
}

// 호출 방법
instance.method(parameters)
class Sample {
    var data = 0
    static var sharedData = 123
    
    func doSomething() {
        print(data)
        // static 멤버를 인스턴스 멤버에서 사용할 수 없음
        // sharedData - 에러
        // 인스턴스 멤버에서 타입 멤버에 접근할 때는 형식 이름을 통해 접근
        Sample.sharedData
    }
    
    func call() {
        doSomething()
    }
}

let a = Sample()
a.data
a.doSomething()
a.call()

class Size {
    var width = 0.0
    var height = 0.0
    
    func enlarge() {
        width += 1.0
        height += 1.0
    }
}

let s = Size()
s.enlarge()

 

위 예제에서 class 로 구현한 Size 객체를 구조체로 변경해 보겠습니다. 이 경우 enlarge() 인스턴스 메소드에서 에러가 발생합니다. 클래스에서는 인스턴스 메소드가 인스턴스 속성을 얼마든지 변경할 수 있지만 구조체에서는 메소드를 mutating 으로 선언해야 변경이 가능합니다. 즉, 값 형식에서 속성을 바꾸는 메소드를 구현할 때는 반드시 mutating 으로 선언해야 합니다.

struct Size {
    var width = 0.0
    var height = 0.0
    
    mutating func enlarge() {
        width += 1.0
        height += 1.0
    }
}

var s = Size()
s.enlarge()

 

2. Type Method

인스턴스가 아닌 형식에 연관된 메소드를 말하며, 클래스, 구조체, 열거형에서 모두 구현 가능합니다.

// 문법
// static 키워드로 선언하면 오버라이딩이 금지됩니다.
static func name(parameters) -> ReturnType {
    statements
}

// 서브 클래스에서 오버라이딩을 허용할 때 사용하는 문법입니다.
// 상속과 연관된 문법으로 클래스에서만 사용이 가능합니다.
class func name(parameters) -> ReturnType {
    statements
}

// 호출 방법
Type.method(parameters)
class Circle {
    static let pi = 3.14
    var radius = 0.0
    
    func getArea() -> Double {
        // 인스턴스 메소드에서 타입 속성에 접근할 때는 타입명을 사용하여 잡근합니다.
        return radius * radius * Circle.pi
    }
    
    struct func printPi() {
        // 타입 메소드에서는 타입 속성에 바로 접근이 가능합니다.
        // 반면, 인스턴스 속성에 접근하는 방법은 없습니다.
        print(pi)
    }
}

Circle.printPi()

 

위에서 선언한 Circle 클래스를 상속 받는 새로운 클래스를 만들고 printPi() 타입 메소드를 오버라이딩 해보겠습니다. 이 경우 Circle 클래스의 정의된 printPi() 타입 메소드는 static 이 아닌 class 키워드를 사용하여야 합니다.

class Circle {
    static let pi = 3.14
    var radius = 0.0
    
    func getArea() -> Double {
        return radius * radius * Circle.pi
    }
    
    class func printPi() {
        print(pi)
    }
}

class StrokeCircle: Circle {
    // static 키워드로 선언된 타입 메소드는 오버라이딩이 불가능합니다.
    // 오버라이딩을 할 경우 class 키워드로 타입 메소드를 선언하여야 합니다.
    override static func printPi() {
        print(pi)
    }
}

 

3. Subscript

let list = ["A", "B", "C"]
list[0]

 

위와 같이 [] 를 통해 값에 접근하는 것을 subscript 라고 합니다. 문법은 다음과 같습니다.

// 문법
instance[index]
instance[key]
instance[range]

 

지금까지는 이미 만들어진 subscript 를 사용하였는데 이번에는 직접 만들어서 사용하는 방법을 알아보겠습니다.

// 문법
subscript(parameters) -> ReturnType {
    get {
        return expression
    }
    set {
        statements
    }
}

 

[] 사이에 전달되는 값은 파라미터를 통해 전달됩니다. 파라미터의 수와 형식에는 제한이 없지만 대부분 2개 이하의 파라미터를 사용합니다. 그리고 가변 파라미터를 사용하는 것도 가능합니다. 하지만 입출력 파라미터로 선언하거나, 파라미터에 기본값을 지정하는 것은 불가능합니다. 그리고 서브스크립트를 통해 값을 받아야하기 때문에 파라미터를 생략하는 것도 불가능합니다.

 

리턴형은 서브스크립트에서 중요한 의미를 가지고 있습니다. 리턴되는 값의 형식인 동시에 저장하는 값의 형식입니다. 문법에서는 저장하는 값의 형식을 별도로 지정하지 않습니다. 대신 리턴형의 자료형을 저장하는 값의 자료형으로 사용합니다. 그래서 메서드와 달리 리턴형을 생략하는 것은 불가능합니다.

 

get block, set block 모두 구현하면 subscript 를 통해 값을 읽고 쓸 수 있습니다. subscript 를 통해 저장한 값은 parameter 를 통해 set block 으로 전달됩니다. 파라미터 이름을 직접 쓰고 싶다면 set 키워드 뒤에서 선언합니다. 파라미터 이름을 생략한다면 newValue 라는 이름의 상수가 자동으로 생성됩니다.

subscript 에서 값을 저장하지 않는다면 set block 을 생략합니다. 이렇게 하면 읽기 전용 subscript 가 됩니다. subscript 는 최소한 값을 읽을 수는 있어야 하기 때문에 get block 을 생략하고 set block 만 쓰는 것은 허용되지 않습니다.

 

간단한 코드를 구현하여 subscript 를 살펴보겠습니다.

class List {
    var data = [1, 2, 3]
    
    // subscript 에서는 파라미터 이름이 argument label 로 사용되지 않습니다.
    // 따라서 argument label 이 필요하다면 직접 선언해야 합니다.
    subscript(index: Int) -> Int {
        get {
            return data[index]
        }
        set {
            data[index] = newValue
        }
    }
}

var l = List()
l[0]        // get block 이 호출됩니다.
l[1] = 123  // set block 이 호출됩니다.

// l[0, 1] - 에러: 위 subscript 에서는 파라미터를 하나만 받기 때문
// l["A"] - 에러: 위 subscript 에서 파라미터 타입은 Int 이기 때문

struct Matrix {
    var data = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ]
    
    // 읽기 전용 subscript
    subscript(row: Int, col: Int) -> Int {
        return data[row][col]
    }
}

let m = Matrix()
m[0, 0]  // 위 subscript 에서는 파라미터를 2개 받고 있기 때문에 이와 같이 호출해 주어야 합니다.

 

4. Dynamic Member Lookup

파이썬과의 호환성을 위해 도입된 문법입니다. 점 문법으로 subscript 에 접근하는 단축 문법을 제공합니다.

@dynamicMemberLookup
struct Person {
    var name: String
    var address: String
    
    // @dynamicMemberLookup 을 지정하면 
    // 반드시 argument label 이 dynamicMember 이고 타입이 String 인 파라미터를 하나 가지는 subscript를 구현해야 합니다. 
    // ReturnType 은 경우에 따라 알맞게 지정합니다.
    subscript(dynamicMember member: String) -> String {
        switch member {
        case "nameKey":
            return name
        case "addressKey":
            return address
        default:
            return "n/a"
        }
    }
}

let p = Person(name: "James", address: "Seoul")
p.name
p.address

// 일반적인 subscript 호출
p[dynamicMember: "nameKey"]
p[dynamicMember: "addressKey"]

// dynamic member lookup 사용
p.nameKey
p.addressKey
p.missingKey

 

Dynamic Member Lookup 은 점 문법을 통해 subscript 에 접근하는 것을 가능하게 하여 코드의 유연성이 높아진다는 장점이 있습니다.

 

하지만 대상에 접근하는 시점이 런타임이기 때문에 이에 따른 단점도 있습니다. 컴파일 타임에는 접근 가능한 subscript 를 판단하지 못합니다. 그래서 자동 완성이 제공되지 않습니다. 오타가 발생한다고 하더라도 컴파일 타임에는 확인할 방법이 없습니다. 이런 특성은 가독성과 유지보수에 문제가 있을 수 있습니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함