swift - 클래스와 구조체 정리하기

swift - 클래스와 구조체

 

 구조체와 클래스

 구조체와 클래스는 개발자가 데이터를 용도에 맞게 묶어서 표현하고자 할 때 유용하다.

 

 구조체의 인스턴스는 값 타입이고, 클래스의 인스턴스는 참조 타입이다.

 

struct Person{
    var name: String
    var age: Int
}

 

구조체 인스턴스의 생성 및 초기화

구조체 정의를 하고, 구조체의 인스턴스를 생성하고 초기화 하려면 기본적으로 생성되는 멤버 와이즈 이니셜라이저를 사용한다.

구조체에 기본 생성된 이니셜라이저의 매개변수는 구조체의 프로퍼티 이름으로 자동 저장된다.

인스턴스가 생성되고 초기화 된 후 프로퍼티 값에 접근하고 싶다면 ( . ) 마침표를 사용하면 된다.

 

[참고]

구조체를 상수 let으로 선언하면 인스턴스 내부의 프로퍼티 값은 변경할 수 없다[!]

변수 var로 선언하면 내부의 프로퍼티가 var로 선언된 경우 값을 변경해 줄 수 있다.

 

var son : Person = Person(name: "손흥민", age: 30)
son.age = 25    //변경 가능

let kim: Person = Person(name: "김기동", age: 20)
//kim.age = 30    //Cannot assign to property: 'kim' is a 'let' constant

 

클래스를 정의할 때는 class라는 키워드를 사용한다.

클래스는 구조체와 비슷하게 아래와 같이 정의 한다.

(참고로 클래스는 구조체와 다르게 상속기능을 사용할 수 있지만 구조체는 상속기능이 불가능하다.)

 

아래 정의된 클래스 Animal은 String 타입인 name과 Int 타입의 age 저장 프로퍼티가 있는 클래스이다.

 

class Animal{
    var name: String = ""
    var age: Int = 0
    
    deinit{
        print("\(name) 동물 클래스의 인스턴스가 사라집니다.")
    }
}

 

클래스 인스턴스의 생성과 초기화

클래스를 정의한 후 인스턴스를 생성하고 초기화하고자 할 때는 기본적인 이니셜라이저를 사용한다.

위의 예제에서 프로퍼티의 기본 값이 지정되어 있으므로 전달 인자를 통해서 따로 초기값을 잔달해주지 않아도 된다.

 

또한 클래스는 구조체와 다르게 클래스의 인스턴스는 참조 타입이므로 클래스의 인스턴스를 상수 let으로 선언해도 내부 프로퍼티 값을 변경 할 수 있다.

 

var tom:Animal = Animal()
tom.name = "tom"
tom.age = 12

print("tom name: \(tom.name)")
print("tom age: \(tom.age)")

let pingu:Animal = Animal()
pingu.name = "pingu"
pingu.age = 30

print("pingu name: \(pingu.name)")
print("pingu age: \(pingu.age)")

 

클래스 인스턴스의 소멸

클래스의 인스턴스는 참조 타입이므로 더 참조가 필요 없을 때 메모리에서 해제 됩니다.

 

이 과정을 소멸이라고 하는데 소멸되기 직전에 deinit이라는 메서드가 호출됩니다.

 

이 deinit 메서드는 디이니셜라이저라고 부르며 인스턴스 소멸 전에 데이터를 저장하거나 다른 객체에 인스턴스 소멸을 알려야 할때 이 메서드를 구현합니다.

var willDieAnimal:Animal? = Animal()
willDieAnimal?.name = "dinit"
willDieAnimal = nil
//dinit 동물 클래스의 인스턴스가 사라집니다. 출력됨

 

 

구조체와 클래스의 차이

공통점

1.값을 저장하기 위해 프로퍼티를 정의 할 수 있다.

2.기능 실행을 위해서 메서드를 정의할 수 있다.

3.서브스크립트 문법을 통해 구조체 또는 클래스가 갖는 값에 접근하도록, 서브스크립트를 정의할 수 있다.

4.초기화될 때의 상태를 지정하기 위해서 이니셜라이저를 정의할 수 있다.

5.초기구현과 더불어 새로운 기능 추가를 위해 익스텐션을 통해 확장할 수 있다.

6.특정 기능을 실행하기 위해 특정 프로토콜을 준수할 수 있다.

 

차이점

1.구조체는 상속할 수 없다.

2.타입캐스팅은 클래스의 인스턴스에만 허용된다.

3.디이니셜라이저는 클래스의 인스턴스에만 활용할 수 있다.

4.참조 횟수 계산은 클래스의 인스턴스에만 적용된다.

 

//구조체
var park:Person = Person(name: "park", age: 33)
park.age = 45

var parkFriend: Person = park


print("")
print("park age: \(park.age)")                      //45
print("parkFriend age: \(parkFriend.age)\n")        //45


parkFriend.age = 50


print("")
print("park age: \(park.age)")                      //45
print("parkFriend age: \(parkFriend.age)\n")        //50


/*
 park의 나이는 변동이 없다.
 parkFriend 는 park의 값을 복사해왔기 때문에 별개의 값을 갖는다.
 */


//클래스
var dongky: Animal = Animal()
var dongkyFriend: Animal = dongky

print("")
print("dongky age: \(dongky.age)")                      //0
print("dongkyFriend age: \(dongkyFriend.age)\n")        //0

dongkyFriend.age = 78

print("")
print("dongky age: \(dongky.age)")                      //78
print("dongkyFriend age: \(dongkyFriend.age)\n")        //78

/*
 dongky와 dongkyFriend가 참조하는 곳이 같음을 알 수 있다.
 */



//값만 전달이 된다.
func changePerson(_ info: Person){
    var copiedInfo:Person = info
    copiedInfo.age = 20
}


//주소가 전달된다.
func changeAnimal(_ info: Animal){
    let copiedInfo:Animal = info
    copiedInfo.age = 40

	//0x0000600000c00030
    print("copiedInfo address : \(Unmanaged.passUnretained(copiedInfo).toOpaque())") 

}

//값만 전달이 된다.
changePerson(park)
print("park age: \(park.age)")                      //45


// 클래스 타입의 인스턴스 주소 확인
//0x0000600000c00030
print("dongky address : \(Unmanaged.passUnretained(dongky).toOpaque())")
//전달 인자로 참조가 전달되었기 때문에 donky가 참조하는 값들에 변화가 생긴다.
changeAnimal(dongky)
print("dongky age: \(dongky.age)")                  //78 --> 40

 

식별연산자

클래스 인스턴스끼리 참조가 같은지 확인할 때 사용한다.

아래는 식별 연산자를 사용해서 두 참조가 같은 인스턴스를 가리키고 있는지 비교해 보는 코드.

var lionKing:Animal = Animal()
let lionFriend: Animal = lionKing
let anotherKing: Animal = Animal()


print(lionKing === lionFriend)              //true
print(lionKing === anotherKing)             //false
print(lionFriend !== anotherKing)           //true

 

구조체를 사용해야할 때

1.연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때

2.캡슐화한 값을 참조하는 것보다 복사하는 것이 합당할 때

3.구조체에 저장된 프로퍼티가 값 타입이며 참조하는 것보다 복사하는 것이 합당할 때

4.다른 타입으로부터 상속받거나 자신을 상속할 필요가 없을 때

 

 

 

참고: 스위프트 프로그래밍: Swift 5 - 한빛미디어