반응형

객체 지향 언어에서는 '클래스'를 정의하여 객체를 만들고 사용할 수 있다. 이 "클래스"의 기본적인 사용법에 대해 알아보겠다. 그리고, 클래스의 특성을 더욱 이해하기 위해 "상속", "오버라이드", "Computed 속성", "클래스 속성/클래스 메소드"에 대해서도 설명한다.


클래스(class) 정의

Swift는 객체 지향 언어이다. 이는 물론 객체(object)를 사용할 수 있다는 의미가 된다.

Swift의 객체 지향은 일반적으로 "클래스 기반"라는 것이다. "클래스"는 객체의 설계도에 해당하는 것을 정의 해두고,이를 바탕으로 개체를 만든다.

클래스에서 생성된 객체를 "인스턴스"라고 한다. Swift는 정의된 클래스에서 실제로 작업할 수 있는 인스턴스를 만들어 사용한다는 것이다.

클래스에는 값을 저장하는 변수와 처리를 하는 함수를 넣어 둘 수 있다. 클래스에 제공되는 변수를 "속성(property)", 클래스에서 제공되는 함수를 "메소드(method)"라고 한다.

클래스의 정의

클래스 정의의 가장 간단한 형태는 다음과 같다.

class 클래스명 {
    ...... 속성과 메소드를 작성한다 ......
}

"class 〇〇"라고 작성한 후에 {}를 붙여 그 안에 클래스에 속성과 메소드를 작성하는 형태이다.

아래에 "Helo"클래스의 정의를 보도록 하자.

import Cocoa

class Helo {
    var name = "Taro";
    
    func say(){
        print("Hello, " + name + "!");
    }
}

이 클래스에는 name이라는 속성과 say라는 메소드가 작성되어 있다. 이것들이 Helo 클래스에 제공되는 기능이라는 것이다.

say 메소드 안에 name 속성이 사용되고 있다. 이런 식으로 클래스에 있는 메소드의 내부는 그 클래스에 있는 속성과 메소드를 그대로 사용할 수 있도록 되어 있다.

어시스트 에디터(Assistant Editer)

여기에는 println이라는 것을 사용하고 있는데, 이것은 값을 표준 출력으로 내보내는 함수이다. Xcode의 플레이 그라운드에서 실행하는 경우, "어시스트 에디터(Assistant Editer)"라는 것을 표시하는 것이 편리하다.

Xcode의 [View] 메뉴에서 [Assistant Editor]-[Show Assistant Editor]를 선택한다다. 화면 오른쪽에 새로운 영역이 표시된다. 이것이 "어시스트 에디터"이다. println 등이 실행되면 여기에 "Console Output"라는 항목이 자동으로 표시되고 println한 결과가 나타나게 된다.


클래스 이용

클래스를 실제로 사용하기 위해서는 해당 클래스를 바탕으로 "인스턴스"를 만들고 그 안에 속성과 메소드를 이용하는 방법을 이해하지 않으면 안된다.

인스턴스 생성

클래스에서 인스턴스를 생성하려면 해당 클래스명을 함수처럼 호출할 뿐이다. 예를 들어, "Abc"라고 하는클래스가 있었다면, 다음과 같이 인스턴스를 생성할 수 있다.

var obj:Abc = Abc();

인수에는 일반적으로 아무것도 지정하지 않다 (인스턴스 작성 시에 인수를 지정할 수 있다. 이것은 나중에 설명하겠다). 이런 식으로 클래스명의 함수를 호출하여 인스턴스를 생성할 수 있다.

속성 / 메소드 호출

속성과 메소드의 호출은 인스턴스를 얻은 변수 뒤에 점(.)으로 속성 및 메서드를 호출하도록 작성한다. 예를 들어, 클래스 Abc에 efg라는 속성 xyz라는 메소드가 있었다고 하면 이런 식으로 사용할 수 있다.

var obj:Abc = Abc();
var x = obj.efg; // efg 값을 추출한다
obj.efg = 〇〇; // efg 값을 변경한다
obj.xyz(); // xyz를 호출한다

이전에 Helo 클래스를 사용하는 예제를 보도록 하자.

import Cocoa

class Helo {
    var name = "Taro";
    
    func say(){
        print("Hello, " + name + "!");
    }
}

var obj:Helo = Helo();
obj.say();
 
obj.name = "Hanako";
obj.say();

여기에서는 Helo 인스턴스를 만들어 say 메소드로 메시지를 표시한다. 그 후에 인스턴스의 name 속성을 다시 변경하여 say를 호출한다.

