티스토리 뷰

Swift

Swift ) Method

Zedd0202 2017. 10. 17. 16:37
반응형

프로토콜...글 쓰다가..  급하게 Method(메소드) 글을 쓰게 됐네요 :).....

중간에 mutating내용이 나오는데, 그 내용이 Method챕터에 잘 설명이 되어있거든요 :)

역시나 <The Swift Programming Language (Swift 4) - Methods >의 내용을 알아볼거에요 :)

시작할게요!



Method 



메소드는 특정 타입과 연관된 함수입니다. 

클래스, 구조체 및 열거형은 특정 작업이나 기능을 캡슐화한 인스턴스 메소드와 타입 자체와 관련된 타입메소드를 정의 할 수 있습니다.

타입 메소드는 Objective-C의 클래스 메소드와 비슷합니다.


Swift에서 구조체와 열거형을 정의할 수 있다는 사실은 C와  Objective-C와의 주요한 차이점입니다.

Objective-C에서 클래스는 메소드를 정의할 수 있는 유일한 타입입니다. 

Swift에서는 클래스, 구조체 또는 열거형을 정의하는 여부를 선택할 수 있으며, 사용자가 만든 타입에 대한 메소드를 유연하게 정의 할 수 있습니다. 


Instance Methods

인스턴스 메소드드는 특정 클래스, 구조체 또는 열거형의 인스턴스에 속하는 함수입니다. 
인스턴스 프로퍼티의 접근 및 수정 방법을 제공하거나 인스턴스 목적(purpose) 과 관련된 기능을 제공하여 해당 인스턴스의 기능을 지원합니다.
인스턴스 메소드는 함수(function)에서 설명한대로 함수(function)와 완전히 동일한 구문을 사용합니다.

인스턴스 메소드가 속한 타입의 여는 중괄호({)와 닫는 중괄호(})안에 인스턴스 메소드를 작성합니다. 
인스턴스 메소드는, 다른 모든 인스턴스 메소드 및 해당 타입의 특성에 암시적으로(implicit) 접근합니다. 
인스턴스 메소드는 자신이 속한 타입의 특정 인스턴스에서만 호출 될 수 있습니다.
기존의 인스턴스가 없으면, 호출 할 수 없습니다. 


인스턴스 메소드의 예제를 한번 봅시다. 

class Counter {

    var count = 0

    func increment() {

        count += 1

    }

    func increment(by amount: Int) {

        count += amount

    }

    func reset() {

        count = 0

    }

}

Counter라는 클래스를 정의했네요 :)

Counter클래스는 3가지 인스턴스 메소드를 정의하고 있습니다.


increment()는 카운터를 1씩 증가시킵니다. 

increment(By: Int)는 지정된 정수만큼 카운터를 증가시킵니다.

reset()은 카운터를 0으로 재설정합니다.


Counter클래스는 현재 카운터 값을 추적하기 위해 count라는 변수 저장 프로퍼티를 선언한 것을 볼 수 있습니다. 


프로퍼티와 마찬가지로 .(점)을 통해 인스턴스 메소드를 호출 할 수 있습니다. 


let counter = Counter()// count = 0 (기본값)

counter.increment()//count = 1

counter.increment(by: 5)//count = 6

counter.reset()//count = 0

간단하죠?

프로퍼티랑 비슷해요 :)



The self Property


타입의 모든 인스턴스에서는 "self"라는 암시적(implicit) 프로퍼티를 가집니다.  이는 인스턴스 자신과 정확하게 동일합니다.

self프로퍼티를 사용하여 자체 인스턴스 메소드 내에서 현재 인스턴스를 참조할 수 있습니다.

위의 에제에서 increment()메소드는 다음과 같이 작성할 수 있습니다. 

func increment() {

self.count += 1

}

실제로는 코드에 "self"를 자주 쓰지 않아도 됩니다.

명시적으로(explicitly) self를 작성하지 않으면, Swift는 메소드 내에서 알려진 프로퍼티 또는 메소드 이름을 사용할 때마다, 현재 인스턴스 프로퍼티 또는 메소드를 참조한다고 가정합니다.

이 가정은 Counter클래스의 세가지 메소드 내에서 count(변수 저장 프로퍼티였던.)를 사용하는 것을 보면  증명이 됩니다. 


이 규칙에 대한 주된 예외는 인스턴스 메소드의 매개변수 이름이 해당 인스턴스 프로퍼티와 동일한 이름을 가질 때 발생합니다.

