[Swift] 문법5 - 클래스와 구조체

LeeEunJae·2023년 3월 17일
0

iOS

목록 보기
6/14

📌 클래스와 구조체

클래스는 class , 구조체는 struct 로 정의합니다.

class Cat {
    var name: String?
    var age: Int?
    
    func simpleDescription() {
        if let name = self.name {
            print("😺 \(name)")
        } else {
            print("😺 No Name")
        }
    }
}

struct Coffee {
    var name: String?
    var size: String?
    
    func simpleDescription() {
        if let name = self.name {
            print("☕️\(name)")
        } else {
            print("☕️ No Name")
        }
    }
}

var myCat = Cat()
myCat.name = "숑이"
myCat.age = 7
myCat.simpleDescription() // 😺 숑이

var myCoffee = Coffee()
myCoffee.name = "아메리카노"
myCoffee.size = "Venti"
myCoffee.simpleDescription() // ☕️아메리카노

클래스는 상속이 가능합니다. 구조체는 불가능합니다.

class Animal {
    var name: String?
    var age: Int?
    var numberOfLegs = 4
    
    func simpleDescription() {
        if let name = self.name {
            print("animal : \(name)")
        } else {
            print("animal : No Name")
        }
    }
}

class Cat: Animal {
    override func simpleDescription() {
        if let name = self.name {
            print("😺 \(name)")
        } else {
            print("😺 No Name")
        }
    }
}

myCat.simpleDescription() // 😺 숑이 (오버라이드한 함수 호출)
print(myCat.numberOfLegs) // Animal 클래스로부터 상속 받은 값 4

클래스는 참조(Reference)하고 구조체는 복사(Copy)합니다.

var cat1 = Cat() // cat1은 새로 만들어진 Cat()을 참조
var cat2 = cat1 // cat2는 cat1이 참조하는 Cat()을 똑같이 참조
cat1.name = "숑이"
cat1.age = 7
// cat1 의 멤버 변수를 변경하면, cat1을 참조하고 있는 cat2의 내용을 출력했을 때, cat1과 동일하게 나옵니다.
cat1.simpleDescription() // 😺 숑이
cat2.simpleDescription() // 😺 숑이

var coffee1 = Coffee() // cofffee1은 새로 만들어진 Coffee() 그 자체
var coffee2 = coffee1 // coffee2는 coffee1을 복사한 값 그 자체
coffee1.name = "아메리카노"
coffee1.size = "Venti"
// 따라서 coffee1의 내용을 변경해도 coffee2는 변하지 않습니다.
coffee1.simpleDescription() // ☕️아메리카노
coffee2.simpleDescription() // ☕️ No Name

📌 생성자(Initializer)

클래스와 구조체는 모두 생성자를 가지고 있습니다. 생성자에서 멤버 변수를 초깃값을 지정할 수 있습니다.

class Animal {
    var name: String?
    var age: Int?
    var numberOfLegs = 4
    
    init() {
        self.name = "아무개"
        self.age = 1
    }
}

struct Coffee {
    var name: String?
    var size: String?
    
    init() {
        name = "커피"
        size = "Tall"
    }
}

만약 멤버 변수가 옵셔널이 아니라면, 항상 초깃값을 가져야 합니다. 그렇지 않으면, 컴파일 에러가 발생합니다.

class Animal {
    var name: String?
    var age: Int // error
    var numberOfLegs = 4
    
    init() {
    	// self.age = 1 <- 이렇게 초기화 해줘야 에러 발생하지 않음
    }
}

위 처럼 init() 안에서 초기화를 해주거나 아래와 같이 변수를 선언과 동시에 초기화 해야 한다.

class Animal {
    var name: String?
    var age: Int = 1
    var numberOfLegs = 4
}

생성자도 함수와 마찬가지로 파라미터를 받을 수 있습니다.

class Animal {
    var name: String?
    var age: Int
    var numberOfLegs = 4
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    func simpleDescription() {
        if let name = self.name {
            print("animal : \(name)")
        } else {
            print("animal : No Name")
        }
    }
}