인스턴스를 생성할때, 그 안에 클래스의 내용이 통째로 제공된다. 이는 여러 인스턴스를 만들 때마다 각 인스턴스마다 독립적으로 속성이 저장되는 것을 의미한다. 예를 들어, 이 예제라면 인스턴스마다 name 값은 다르게 설정할 수 있다. 그렇게 되면, 같은 Helo 클래스에서 만든 인스턴스이기에 say로 표시되는 메시지는 인스턴스마다 달라진다.


클래스 상속

클래스를 사용하면 무엇이 유용한 것인가. 관계가 있는 함수나 변수를 한곳에 모아 넣을 수 있다는 것도 있지만, 그 이상으로 큰 것은 "프로그램의 재사용을 쉽게 할 수있다"라는 것이다. 클래스를 복사하면 어디서나 동일한 기능을 사용할 수 있게 된다.

이 "클래스의 재사용"라는 것을 크게 확장하는 것이 "상속"라고 하는 기능이다. 상속은 이미 클래스의 모든 기능을 이어 받아 새로운 클래스를 정의하는 것이다. 즉, 이미 편리한 클래스가 있으면, 그것을 계승하여 자신의 고유한 기능을 덧붙여서 새로운 클래스를 쉽게 만들 수 있다.

상속을 사용한 클래스의 정의는 아래와 같은 식으로 작성한다.

class 클래스명:슈퍼 클래스명 {
    ...... 클래스의 내용 ......
}

상속 근원이 되는 클래스를 "슈퍼 클래스"라고 한다. 또한 그것을 계승하여 만든 클래스를 "서브 클래스"라고 한다. 서브 클래스는 슈퍼 클래스의 기능을 모두 사용할 수 있게 된다.

실제로 상속을 사용해 보자.

import Cocoa

class Helo {
    var name:String = "Taro";
    
    func say(){
        print("Hello, " + name + "!");
    }
}

class Hello:Helo {
    var name2:String = "YAMADA";
    
    func say2(){
        print("Hi," + name + "-" + name2 + "!");
    }
}

var obj:Hello = Hello();
obj.say();

obj.name = "Hanako";
obj.name2 = "TANAKA";
obj.say2();

위에 예제은 Helo 클래스를 상속한 'Hello'클래스를 만들어 사용하고 있다. Hello 클래스의 인스턴스에는 Hello에 추가한 name2 속성과 say2 메소드를 사용할 뿐만 아니라, 슈퍼 클래스의 Helo의 name이나 say도 사용할 수 있다.

Hello 클래스에 추가한 say2 메소드를 보면, 그 속에서 슈퍼 클래스에 있는 name 속성을 사용하는 것을 알았다. 이런 식으로 슈퍼 클래스에 있는 것은 모두 그 클래스에 있는 것으로 간주하고 사용할 수 있다.

슈퍼 클래스의 메소드 호출

아래에 또 다른 상속 샘플을 보도록 하자.

class Friend {
    var name:String
    
    init(name:String) {
        self.name = name
    }
}

class BestFriend : Friend {
    var age:Int
    
    init(name:String, age:Int) {
        self.age = age
        super.init(name:name)
    }
    
}

var you = BestFriend(name: "Taro", age: 30)
print("\(you.name) (\(you.age))")

여기에서는 Friend 클래스를 상속해서 BestFriend 클래스를 만들고 있다. 보면 알 수 있듯이, BestFriend에는 name 속성은 없지만 문제없이 동작한다. 슈퍼 클래스의 Friend에 name이 있기 때문에 BestFriend에도 작동하는 것이다.

BestFriend의 이니셜라이저에는 "super.init(name:name)"라는 문장이 있는데, 이 "super"는 슈퍼 클래스를 나타내는 키워드이다. 이렇게 하여 슈퍼 클래스인 Friend의 이니셜라이저를 호출한다. 슈퍼 클래스의 메소드는 모두 이처럼 'super.〇〇'라는 형태로 호출 할 수 있다.



메소드 오버라이드(override)

상속을 이용하면 이미 있는 클래스에 새로운 기능을 추가할 수 있는 것은 알았다. 하지만 그것만으로는 부족한다. 이미 있는 클래스의 기능을 변경할 수 있어야 한다.

여기에는 '오버라이드(override)'라는 기능을 사용한다. 오버라이드는 슈퍼 클래스에 있는 메소드를 재정의하는 기술이다. 이것은 메소드의 정의를 다음과 같이 작성하면 된다.

override func 메소드명() {...}