이 경우 매개변수 이름이 우선 적용되며, 더 적합한 방법으로 프로퍼티를 참조해야합니다. self프로퍼티를 사용하여 매개변수 이름과 프로퍼티 이름을 구별합니다.


다음 예제에서 self는 x라는 메소드 매개변수와 x라고도 하는 인스턴스 프로퍼티 사이를 모호하게 합니다. 


정리하면, 인스턴스 메소드 내에서 해당 타입이 가진 프로퍼티나 메소드를 참조할 수 있다는 것입니다.

하지만, 다음과 같이 매개변수 이름과 저장 인스턴스 프로퍼티 이름이 같으면, Swift는 매개변수 이름을 우선 적용한기 때문에 "self"를 꼭 붙혀서

이게 해당 타입의 저장 인스턴스 프로퍼티인지 구별해줘야한다는 것이죠.


struct Point {

    var x = 0.0, y = 0.0

    func isToTheRightOf(x: Double) -> Bool {

        return self.x > x

    }

}

위 Point라는 구조체는 isToTheRightOf라는 인스턴스 메소드를 하나 가지고 있네요.

근데 파라미터 이름으로 x를 받습니다. 

하지만 Point의 저장 인스턴스 프로퍼티로 x가 또 있죠?

바로 이럴 때 "self"를 붙혀서 구별을 해줘야한다~~이 말입니다 :)


Modifying Value Types from Within Instance Methods


이 부분은 주의깊게 봐주세요 :)



구조체와 열거형은 값타입(Value Type)입니다. <Swift 기초 문법>에서 배웠었죠?

기본적으로 Value Type의 프로퍼티들은 해당 인스턴스 메소드 내에서 수정할 수 없습니다.

그러나 특정 메소드 내에서 구조체 또는 열거형의 프로퍼티를 수정해야 하는 경우, 해당 메소드의 동작을 변경(mutating)하도록 선택 할 수 있습니다. 

그런 다음 메소드는 메소드 내에서 프로퍼티를 변경할 수 있으며 메소드가 끝나면 변경된 내용이 원래 구조체에 다시 기록됩니다.

메소드는 암시적(implicit) 자체 프로퍼티에 완전히 새로운 인스턴스를 할당 할 수도 있습니다. 이 새 인스턴스는 메소드가 종료되면 기존 인스턴스를 바꿉니다.


func키워드 앞에 "mutating"키워드를 두면 위 동작을 수행할 수 있습니다. 


struct Point {

    var x = 0.0, y = 0.0

    func moveBy(x deltaX: Double, y deltaY: Double) {

        x += deltaX

        y += deltaY

    }

}

var somePoint = Point(x: 1.0, y: 1.0)

somePoint.moveBy(x: 2.0, y: 3.0)

위 코드는 맞는 코드일까요~~ 틀린코드일까요~~


네!! 틀린 코드에요 :)
방금 위에서 그랬죠? 
"기본적으로 Value Type의 프로퍼티들은 해당 인스턴스 메소드 내에서 수정할 수 없습니다."

메소드 안에서 프로퍼티들을 "수정"하고 있으니 위 코드는 에러를 내게 됩니다.
하지만!!! 이것을 가능하게 해주는 것이 바로 "mutating"키워드 라고 그랬죠? 


struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        x += deltaX

        y += deltaY

    }

}

var somePoint = Point(x: 1.0, y: 1.0)

somePoint.moveBy(x: 2.0, y: 3.0)

네!! 이러면!! 이제 프로퍼티들의 값 변경이 가능해진다~₩ 이거죠. 

위 Point라는 구조체는 인스턴스를 일정량 만큼 이동시키는 mutating moveBy(x: y:)메소드를 정의해요.

새 point를 반환하는 대신, 이 메소드는 실제로 이 point를 호출 한 point를 수정합니다. mutating 키워드는 해당 프로퍼티를 수정할 수 있도록 정의부분에 추가됩니다.


그리고, < Stored Property(저장 프로퍼티) >글에서 설명했었는데, 지금은 var로 somePoint라는 이름의 Point인스턴스를 만들었죠?

하지만 let으로 만들게 되면 해당 프로퍼티들이 let으로 선언된 것과 동일한 효과를 가져와 값을 변경하지 못하게 된다고 그랬죠?

mutating과 상관없이 말이에요.


Assigning to self Within a Mutating Method


mutating메소드는 암시적(implicit) self 프로퍼티에 완전히 새로운 인스턴스를 할당 할 수 있습니다. 

위의 Point예제는 다음과 같은 방법으로 작성할 수 있습니다. 