class Cat: Animal {
    override func simpleDescription() {
        if let name = self.name {
            print("😺 \(name)")
        } else {
            print("😺 No Name")
        }
    }
}

var myCat = Cat(name: "숑이",age: 7)
myCat.simpleDescription() // 😺 숑이

만약 클래스를 상속받은 클래스에서 생성자를 구현하려면, 상위 클래스의 생성자를 호출해야 합니다. 만약 상위 클래스의 생성자와 파라미터가 모두 같다면, override 키워드를 붙여야합니다.
super.init() 은 클래스의 변수가 모두 초기화 된 후 호출되어야 합니다.
그리고나서 자기 자신에 대한 self 키워드를 사용할 수 있습니다.

class Cat: Animal {
    var hairColor: String?
    
    override init(name: String, age: Int) {
        self.hairColor = "gray"
        super.init(name: name, age: age)
        self.simpleDescription()
    }
    
    override func simpleDescription() {
        if let name = self.name {
            print("😺 \(name)")
        } else {
            print("😺 No Name")
        }
    }
}

만약 위 예시 코드에서 super.init() 이전에 self.simpleDescription()을 호출하면 컴파일 에러가 발생합니다.

override init(name: String, age: Int) {
        self.hairColor = "gray"
        self.simpleDescription() // error
        super.init(name: name, age: age)
    }

'self' used in method call 'simpleDescription' before 'super.init' call
self.simpleDescription()

deinit 은 메모리에서 해제된 직후에 호출됩니다.

class Cat: Animal {
	...
    
    deinit {
        print("메모리 해제 됨")
    }
    
    ...
}

📌 속성(Properties)

속성은 크게 두가지로 나뉩니다. 값을 가지는 속성 (Stored Property)과 계산되는 속성 (Computed Property) 인데요. Stored Property 는 값 자체를 가지고 있는 속성이고, Computed Property 는 어떠한 계산을 통해 반환되어지는 값 입니다.

우리가 지금까지 정의하고 사용한 name, age 와 같은 속성들은 모두 Stored Property 입니다. Computed Property 는 get, set 을 사용해서 정의할 수 있습니다. set 에서는 새로 설정된 값을 newValue 라는 예약어를 통해 접근할 수 있습니다.

struct Hex {
    var decimal: Int?
    var hexString: String? {
        get {
            if let decimal = self.decimal {
                return String(decimal, radix: 16)
            } else {
                return nil
            }
        }
        set {
            if let newValue = newValue {
                self.decimal = Int(newValue, radix: 16)
            } else {
                self.decimal = nil
            }
        }
    }
}

var hex = Hex()
hex.decimal = 10
print(hex.hexString) // "a"

hex.hexString = "b"
print(hex.decimal) // 11

위 코드에서 hexString 은 실제로 값을 가지고 있지 않지만, decimal 로부터 값을 가져와 16진수 문자열로 만들어서 반환합니다.
decimal 은 Stored Property, hexString 은 Computed Property 입니다.

추가적으로, get 만 정의할 경우, get 키워드를 생략 가능합니다. 이런 속성을 Read Only(읽기 전용) 라고 합니다.

struct Hex {
	...
    var hexString: String? {
        if let decimal = self.decimal {
            return String(decimal, radix: 16)
        } else {
            return nil
        }
    }
}

willSet, didSet 을 사용하면, 속성에 값이 지정되기 직전, 직후에 원하는 작업을 할 수 있습니다.

struct Hex {
    var decimal: Int? {
        willSet {
            print("\(self.decimal)에서 \(newValue)로 값이 변경될 예정입니다")
        }
        didSet {
            print("\(oldValue)에서 \(self.decimal)로 값이 변경 되었습니다.")
        }
    }
}

willSet은 set 과 마찬가지로 새로운 값을 newValue로 가져올 수 있고, didSet 은 바뀌기 이전 값에 oldValue 로 접근할 수 있습니다.

profile
매일 조금씩이라도 성장하자

0개의 댓글