본문 바로가기

Mobile/Swift

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

반응형

1. 구조체

구조체는 struct 키워드로 정의합니다. 구조체를 정의한다는 것은 새로운 타입을 생성해주는 것과 마찬가지이기 때문에 기본 타입 이름처럼 대문자 카멜케이스를 사용해 이름을 짓습니다. 구조체는 상속이 되지 않습니다.

struct [구조체 이름] {
    [프로퍼티와 메소드]
}
struct Person {
	// "이름" 부분은 초기값을 정해주는 부분입니다.
    // 이렇게 초기값을 정해주면 인스턴스를 생성할 때 값을 생략할 수 있습니다.
    var name: String = "이름"
    var age: Int
    var height: Int
    var weight: Int
}

// 구조체의 인스턴스 생성 및 초기화
var axce: Person = Person(name: "Sudon Noh", age: 31, height: 176, weight: 80)
print(axce.name) // Sudon Noh
print(axce.age) // 31

2. 클래스

클래스는 class 키워드로 정의합니다. 클래스도 구조체와 마찬가지로 새로운 타입을 생성해주는 것과 같기 때문에 대문자 카멜케이스를 사용합니다.

class [클래스 이름] {
    [프로퍼티와 메소드]
}

클래스를 상속 받을 때는 클래스 이름 뒤에 콜론(:)을 붙이고 부모 클래스의 이름을 명시합니다.

class [클래스 이름]: [부모 클래스 이름] {
    [프로퍼티와 메소드]
}
class KoreaMember {
    var team: String = ""
    var number: Int = 0
    var position: String = ""
}

// 클래스의 인스턴스 생성 및 초기화
var lee: KoreaMember = KoreaMember()
lee.team = "마요르카"
lee.number = 19
lee.position = "공격형 미드필더"
"""
구조체에서는 상수 let으로 선언하면 인스턴스 내부의 프로퍼티 값을 변경할 수 없습니다.
하지만 클래스에서의 인스턴스는 참조 타입이기 때문에 클래스의 인스턴스를 상수 let으로
선언하더라도 내부 프로퍼티 값을 변경할 수 있습니다.
"""
let son: KoreaMember = KoreaMember()
son.team = "토트넘"
son.number = 7
son.position = "공격수"

클래스의 소멸

클래스의 인스턴스는 참조 타입이기 때문에 더는 참조할 필요가 없을 때 메모리에서 해제됩니다. 이 과정을 소멸이라고 하는데 소멸되기 직전에 deinit(deinitializer) 이라는 메소드가 호출됩니다.

class EnglishMember {
    var name: String = ""
    var team: String = ""
    var number: Int = 0
    var position: String = ""
    
    deinit {
        print("\(name) 선수가 영국 대표팀에서 제외되었습니다.")
    }
}

var kane: EnglishMember? = EnglishMember()
kane?.name = "Kane"
kane?.team = "토트넘"
kane?.number = 10
kane?.position = "공격수"
// 이때까지는 대표팀에 소속되어 있다.

// deinit 을 실행하고 소멸된다.
kane = nil
// Kane 선수가 영국 대표팀에서 제외되었습니다.


"""
deinit 메소드에는 인스턴스가 메모리에서 해제되기 직전에 처리할 코드를 넣어줍니다.
예를 들어 인스턴스 소멸 전에 테이터를 저장한다거나 다른 객체에 인스턴스 소멸을 알려주어야
할 때 구현합니다.
"""

3. 구조체와 클래스의 차이(값 복사와 참조)

구조체는 값 타입이고 클래스는 참조 타입입니다. 예를 들어 어떤 함수의 전달인자로 값 타입의 값을 넘긴다면 전달될 값이 복사되어 전달됩니다. 하지만 참조 타입이 전달인자로 전달될 때는 값을 복사하지 않고 참조 주소가 전달됩니다.

아래 코드를 예시로 보시면 더 쉽게 이해하실 수 있습니다.

// 구조체 예시
struct PlayerInfo {
    let name: String
    var age: Int
    var team: String
}

var player1: PlayerInfo = PlayerInfo(name: "Sudon Noh", age: 31, team: "Hanhwa Eagles")
player1.age = 100

