2014년 6월 14일 토요일

Swift - 메소드(Method)

메소드(Method)란 클래스에 정의된 클래스 멤버 함수를 의미한다. 스위프트(Swift)에서는 클래스 뿐만 아니라 구조체에서도 동일하게 이 메소드를 정의 할 수 있다는 특징이 있다.

아래 예제는 메소드 2가지를 정의한 클래스이다.
class SomeClass {
    func simpleMethod() {
        println("Simple Method")
    }
    
    func sum(a: Int, b: Int) -> Int {
        return a + b
    }
}
선언하는 방식은 함수를 정의하는 것과 동일한 모습이다. 매개변수 선언도, 리턴 타입 선언도 동일한 모양이다. 단지 차이가 있다면 이 함수들은 메소드를 인스턴스화 시켜서만 호출이 가능하다는 점이다.

메소드 호출 시 일반 함수와는 다른 차이점이 있다.
var instance = SomeClass()
instance.simpleMethod()

let result = instance.sum(10, b: 20)
// result = 30
첫 번째 메소드는 매개변수가 없어서 별 다른점이 없어 보이지만, 두 번째 sum 메소드는 호출의 모습은 의도치않게 별명이 붙어있다. 메소드는 두 번째 매개변수부터는 반드시 별명을 붙여서 호출하는 것이 스위프트의 제약사항이다. 왜 그런지 이유는 모르겠지만, 메소드의 매개변수는 두 번 째 부터 자동으로 축약된 별명이 붙어서 선언된다고 생각하자.

별명 숨기기

앞에서 본 메소드 호출에서 별명이 붙는 것이 싫다면 언더라인(_)을 이름으로 붙여주는 방법이 있다.
class TestClass {
    var valueA = 0
    var valueB = 0
    
    func update(valueA: Int, _ valueB: Int) {
        self.valueA = valueA
        self.valueB = valueB
    }
}

let obj = TestClass()
obj.update(500, 600)
update 라는 메소드의 두 번째 매개변수(valueB) 선언에서 별명으로 언더라인을 넣었고 실제로 호출 시 별명을 넣지 않아도 정상적으로 메소드가 호출됨을 알 수 있다.

스위프트에서 언더라인은 여러 곳에서 사용되지만 그 의미는 하나같이 '필요없어' 라는 의미같다. 어쨌거나 메소드 호출 시 매개변수 별명을 붙이기 싫다면 이렇게 언더라인을 이용해 생략시켜 버리자. 물론 이 경우 별명이 필요없다고 선언했으니 호출 시 별명을 붙이면 오히려 오류가 발생한다는 것도 당연하다.

self

클래스나 구조체에서는 필연적으로 self 라는 키워드를 쓸 일이 많다. 이 키워드는 일반 이름과 클래스의 멤버 이름을 구분하기 위한 용도로 사용된다.
class SimpleClass {
    var value = 0
    
    func print() {
        println("Value = \(self.value)")
    }
    
    func setValue(v: Int) {
        self.value = v
        self.print()
    }
}

var simpleInstance = SimpleClass()

simpleInstance.print()
// 콘솔에 Value = 0 이 찍힌다.

simpleInstance.setValue(100)
// 콘솔에 Value = 100이 찍힌다.
위에서는 self 키워드를 이용해 value 프로퍼티와 print() 메소드를 클래스 메소드 내부에서 사용하는 것을 볼 수 있다.

상속과 오버라이드 그리고 super

상속은 클래스만의 고유 기능이다. 상속 시 메소드도 부모의 성질을 그대로 이어받지만 자식 만의 메소드로 바꾸는 것(오버라이드)도 가능하다. 앞서 설명한 SomeClass를 활용해 이번에는 SomeClass를 상속받는 AnotherClass를 정의해 본다.
class AnotherClass: SomeClass {
    override func sum(a: Int, b: Int) -> Int  {
        println("I'm child class of SomeClass")
        return super.sum(a, b: b)
    }
}
AnotherClass는 SomeClass를 상속했다. 그래서 모든 기능을 이어받았다. 하지만 sum() 메소드는 오버라이드(override) 키워드를 정의해서 자식 만의 기능을 덧붙이려고 한다고 선언되어 있다.

super 라는 키워드는 부모를 의미한다. 따라서 super.sum 이라는 구분은 부모의 sum() 메소드를 실행시키는 것을 의미한다. 따라서 AnotherClass의 sum() 메소드도 부모의 sum() 메소드와 기능적으로는 똑같다. 단지 콘솔에 뭔가를 찍는다는 점이 차이가 있다.
var anotherInstance = AnotherClass()
anotherInstance.simpleMethod()