처음에 override를 붙이는 것으로, 슈퍼 클래스의 메소드를 새롭게 정의된 메소드로 덧쓰기를 한다. 이렇게 하면, 그 메소드를 호출했을 때의 동작을 변경할 수 있다.

오버라이드를 사용하는 경우, 메소드명뿐만 아니라 인수와 반환 값도 정확하게 슈퍼 클래스의 메소드와 동일해야 한다.

아래에 실제 사용 예제를 보도록 하자.

import Cocoa

class Helo {
    var name:String = "Taro";
    
    func say(){
        print("Hello, " + name + "!");
    }
}

class Hello:Helo {
    var name2:String = "YAMADA";
    
    override func say(){
        print("Hi," + name + "-" + name2 + "!");
    }
}

var obj:Hello = Hello();
obj.say();

obj.name = "Hanako";
obj.name2 = "TANAKA";
obj.say();

이것은 이전에 언급한 예제를 이용하여 say 메서드를 재정의하는 형태로 변경한 것이다. say 메소드를 호출하면 Helo 클래스에있는 say 대신에 Hello에서 재정의되는 say가 호출되는 것을 확인할 수 있다.

오버라이드 상세

상속을 이용하면 슈퍼 클래스의 속성과 메소드를 그대로 사용할 수 있다. 예를 들어 슈퍼 클래스의 기능을 서브 클래스에서 변경하려는 생각도 있을 것이다. 그럴 때 사용되는 것이 '오버라이드'이다.

오버라이드는 슈퍼 클래스에 있는 기능을 서브 클래스에서 덮어 변경하는 것이다. 예를 들어 슈퍼 클래스 'A'라는 메소드가 있었을 때, 그 서브 클래스에서 "A"를 호출하면 슈퍼 클래스에 있는 A라는 메소드가 그대로 실행된다. 상속을 했기 때문이다.

그럼, 서브 클래스에 똑같은 'A'라는 메소드가 있다면 어떻게 될까? 그러면 서브 클래스에서 "A"를 호출하면, 서브 클래스에 추가한 A 메소드가 호출하고, 슈퍼 클래스에 있는 A는 더 이상 호출되지 않을 것이다. 이것이 오버라이드 개념이다.

오버라이드하는 메서드는 메서드의 시작 부분에 'override'라는 수식자를 지정한다. 또한 메소드명, 인수 , 반환 값은 모두 동일해야 한다. 어떤 거라도 다르면, 그것은 "다른 메소드"라고 판단되어서 오버라이드가 되지 않는다.

아래에 또 다른 오버라이드 예제를 보도록 하자.

class Friend {
    var name:String
     
    init(name:String) {
        self.name = name
    }
     
    func printData() {
        println("\(self.name)")
    }
}
 
class BestFriend : Friend {
    var age:Int
     
    init(name:String, age:Int) {
        self.age = age
        super.init(name:name)
    }
     
    override func printData() {
        println("\(self.name) (\(you.age))")
    }
}
 
var you = BestFriend(name: "Taro", age: 30)
you.printData()

여기에서는 printData라는 메소드를 하위 클래스에서 오버라이드 한다.


이니셜라이저

여기까지 인스턴스 생성은 모든 Helo();와 같이 인수없이 만들었다. 이 인스턴스를 만들 때 필요한 값 등을 인수로 전달할 수 있다면 매우 편리할 것이다.

이런 때에 이용되는 것이 "이니셜라이저(initialize)"이다. 이것은 인스턴스를 만들 때 자동으로 호출되는 초기화 처리 전용의 메소드이다. 이 이니셜라이저는 다음과 같이 정의한다.

init (인수) {
    ...... 초기화 처리 ......
}

func init로 쓸 필요는 없다. 단, init만 작성하면 된다. 이니셜라이저는 특별한 방법이므로 func 정의할 필요가 없는 거다.

이 init의 ()에 인수를 선언하면 인스턴스 생성시에 값을 전달할 수 있게된다. 아래에 간단한 예제를 보도록 하자.

import Cocoa

class Helo {
    var name:String;
    
    init(name:String){
        self.name = name;
    }
    
    func say(){
        print("Hello, " + name + "!");
    }
}

var obj:Helo = Helo(name:"Taro");
obj.say();