print("player1's age:", player1.age)
// player1's age: 100

// player2에 player1을 할당합니다.
var player2: PlayerInfo = player1

// player2에 player1에서 지정했던 100의 값이 그대로 복사되어 들어갑니다.
print("player2's age:", player2.age)
//player2's age: 100

// player2의 age값을 999로 변경해줍니다.
player2.age = 999
print("player1's age:", player1.age)
print("player2's age:", player2.age)
// player1's age: 100
// player2's age: 999
// player2 는 player1 의 값 자체를 복사해서 왔기 때문에 변화가 없습니다.
// 클래스 예시
class PlayerInfo2 {
    var name: String = ""
    var age: Int = 0
    var team: String = ""
}

var player3: PlayerInfo2 = PlayerInfo2()
player3.name = "Sudon Noh"
player3.age = 31
player3.team = "Hanhwa Eagles"

// player3의 값을 player4로 할당해줍니다.
var player4: PlayerInfo2 = player3
print("player3's age:", player3.age)
print("player4's age:", player4.age)
// player3's age: 31
// player4's age: 31

"""
player4의 age값을 100으로 변경해줍니다.
player3의 age값은 31이었고, player4의 age값은 100으로 변경했기 때문에
player3의 age값이 31일 것으로 기대됩니다.
하지만 print 해보면 player3의 age값이 100인 것을 확인할 수 있습니다.
"""
player4.age = 100
print("player3's age:", player3.age)
print("player4's age:", player4.age)
// player3's age: 100
// player4's age: 100

// player3과 player4가 참조하는 곳이 같기 때문에 값을 복사했을 때
// 값이 같이 변경됩니다.

아래에서는 메소드는 구조체와 클래스를 각각 받아 age를 변경하는 메소드들 입니다. 메소드에서 사용할 경우 어떻게 값이 변화하는지 확인해보도록 하겠습니다.

// 구조체 age 변경 함수
func changeInfo(_ info: PlayerInfo) {
    var copiedInfo: PlayerInfo = info
    copiedInfo.age = 1
}

// 클래스 age 변경 함수
func changeInfo2(_ info: PlayerInfo2) {
    var copiedInfo: PlayerInfo2 = info
    copiedInfo.age = 9999
}

changeInfo(player1)
print("changeInfo player1's age: \(player1.age)")
// changeInfo로 전달된 것은 player1의 값이기 때문에
// 함수를 통해 player1의 age를 1로 바꾸더라도 player1의 값은 변경되지 않습니다.

changeInfo2(player3)
print("changeInfo2 player3's age: \(player3.age)")
print("changeInfo2 player4's age: \(player4.age)")
// changeInfo2 player3's age: 9999
// changeInfo2 player4's age: 9999
// changeInfo2로 전달된 것이 player3의 참조 값이기 때문에
// 같은 참조를 하고 있던 player4의 값까지 변경된 모습입니다.

클래스의 인스턴스 식별 연산자

클래스의 인스턴스끼리 참조가 같은지 확인할 때는 식별 연산자(Identity Operators)를 사용합니다.

// 새로운 PlayerInfo2의 인스턴스를 생성해서 비교해보도록 하겠습니다.
// 현재 player3 와 player4 의 참조값은 같은 상태입니다.

var player5: PlayerInfo2 = PlayerInfo2()

print(player3 === player4) // true
print(player3 === player5) // false
print(player4 !== player5) // true

// 스위프트의 기본 데이터 타입은 모두 구조체입니다.

스위프트의 기본 데이터 타입은 모두 구조체입니다.

4. 구조체를 써야할 때

애플은 가이드라인의 조건 중 하나 이상 해당한다면 구조체를 사용하는 것을 권장합니다.

1. 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때
2. 캡슐화한 값을 참조하는 것보다 복사하는 것이 합당할 때
3. 구조체에 저장된 프로퍼티가 값 타입이며 참조하는 것보다 복사하는 것이 합당할 때
4. 다른 타입으로부터 상속받거나 자신을 상속할 필요가 없을 때
[출처: 야곰 SWIFT 스위프트 프로그래밍 (swift 5)]

반응형