본문 바로가기

전공/swift

swift(6) - Object 와 Class, Tuple

1. 객체와 클래스 (Objects and Classes)
(1) 클래스 생성 및 설정
class 키워드 뒤에 클래스로 설정할 이름을 적어서 클래스를 생성한다.
클래스 내부에 선언된 변수를 클래스 프로퍼티(property)라고 한다. c++의 멤버 변수와 유사한 개념이다.
메서드 또한 같은 위치에 정의해주면 된다. 

class Shape {
    var numberOfSides = 0
    func simpleDescription()-> String {
        return "이 도형은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

 
클래스를 기반으로 생성된 객체를 인스턴스(instance)라고 한다. 
클래스 이름 뒤에 소괄호를 붙여서 인스턴스를 생성할 수 있다. 
인스턴스의 프로퍼티에 접근하거나 메서드를 사용할 때엔 인스턴스 이름 뒤에 점(.)을 붙인다.

let a = Shape() // 인스턴스 a 생성
print(a.simpleDescription()) // 메서드 사용, "이 도형은 0개의 변을 가지고 있습니다." 출력
print(a.numberOfSides) // 프로퍼티 접근, "0" 출력

 
인스턴스가 생성될 때 클래스의 프로퍼티를 구체적으로 설정할 수 있다. 이때 초기화 구문(initializer)을 사용한다.
c++ 의 생성자와 유사한 개념이라 볼 수 있다. init 키워드를 쓴다. 

class Shape {
    var numberOfSides = 0
    var name: String
    
    init(name: String){
        self.name = name
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

let a = Shape(name: "a") // 인스턴스 a 생성, name은 a
print(a.simpleDescription()) // 메서드 사용, "도형 a은 0개의 변을 가지고 있습니다." 출력

 
init 블럭에서 name 인수(괄호 안에서 받는 값)와 name 프로퍼티(인스턴스의 설정 값) 을 구분하기 위해 self를 사용하였다.
인수 이름과 프로퍼티 이름이 다르면 사용하지 않아도 괜찮다. 

class Shape {
    var numberOfSides = 0
    var name: String
    
    init(xx: String){
        name = xx
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

let a = Shape(xx: "a") // 인스턴스 a 생성, name은 a

 
이런 식으로 코드를 작성해줘도 작동한단 소리다. 즉, self 는 c++의 this 와 비슷한 역할을 해준다고 생각하면 된다. 
모든 프로퍼티는 값이 할당되어 있어야 한다 !!
nameOfSides 처럼 선언 시에 값을 할당해주거나,
name과 같이 init, 즉 초기화 구문에서 값을 할당해주면 된다. 
 
(2) 클래스 간 상속
슈퍼클래스(superclass)는 다른 클래스가 상속할 수 있는 클래스로, 상위 클래스와 같은 의미이다. (더 높은 위치)
서브클래스(subclass)는 다른 클래스를 상속받는 클래스로, 하위 클래스와 같은 의미이다. (더 낮은 위치)
즉, 서브 클래스는 슈퍼 클래스를 상속받는다. 
 
서브 클래스는 클래스 이름 뒤에 콜론으로 구분하여 슈퍼 클래스를 포함할 수 있다.
override 키워드를 사용해서 슈퍼 클래스의 메서드를 재정의한 것에 주목하자. 
override 키워드 없이 메서드를 재정의 하면 컴파일 에러가 뜬다. 

class Shape{
    var numberOfSides = 0
    var name: String
    
    init(name: String){
        self.name = name
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

class Square: Shape {
    var sideLength: Double
    
    init(sideLength: Double, name: String){
        self.sideLength = sideLength // Square 클래스의 프로퍼티 설정
        super.init(name: name) // 슈퍼 클래스의 초기화 구문을 부른다
        numberOfSides = 4
    }
    
    func area() -> Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "정사각형 \(name) 의 한 변의 길이는 \(sideLength) 입니다."
    }
}

let a = Shape(name: "a") // 인스턴스 a 생성, shape 타입
let b = Square(sideLength: 3, name: "b") // 인스턴스 b 생성, square 타입
print(b.simpleDescription()) // 서브 클래스에서 재정의 된 메서드가 작동한다
print(b.numberOfSides) // 프로퍼티 접근, 4 출력
print(b.area()) // 서브 클래스의 메서드 작동, 9.0 출력

 
서브 클래스에서 재정의 되지 않고, 슈퍼 클래스에서 존재하는 메서드가 있을 때
서브 클래스 인스턴스에서 해당 메서드를 호출하면 슈퍼 클래스에서 정의된 기능을 수행한다. 

class Shape{
    var numberOfSides = 0
    var name: String
    
    init(name: String){
        self.name = name
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

class Square: Shape {
    var sideLength: Double
    
    init(sideLength: Double, name: String){
        self.sideLength = sideLength // Square 클래스의 프로퍼티 설정
        super.init(name: name) // 슈퍼 클래스의 초기화 구문을 부른다
        numberOfSides = 4
    }
    
    func area() -> Double {
        return sideLength * sideLength
    }
}

let b = Square(sideLength: 3, name: "b") // 인스턴스 b 생성, square 타입
print(b.simpleDescription()) // 서브 클래스지만 메서드가 슈퍼 클래스에만 존재한다. 이땐 슈퍼 클래스의 메서드를 출력한다.
// "도형 b은 4개의 변을 가지고 있습니다." 출력

 
서브클래스의 초기화 구문에서 슈퍼클래스의 프로퍼티를 설정하기 위해서는 슈퍼클래스의 초기화 구문을 호출해야 한다. 
super.init() 키워드를 통해 호출할 수 있다. 
아래 예시는 슈퍼클래스의 초기화 구문을 부르지 않아서 오류가 발생한 경우다.

class Shape{
    var numberOfSides = 0
    var name: String
    
    init(name: String){
        self.name = name
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

class Square: Shape {
    var sideLength: Double
    
    init(sideLength: Double, name: String){
        self.sideLength = sideLength // Square 클래스의 프로퍼티 설정
        self.name = name // 슈퍼 클래스의 프로퍼티를 설정할 때, 슈퍼 클래스의 초기화 구문을 부르지 않음.
        numberOfSides = 4
    }
    
    func area() -> Double {
        return sideLength * sideLength
    }
}

 
아래의 예시를 확인하면 super.init의 쓰임을 확실하게 이해할 수 있다. 

class Shape{
    var numberOfSides = 0
    
    func simpleDescription()-> String {
        return "도형은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

class Square: Shape {
    var sideLength: Double
    
    init(sideLength: Double){
        self.sideLength = sideLength // Square 클래스의 프로퍼티 설정
        super.init() // 슈퍼클래스의 프로퍼티인 numberOfSides를 수정하기 전, 초기화 구문을 호출한다.
        numberOfSides = 4
    }
    
    func area() -> Double {
        return sideLength * sideLength
    }
}

 
(3) getter와 setter
프로퍼티는 getter와 setter를 가질 수 있다. 
다음 예시를 살펴보자. 

class Shape{
    var numberOfSides = 0
    var name: String
    
    init(name: String){
        self.name = name
    }
    
    func simpleDescription()-> String {
        return "도형 \(name)은 \(numberOfSides)개의 변을 가지고 있습니다."
    }
}

class EquilateralTriangle: Shape {
    var sideLength : Double = 0.0
    
    init(sideLength: Double, name: String){
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter : Double{
        get{
            return 3.0 * sideLength
        }
        set{
            sideLength = newValue / 3.0
        }
    }
    
    override func simpleDescription() -> String {
        return "정삼각형 \(name)은 길이가 \(sideLength)인 변 세 개를 가지고 있습니다."
    }
}

var triangle = EquilateralTriangle(sideLength: 3.1, name: "A") // 정삼각형 인스턴스 triangle 생성
print(triangle.perimeter) // get 블록 작동, 3.0 * sideLength의 값을 return 해줌. "9.3" 출력
triangle.perimeter = 12
//set 블록 작동, 수정된 프로퍼티 perimeter을 newValue에 전달 받아 자동으로 sideLength의 값을 업데이트
print(triangle.sideLength) // "4" 출력

 
get 블록은 해당하는 값을 읽을 때, 
set 블록은 해당하는 값을 수정할 때 수행된다고 생각하면 
get 블록에만 return 키워드가 존재하는 이유를 이해할 수 있다.
c++에서의 get과 set 멤버 함수 설정과 비슷한 작동을 한다. 
즉, 특정 프로퍼티 값과 연관된 다른 프로퍼티를 굳이 따로 설정해주지 않아도 자동으로 변환해주는 기능이라 생각하면 된다. 
 
get 블록과 set 블록의 작성 방식에 대해 신경 쓰자!! 
 
 
 
 
 
 
 
2. 튜플(Tuples) 
튜플은 여러 타입의 값들을 하나로 그룹화하는 기능을 가진다. 
하나의 튜플 안에는 어떠한 타입도 가능하며 모두가 같은 타입일 필요는 없다. 
아래의 예시는 숫자와 사람이 읽을 수 있는 설명을 제시하는 문자를 포함한 튜플을 사용하고 있다.

let http404Error = (404, "Not Found") 
// 튜플의 타입은 (Int, String) 이다.

 
 
(1) 튜플의 내용에 접근하기
튜플의 내용은 아래와 같이 각각의 상수 또는 변수로 분해하여 접근할 수 있다. 

let (code, message) = http404Error // 404와 "Not Found" 값이 상수 code와 message에 저장된다.
print(code) // "404" 출력
print(message) // "Not Found" 출력

 
만약 튜플의 내용 중 일부만 필요하다면, 밑줄을 써서 대충 생략해주면 된다. 

let (onlyCode, _) = http404Error
print(onlyCode) // "404" 출력
// print(_) 를 해도 "Not Found" 는 출력되지 않고 컴파일 오류가 뜬다.

 
숫자 0으로 시작하는 인덱스를 사용해서 튜플의 개별 내용에 접근할 수 있다. 

print(http404Error.0) //"404" 출력
print(http404Error.1) //"Not Found" 출력

 
튜플을 정의하는 단계에서부터 각각의 요소에 이름을 붙여 사용할 수 있다.
이 경우에도 dot(.) 앞에 튜플의 이름을 적어주는 것은 필요하다. 

let http404Error = (code: 404, description: "Not Found")

print(http404Error.code) //"404" 출력
print(http404Error.description) //"Not Found" 출력

 
 
 
(2) 여러개의 반환값이 있는 함수
튜플은 함수의 반환 값으로 사용할 때 특히 유용하다.
일반적으로는 단일 타입의 단일 값만 반환할 수 있지만,
튜플을 사용하면 다른 타입의 여러 값들을 한번에 반환할 수 있다.
아래의 예시를 자세하게 살펴보자. 

func minMax(array: [Int])->(min: Int, max: Int){
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count]{
        if value<currentMin{
            currentMin = value
        }
        if value>currentMax{
            currentMax = value
        }
    }
    return (currentMin,currentMax)
}

let test = minMax(array: [8,0,1,5,3,10,-4,-20])
print(test.min)
print(test.max)

 
첫 줄을 살펴보자. minMax(array: [Int] ) 로 정의된 함수는 정수 배열 array를 입력받아서 Int 값 두 개를 포함한 튜플을 반환한다. 
이때 반환되는 튜플의 각 값에는 min과 max로 이름이 붙여져 있다. 이는 이후 튜플 내 요소에 접근할 때 사용할 수 있다. 
 
함수의 본문을 살펴보자. currentMin과 currentMax로 정의된 두 변수는 array의 인덱스 0에 위치한 값으로 설정된다. 
for문 안에서는 value 값에 array 배열의 인덱스 1부터 마지막 인덱스 값까지 차례로 대입하여, 이를 currentMin 과 currentMax 보다 작거나 더 큰지 확인하는 작업을 수행한다. 이때 더 작거나 큰 값이 발견되면 이를 currentMin과 currentMax 값에 새롭게 업데이트한다. 
최종적으로는 currentMin과 currentMax에 가장 작고 큰 값만 남고, 이를 튜플 형태로 반환한다.
 
아래의 print 문에서, 앞서 각 값에 붙였던 이름인 min과 max를 이용하여 출력하고 있다. 
함수를 정의하는 과정에서 min과 max로 이름을 굳이 붙여주지 않아도 해당 코드는 정상적으로 돌아간다. 
아니면 print 부분에서 이름으로 값을 부르는 것이 아닌, 인덱스 넘버를 활용하거나 상수 또는 변수로 분해하여 출력하는 등 앞서 제시된 방법으로 바꿔 작성해도 괜찮다.