let anotherResult = anotherInstance.sum(10, b: 20)
// 콘솔에 I'm child class of SomeClass가 찍힌다.
// anotherResult = 30
참고로 스위프트는 오버라이드 시 반드시 override 키워드를 붙여야 한다. 귀찮지만 어떻게 보면 클래스 정의만 보고도 오버라이드 된 메소드가 어떤 것인지 파악이 가능하다는 장점도 있긴 하다.

구조체와 클래스의 차이점 - Mutating

스위프트의 구조체(struct)와 클래스(class)는 여러 면에서 닮아있지만 차이점도 존재한다. 가장 큰 차이점은 상속이지만, 그 외에 가장 큰 차이점은 바로 메소드에서 프로퍼티를 수정 할 수 있는지의 여부이다.

아래 예제 코드는 에러가 발생한다.
struct SomeStruct {
    var value = 0
    
    func setValue(v: Int) {
        self.value = v  // ERROR
    }
}
자신의 프로퍼티를 바꾸려고 하는데 에러가 발생한다.

물론 프로퍼티 수정을 아예 불가능하게 해 놓은 것이 아니다. 메소드를 mutating 타입으로 선언하면 프로퍼티 엑세스가 마음껏 가능해진다.
struct SomeStruct {
    var value = 0
    
    mutating func setValue(v: Int) {
        self.value = v
    }
}
이 코드는 에러가 발생하지 않고 의도한 대로 잘 동작한다.

mutating은 struct 입장에서 본다면 프로퍼티를 엑세스하는 메소드를 선언하기 위해 필요하지만, 상수(let) 입장에서 본다면 상수로써의 입장(?)을 지키기 위해서 사용된다.

mutating이 선언된 메소드는 self의 프로퍼티 뿐만 아니라 self 자체를 변경하는 것도 가능해진다. 그다지 선호하지 않는 방법이어서 설명은 생략하겠지만, self를 동일한 타입의 인스턴스를 만들어서 대입해 버리면 다른 인스턴스로 바뀌어 버린다.

물론 클래스의 메소드는 mutating 없이도 멤버 프로퍼티 엑세스가 그냥 자유롭다.

타입 메소드(Type Method)

일반적으로는 정적 메소드(Static Method)라 불리우는 것과 동일한 기능이다. 이 기능은 인스턴스로 만들지 않아도 호출이 가능한 메소드를 의미힌다. 그런데 이 타입 메소드는 타입 프로퍼티와 마찬가지로 구조체와 클래스 간의 차이가 존재한다.

구조체나 열거형(enum)에서는 타입 메소드는 정적 메소드와 동일한 static 키워드를 이용해 선언한다.
struct TestStruct {
    static func description() {
        println("This is TestStruct")
    }
}

TestStruct.description()
// 콘솔에 This is TestStruct 가 찍힌다.
예제에서 볼 수 있듯이 인스턴화(객체를 얻는 과정)가 없이도 메소드 호출이 가능하다.

하지만 클래스는 static 키워드 사용이 제한되어 있다. 다만 프로퍼티와 비슷하게 타입 메소드라는 방식으로 static 대신 class를 명기하면 가능하다.
class TestClass {
    class func description() {
        println("This is TestClass")
    }
}

TestClass.description()
// 콘솔에 This is TestClass 가 찍힌다.
이 타입 메소드는 싱글톤 패턴에서 종종 쓰게 될 것이다.

[관련글] Swift - 함수(Function)
[관련글] Swift - 구조체(Structure) 훑어보기
[관련글] Swift - 클래스(Class) 훑어보기
[관련글] Swift - 프로퍼티(Properties)
[관련글] Swift - 싱글톤 패턴(Singleton Pattern)
[관련글] Swift - let(상수선언)에 대해 파고들기

[돌아가기] 스위프트(Swift) 가이드

댓글 19개 :

익명 :

다른 swift 파일에 있는 클래스 내 메소드는 어떻게 호출해야 하나요?

클래스이름.메소드() 로 하니 에러가 나네요..ㅠㅜ

Renn Seo :

1.
클래스이름.메소드() 로 호출 가능한 메소드는 정적메소드(static method) 일 경우만 가능합니다.
해당 클래스의 메소드 선언 앞에 static 을 붙여주면 됩니다.
수정하기 힘들면 아래 2번으로...

2.
일반적인 메소드는 오브젝트화 시켜서 호출해야 합니다.
예)
let obj = 클래스이름()
obj.메소드()

혹은 아래처럼 한 줄로도 할 수 있을겁니다:
클래스이름().메소드()

3.
에러가 만약 클래스 이름을 못 찾는 경우라면 해당 클래스가 private 혹은 fileprivate 로 선언되어 있을 가능성이 있습니다.
이럴 때는 해당 클래스 소스를 수정해서 public 혹은 open으로 열어주거나
수정할 수가 없다면 포기해야죠.