여기에서는 Helo 클래스에서 init(#name:String) 형태로 이니셜라이저가 작성되어 있다. 이렇게 하면 인스턴스 생성시에는 Helo(name:"Taro");와 같이 인수를 지정하여 쓰면 된다.

또한, 이 이니셜라이저에서 인수로 전달된 변수 name을 Helo 클래스의 name 속성에 할당하기 위해, "self.name"라고 작성을 하고 있다. 이 "self"는 인스턴스 자신을 나타내는 특별한 값이다. 이와 같이 작성하는 것으로, "이 인스턴스 자신의 name 속성"을 지정하고 있다. 클래스 정의에서 자주 사용하는 것이므로 모두 기억하도록 하자.



Computed 속성

상속 외에도 이것은 기억 두지 않으면 안되는 기능이 몇 가지 있다. 그것에 대해서도 설명하겠다. 먼저 "Computed 속성"을 설명하겠다.

속성이라는 것은 클래스에 값을 보관할 변수이다. 이것은 그대로 값을 교체할 수 있기 때문에, 어떤 값이 될지 모른다. 여기서 값의 입출력을 프로그래밍으로 제어할 수 있도록 한 것이 Computed 속성이다.

Computed 속성은 다음과 같이 작성한다.

var 속성 : 유형 {
    get {
        ...... 처리 ......
        return 값
    }
    set {
        ...... 처리 ......
    }
}

속성의 선언 뒤에 {}가 있고, 그 안에 get 및 set이라는 것이 작성하고, 여기에서 값을 읽고 쓰기 위한 처리를 작성한다. get만 작성하고 set를 작성하지 않으면, 값을 얻기만 가능하고 수정할 수 없는 속성이 된다. 반대로 set만 작성하면, 값 변경만 가능하고 얻을 수 없는 속성이 된다.

아래 Computed 속성의 사용 예제를 보도록 하자.

class Friend {
    var name:String
    
    var old:Int
    
    var age:Int {
        get {
            return old
        }
        set {
            if newValue > 0 {
                old = newValue
            }
        }
    }
    
    init(name:String, age:Int) {
        self.name = name
        self.old = age
    }
    
    func printData() {
        print("\(self.name) (\(age))")
    }
}

var you = Friend(name: "Taro", age: 30)
you.printData()

여기에서는 age라는 Computed 속성을 사용하고 있다. 실제 값은 old라는 속성에 보관하고 있다. Computed 속성은 이 처럼 실제 값을 보관해 두기 위한 장소를 따로 마련할 필요가 있는 것이다.



유형 속성 및 유형 메소드

일반적 속성과 메서드는 인스턴스를 생성해야만 사용할 수 있다만, 클래스에서 바로 직접 이용할 수 있는 속성과 메소드도 제공할 수 있다. 이는 "유형 속성(type property)", "유형 메소드(type method)"라고 한다.

유형 속성과 유형 메소드를 선언할 때는 앞에 "class"라는 수식자를 붙여 클래스에 배치된다. 그러면 클래스에서 직접 호출될 수 있다.

유형 속성 및 유형 메소드를 사용할 때 몇 가지 주의해야 할 점이 있다.

  • 유형 속성으로 만들 수 있는 것은 "Computed 속성"이다. 보통의 변수에 할당하는 속성은 제공되지 않는다. 또한 클래스에 저장할 변수를 제공되지 않기 때문 때문에, Computed 속성의 set도 거의 사용할 수 없다고 생각해도 좋을 것이다.

  • 유형 메소드 내에서 이용할 수 있는 것은 유형 메소드 및 유형 속성뿐이다. 일반 (인스턴스에서 사용하는) 속성과 메소드는 사용할 수 없다.

아래에 실제 사용 예를 보도록 하자.

class Exchange {
    class var rate:Double {
        return 1005.0
    }
    
    class func DollarToWon(d:Double)->Int {
        return Int(d * rate)
    }
    
    class func WonToDollar(y:Int)->Double {
        return Double((y * 100) / Int(rate)) / 100
    }
}

print(Exchange.DollarToWon(1.5))
print(Exchange.WonToDollar(1500))

여기에서는 원화에서 달러로, 달러에서 원화로 환산하면 Exchange 클래스를 준비했다. rate 유형 속성과 계산을 할 두 가지 유형 메소드를 제공하고 있다. 이러한 계산을 중심으로한 클래스는 일일이 인스턴스를 만들 필요가 없기 때문에, 클래스 메소드로 제공하는 편이 효율적으로 사용할 수 있는 것이다.

반응형

'Swift' 카테고리의 다른 글

[Switf] 구조체, 열거형, 튜플  (0) 2017.12.09
[Swift] 배열과 사전  (0) 2017.12.09
[Swift] 클래스 기본  (0) 2017.12.09
[Swift] 함수  (0) 2017.12.09
[Swift] 제어 구문  (0) 2017.12.09
[Swift] 값, 변수, 연산  (0) 2017.12.09

+ Recent posts