struct Point {

    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {

        self = Point(x: x + deltaX, y: y + deltaY)

    }

}

var somePoint = Point(x: 1.0, y: 1.0)

somePoint.moveBy(x: 2.0, y: 3.0)

오....뭔가 저는 처음보는 방식의..그런 할당?법이에요!!!신기하네요:)

이 버전의 mutating moveBy(x:,y:)메소드는 x및 y값이 대상 위치로 설정된 새로운 구조체를 만듭니다.

이 버전의 메소드를 호출하면, 최종결과는 이전 버전(위에서 했던거)을 호출 할 때와 완전히 동일합니다.


자. 위 새로운 버전의 moveBy(x:,y:)메소드를 설명드리면, "self"가 Point자체를 의미하죠?

이 self에 Point타입을 할당해주는 것은 당연히 문제없겠죠?!?

아예 새로운 Point인스턴스가 되는거네요.



자, 다음으로 열거형을 봐봅시다. 

열거형을 변경하는 방법은 암시적(implicit) self 파라미터로 동일한 열거형의 다른 케이스로 설정할 수 있습니다. 

이게 무슨 소리인지 1도 모르겠네요 :) 예제를 통해 같이 봐봅시다. 

enum TriStateSwitch {

    case off, low, high

    mutating func next() {

        switch self {

        case .off:

            self = .low

        case .low:

            self = .high

        case .high:

            self = .off

        }

    }

}

var ovenLight = TriStateSwitch.low

ovenLight.next() // ovenLight == .high

ovenLight.next() // ovenLight == .off

역시나 열거형도 Value Type이기 때문에, next()라는 인스턴스 메소드 내에서 프로퍼티를 바꿀 수 없습니다.

하지만, "mutating" 키워드는 그것을 가능하게 하죠. 


ovenLight로 next()메소드를 호출할 때마다 세가지의 다른 전원상태(off, low, high)사이를 순환하게 합니다.






Type Methods


앞에 타입이란 말만붙으면..이제..왠지...static이 나올 것 같은..그런 느낌...이 들지 않으세요?!

맞습니다. 이 타입 메소드를 읽기전에, < Type Property(타입 프로퍼티) > 글을 읽고오시면 정말로!!!!이해가 훨씬 잘되실거에요 :)


위에서 설명한대로, 인스턴스 메소드는 특정 타입의 인스턴스에 호출되는 메소드입니다.
또한, 타입 자체에서 호출되는 메소드를 정의할 수 있습니다. 
이러한 종류의 메소드를 타입 메소드(Type Method)라고 합니다. 
메소드의 func키워드 앞에 "static"키워드를 작성하여 타입 메소드를 나타냅니다.
클래스는 "class"키워드를 사용하여 하위 클래스가 슈퍼클래스의 해당 메소드 구현을 재정의(override)할 수 있도록 허용할 수 있습니다.


Objective-C에서는 Objective-C클래스에 대해서만 타입-레벨(type-level)메소드를 정의할 수 있습니다.
Swift에서는 모든 클래스, 구조 및 열거형에 대해 타입-레벨(type-level)메소드를 정의 할 수 있습니다. 각 타입 메소드는 명시적으로 지원하는 타입으로 범위가 지정됩니다. 

타입 메소드는 인스턴스 메소드처럼 .(점)구문으로 호출됩니다.
그러나 인스턴스로 메소드를 호출하는 것이 아니라, 타입으로 호출합니다. 

class SomeClass {

    static func otherTypeMethod(){

        //code

    }

    class func someTypeMethod() {

        // code

    }

}

SomeClass.someTypeMethod()

SomeClass.otherTypeMethod()


자 SomeClass라는 클래스는 일단 2개의 메소드가 있네요. 근데 func앞에 "static"과 "class"키워드가 붙은 걸 보니 "타입 메소드"처럼 보이네요.


이 타입 메소드들을 어떻게 호출했는지 볼까요?

SomeClass의 인스턴스를 생성하지 않고도, 바로 "타입"을 통해 메소드를 호출한 것. 보이시죠?


"class"라는 키워드가 붙으면 해당 클래스를 상속받는 자식클래스가 해당 타입 메소드를 재정의할 수 있다고 그랬죠? 그러니까 "class"키워드가 붙은 someTypeMethod를 재정의 할 수 있게 됩니다. 


class Zedd:SomeClass{

    override static func someTypeMethod(){

        // code

    }

}

이렇게요 :)

앞에 override static을 붙혀주시는 것도 잊지 마세요!!!


