iOS/swift

swift - Struct vs Class

돌맹이시터 2021. 7. 22. 00:45

 

 

swift에서 struct와 class의 차이점에 대해 정리한다.

 

 

가장 큰 차이점은 superclass로부터 상속받을 수 있다는 점이 있지만,

그것과는 별개로 superclass로부터 상속받지 않는 기본 class와 struct의 차이점을 정리할 것이다.

 

 

// ##### Enemy.swift #####
struct Enemy {
// ##### 1 ######
    var health : Int
    var attackStrength : Int
    
    // structs are immutable --> mutating func 을 사용해야 property의 값을 바꿀 수 있다.
    mutating func takeDamage(amount: Int) {
    	health -= amount
    }
    func move() {
    	print("Move forward")
    }
}

// ##### main.swift #####

// ##### 2 #####
var skeleton1 = Enemy(health: 100, attackStrength: 10)
var skeleton2 = skeleton1
// struct의 경우, skeleton1에서 초기화시킨 것을 그대로 복사한다. 
// skeleton1과 skeleton2는 서로 다르다. 


skeleton1.takeDamage(amount: 10)

print(skeleton1.health)
// 90
print(skeleton2.health)
// 100, skeleton1과 skeleton2는 별개이기 때문이다.

skeleton1.takeDamage(amount: 10)
skeleton2.takeDamage(amount: 10)

print(skeleton1.health)
// 80
print(skeleton2.health)
// 90

 

1. Struct에서는 자동으로 initializer가 제공(?)되기 때문에 따로 init method를 작성해줄 필요가 없다.

2. Struct는 값 유형(value type semantic)이기 때문에 '값'을 전달한다.

그리고 값을 바꾸면 바뀐 상태로 복사되어서 다시 할당된다.

 

예를 들어,

어떤 사진을 전달한다고 가정하면, 그 사진을 복사해서 전달한다.

여러 명이 사진을 전달받았을 경우

누군가가 전달받은 사진에 낙서를 한다고 해도, 원본이나 다른 사람들에게 전달한 사진에 영향을 주지 않는다.

 

따라서 let으로 초기화시킬 경우 struct의 값을 변경하고자 할 때 경고메시지가 뜨면서 막히게 된다. 

skeleton1, skeleton2를 var로 초기화시켜야 takeDamage() method를 사용하여 health 값을 변경할 수 있게 되는 것이다.

 

 

// ##### Enemy.swift #####
class Enemy {
	var health : Int
    var attackStrength : Int
    
    //####### 1 #######
    init(health: Int, attackStrength: Int) {
    	self.health = health
        //self.health : Enemy's property
        //health : got passed in as the input
        self.attackStrength = attackStrength
    }
    
    func takeDamage(amount: Int) {
    	health -= amount
    }
    func move() {
    	print("Move forward")
    }
}


// ##### main.swift #####


// ########## 2 ########
let skeleton1 = Enemy(health: 100, attackStrength: 10)
let skeleton2 = skeleton1
// skeleton1에서 초기화시킨 object를 참조했다. 즉, 동일한 object에 대해 2개의 레퍼런스가 생긴 것이다.
// let skeleton2 = Enemy(health: 100, attackStrength: 10) 을 입력했다면 
// skeleton1에서 초기화한 것과 동일한 object를 초기화했을 것이다.


skeleton1.takeDamage(amount: 10)

print(skeleton1.health)
// 90
print(skeleton2.health)
// 90 : class가 참조로 전달되기 때문, skeleton2는 skeleton1에서 참조한 것을 똑같이 참조했다.

skeleton2.takeDamage(amount: 10)

print(skeleton1.health)
// 80
print(skeleton2.health)
// 80

 

 

1. class에는 init method를 별도로 작성해줘야 한다.

2. class는 참조 유형(reference type semantic)이기 때문에 '레퍼런스'에 의해 전달된다.

예를 들어,

사진을 전달한다면, 사진이 있는 위치 - /Users/Desktop/photo.jpg -를 전달한다.

여러 명이 사진의 레퍼런스를 전달받았을 경우,

누군가가 전달받은 사진에 낙서를 하거나, 삭제를 할 경우 해당 레퍼런스를 사용하는 모든 사람에게 영향을 준다.

모두가 낙서가 된 사진을 갖게 되거나, 사진이 삭제되는 것이다.

이러한 문제점이 있기 때문에 Apple에서는 struct를 사용하는 것을 디폴트로 할 것을 권장한다.

- 특별히 상속이 필요하거나, Objective-C 코드를 가지고 작업해야 할 경우 class로 바꿔서 사용할 것을 권장한다.

 

 

레퍼런스가 동일하게 유지되는 동안에는

값을 변경할 때마다 메모리의 다른 곳에 저장된 object가 변경될 수 있다고 한다.

무슨 말인지는 잘 모르겠지만..

어쨋거나 결론은 Class에서는

참조하는 변수(skeleton1,2)가 변경 가능한지의 여부와 관계없이 class's member를 수정할 수 있다.

따라서 skeleton1, 2를 let으로 초기화시키더라도 class의 값을 수정할 수 있다는 점이 struct에서와의 차이점이다.

 

이 때 주의해야 할 점은 Enemy class 내에 있는 health나 attackStrength 등이 let으로 입력되어 있었다면,

그 값은 변경할 수 없다.