익명 :

그렇군요 감사합니다!

일단 에러는 안나는 데
let stringURL = NSURL(string: url)
let requestObj = NSURLRequest(URL: stringURL!)

WebViewTest.loadRequest(requestObj)
를 빌드하면 Thread 1 : EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) 에러가 납니다.

fatal error: unexpectedly found nil while unwrapping and Optional value 라고 나오네요....

T^T 뭐가 문제인지...

Renn Seo :

found nil 이라는 표현을 보세요. unwrapping 과정에서 nil값이 발견되어서 죽었다는 의미입니다.
이런 에러는 nil 값을 가지고 있는 옵셔널을 강제로 옵셔널을 벗기는 과정(느낌표)에서 발생합니다.
즉 NSURL 로 생성한 객체가 정상적으로 생성되지 않고 nil값을 가지게 된 것으로 보입니다.
에 아마도 url 값이 잘못되지 않았을까 추측되네요.

관련글은 아래 링크를 참고하세요:
http://seorenn.blogspot.kr/2014/09/swift-optional.html

익명 :

친절한 답변 정말 감사드립니다.

그래서 찍어봤는 데 아래와 같이 나옵니다.

stringURL: Optional(http://www.naver.com)
requestObj: { URL: http://www.naver.com }
fatal error: unexpectedly found nil while unwrapping and Optional value

이러면 url값은 정상적으로 넘어오고 있는 거 아닌가요?

Renn Seo :

NSURL 객체는 제대로 생성되었네요.
그렇다면 WebViewTest 가 nil 이라는 결론이 나오네요.

익명 :

말씀해주신대로 WebViewTest를 보니 nil로 찍히네요.
스토리보드에 웹뷰 끌어와서 만들고
swift파일에 아울렛 UIWebView로 만들어서 연결시켜놓았는 데, 왜 nil값이 찍히는 건지...

익명 :

참고로 UITabBarController 형태로 이니셜뷰에서 URL 값을 스트링 값으로 wvTest 클래스로 넘기고 이 안에 메소드를 통해 웹뷰로 url을 전달하게 해놓았습니다.
동일한 wvTest 클래스 안에 WebViewTest라는 이름으로 UIWebView를 아울렛으로 만들었습니다.

익명 :

Renn Seo님 포스팅 내용보고 WebViewTest?.loadRequest(requestObj)로 바꾸니 일단 에러는 안나는 데 계속 웹뷰는 nil값이고 가져온 url주소로 웹페이지가 열리지 않습니다..ㅠㅜ왜 이럴까요...흑

Renn Seo :

WebViewTest?.loadRequest(...) 에서 WebViewTest 가 nil 이면 아무 일도 하지 않을 뿐입니다. 원천적인 문제는 해결되지 않았지요.

우선 IBOutlet 으로 연결된 인스턴스가 왜 생성되지 않는지부터 보셔야 할텐데 소스 프로젝트가 없는 이상 제가 파악하기에는 힌트가 부족합니다.

익명 :

그럼 project 파일을 보내드릴테니 한번 봐주실 수 있으실까요?

익명 :

바쁘신가요?ㅠㅜ

Renn Seo :

잠깐 개인적인 일이 생겨서 들어와 보질 못 했네요. 문제는 해결 하셨는지요?

익명 :

아니요, 아직 해결 못했습니다.
괜찮으시다면 프로젝트 파일 보내드리고 싶은데 어디로 보내드리면 되는지요?

익명 :

메일주소 알려주시면 거기로 보내드리겠습니다~

Renn Seo :

사정 상 메일 주소를 공개하는 것은 지양하고 있습니다.

github을 이용하시면 저장소에 올리신 후 주소를 알려주시는 방식이 좋을 것 같습니다. 그러면 이슈나 커밋 등으로 도움을 드릴 수 있습니다. (여기 댓글란에 본문과 관계없는 댓글이 계속 쌓이는 것은 피해야 할 것 같습니다)

아니면 dropbox 나 구글 드라이브 등으로 업로드 후 퍼블릭 링크를 알려주셔도 됩니다. :-)

익명 :

그렇군요 확인 감사드립니다.
댓글을 너무 많이 달아서 죄송합니다.
여러차례 여쭤봤는 데도 친철히 답변해주셔서 감사드립니다.

gonzales_eat :

let result = instance.sum(10, b: 20)

버젼이 바뀌는 과정에서 문법이 변경되었는지 현재 xcode 8.3.3(swift 3.1)에선 a의 별명도 붙여야 에러가 나질 않네요.

Renn Seo :

@gonzales_eat 네 맞습니다. Swfit 2 인가 3부터인가 변경되었는데 이 글에는 반영을 안하고 방치해버렸네요... ;; 수정해야겠습니다.