타입 메소드의 본문 내에서 암시적인(implicit) self 프로퍼티는 해당 타입의 인스턴스가 아니라, 타입 "자체"를 참조합니다.

즉, 인스턴스 프로퍼티 및 인스턴스 메소드 매개변수와 마찬가지로 self를 사용하여 타입 프로퍼티와 타입 메소드 매개변수 사이의 모호성을 제거 할 수 있습니다.


보다 일반적으로, 타입 메소드의 본문 내에서 사용되는 규정되지않는 메소드 및 프로퍼티 이름은 다른 타입-수준(type-level)메소드 및 프로퍼티를 참조합니다.

타입 메소드는 타입 이름앞에 접두어를 붙일 필요 없이 다른 메소드의 이름으로 다른 타입 메소드를 호출 할 수 있습니다.

비슷하게, 구조체와 열거형의 타입메소드는 타입 이름 접두사 없이 프로퍼티 이름을 사용하여 타입 프로퍼티에 접근할 수 있습니다.


이제부터 긴~~예제를 하나 볼거에요.

먼저 LevelTracker라는 이름을 가진 구조체가 하나 나올거에요.

자. 게임하나가 있어요!! 이 LevelTracker 구조체는 게임의 단계를 통해 플레이어의 진행 상황을 추적할 수 있는 구조체에요.

싱글 플레이어 게임이지만, 여러 플레이어에 대한 정보를 하나의 디바이스에 저장 할 수 있습니다. 


게임레벨(레벨 1과 별도)은 게임이 처음 실행될 때 잠겨집니다.

플레이어가 레벨을 완료할 때마다 해당 레벨이 디바이스의 모든 플레이어에 대해 잠금이 해제됩니다.

LevelTracker구조체는 타입 프로퍼티와 메소드를 사용하여 잠금해제 된 게임 레벨을 추적합니다.

또한 개별 플레이어의 현재 레벨을 추적합니다.



struct LevelTracker {

    static var highestUnlockedLevel = 1

    var currentLevel = 1

    static func unlock(_ level: Int) {

        if level > highestUnlockedLevel


highestUnlockedLevel = level

        }

    }


    static func isUnlocked(_ level: Int) -> Bool {

        return level <= highestUnlockedLevel

    }

    @discardableResult

    mutating func advance(to level: Int) -> Bool {

        if LevelTracker.isUnlocked(level) { //타입으로 바로 접근해서 참조 => isUnlocked 타입메소드니까!

            currentLevel = level

            return true

        } else {

            return false

        }

    }

}

LevelTracker구조체는 모든 플레이가 잠금해제한 최고 레벨을 추적합니다.(위에서 highestUnlockedLevel임.) 


LevelTracker는 highestUnlockedLevel프로퍼티를 사용하여 작업할 두가지 타입 메소드도 정의했네요. 앞에 "static"이라는 키워드가 붙은 걸로 알 수 있죠?


첫번째는 unlock(_ :)이라는 타입 메소드네요.

새로운 레벨이 잠금 해제 될 때 마다 highestUnlockedLevel의 값을 업데이트 합니다.


두번째는 isUnlocked(_ :)이라는 편리한 타입 메소드로 특정 레벨 번호가 이미 잠금 해제되어 있으면, true를 반환합니다. 


이러한 타입메소드(위 두가지 타입 메소드)는 highestUnlockedLevel타입 프로퍼티에 접근 할 수 있으므로 LevelTracker.highestUnlockedLevel로 작성할 필요가 없습니다. 



LevelTracker는 타입 프로퍼티 및 타입 메소드 외에도 개별 플레이어의 게임 진행상황을 추적합니다.

currentLevel 프로퍼티를 관리할 수 있도록, advance(to :)라는 인스턴스 메소드를 정의합니다. 

("static"이나 "class"라는 키워드가 안붙은걸 보니 그냥 인스턴스 메소드인 것을 알 수 있죠?)


currentLevel을 업데이트 하기 전에 이 메소드는 요청된 새 레벨이 이미 잠금해제 되어있는지 확인합니다. advance(to :) 메소드는 실제로 currentLevel을 설정할 수 있는지 여부를 나타내는 Bool값을 반환합니다.

advance(to :) 메소드에서 반환값을 무시하는 코드는 반드시 실수(mistake)가 없기 때문에, 이 함수는 @discardableResult특성으로 표시되어 있습니다. 


엥..@discardableResult가 도대체 뭐죠...찾아보니


결과를 사용하지 않고, 값을 리턴하는 함수 또는 메소드가 호출될때 컴파일러 경고를 표시 하지않으려면

이 속성을 함수 또는 메소드 선언에 적용하십시오.


아!!!!!!!!!!!!!!!!!!!!!!!이거군요

아 드디어 그 경고를 없앨 수 있겠군요.........



이거....ㅎㅎ

이 경고를 없애고싶다면, 

이렇게 해주면 된다는 것이죠. 와 이런게 있었다니..!!!!!!!!!짱이네요.

깐지나보이네요. 앞으로 자주 써야겠어요!!!!


아 아무튼..저희 LevelTracker에 대해서 이야기 하고 있었죠?..ㅎㅎ

LevelTracker구조체는 아래에 나올 Player클래스와 함께 사용되어 개별 플레이어의 진행상황을 추적하고 업데이트 합니다. 


class Player {

    var tracker = LevelTracker()

    let playerName: String

    func complete(level: Int) {

        LevelTracker.unlock(level + 1)

        tracker.advance(to: level + 1)

    }

    init(name: String) {

        playerName = name

    }

}

자. Player라는 클래스를 정의했어요 :)

이 안에서, tracker는 위에서 선언한 LevelTracker의 인스턴스네요. 

그리고 playerName은 기본값이 없으니 init을 만들어 준 것을 볼 수 있습니다. 


그리고 Player클래스에는 complete라는 인스턴스 메소드가 있네요 :) 

unlock은 타입메소드였으니 LevelTracker타입 자체로 참조하는 것을 볼 수 있네요. 반면에 advance는 LevelTracker의 인스턴스 메소드였으니 LevelTracker의 인스턴스인 tracker로 접근해야하죠.


위 클래스에 대한 애플의 설명을 더 봅시다. 

Player클래스는 해당 플레이어의 진행상황을 추적하기 위해 LevelTracker의 새 인스턴스를 만듭니다.

또한 플레이어가 특정 레벨을 완료할 때 마다 호출되는 complete (level :) 메소드를 제공합니다.

이 메소드는 모든 플레어의 다음 레벨을 잠금 해제하고 플레이어의 진행상황을 업데이트 하여 다음 레벨로 이동시킵니다.

이전 줄의 LevelTracker.unlock (_ :)을 호출하여 레벨이 잠금 해제 된 것으로 알려져 있으므로 advance (to :) 반환값은 무시됩니다.


새 플레이어에 대한 Player클래스의 인스턴스를 만들고 플레이어가 1단계를 완료하면 어떻게 되는지 확인하십시오.



var player = Player(name: "Argyrios")

player.complete(level: 1)

print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")

// Prints "highest unlocked level is now 2"

Argyrios라는 이름을 가진 플레이가 1레벨을 완료했네요. 

그럼 이제 2레벨이 열려야겠죠? highestUnlockedLevel이 2가 되게 됩니다. 


두번째 플레이어를 만들어볼까요? 



player = Player(name: "Beto")

if player.tracker.advance(to: 6) {

    print("player is now on level 6")

} else {

    print("level 6 has not yet been unlocked")

}

// Prints "level 6 has not yet been unlocked"


Beto라는 이름을 가진 플레이가 생성됐습니다. 하지만 현재 highestUnlockedLevel는 1이기 때문에 Beto는 6레벨로 갈 수 없습니다. 6레벨은 잠금해제가 안됐거든요. 



위 예제는 메소드를 총정리 하는 느낌?ㅎㅎ.. 

메소드 챕터가 끝났습니다~~~~~정리하면서 정말 많이 배우는 것 같아요 :)

메소드에 대해 궁금한 점이나 이해가 안되는점, 지적할 점이 있으시다면 댓글이나 PC화면 오른쪽 하단에 있는 채널 서비스를 이용해주세요 :) 

이 글이 메소드를 이해하는데 도움이 되었으면 좋겠어요 XD..

나름대로 가독성을 최대로 할려고 노력하는데 ㅠㅠ 이런 긴 글 일수록 잘 안되는 것 같아요...흐앙 의견 있으시면 주세요 ㅜㅠ

프로토콜 글에서 만나요!! 


반응형

'Swift' 카테고리의 다른 글

Swift ) Type Casting  (2) 2017.10.23
Swift ) Protocols (2)  (3) 2017.10.18
Swift ) Protocols (1)  (4) 2017.10.16
Swift ) Properties - Type Properties  (4) 2017.10.13
Swift ) Properties - Property Observers(프로퍼티 옵저버)  (1) 2017.10.09