지난포스팅을 보고 오시면 훨씬 좋아요
[iOS] 메모리 구조 (Stack, Heap, Data, Code)
Struct (구조체)는 struct 키워드로 다음과 같이 정의한다.
struct someStruct {
// properties and methods
var name: String
var score: Int
}
구조체를 정의하고 인스턴스를 초기화 해보자!
구조체는 기본적으로 생성되는 멤버 와이즈 이니셜라이저
(구조체는 사용자화 초기화 구문을 정의하지 않은 경우 자동적으로 멤버별 초기화 구문을 받음, 기본값이 지정되어 있지 않더라도)를 이용한다. (클래스는 멤버별 초기화를 받지 않는다)
var team1 = someStruct(name: "LiverpoolFC", score: 0)
하지만 프로퍼티에 기본값이 있는 경우 아래와 같이 생성해도 무방하다.
var team1 = someStruct()
인스턴스가 생성이 되고 프로퍼티 값에 접근 하고 싶을 때는 dot syntax (.
) 를 사용
var team1 = someStruct(name: "LiverpoolFC", score: 0)
team1.name = "LFC"
team1.score = 97
print(team1.name) // LFC
print(team1.score) // 97
Class(클래스)는 class 키워드로 다음과 같이 정의한다.
class someClass {
// properties and methods
var name = ""
var score = 0
}
이번에는 클래스를 정의하고 인스턴스를 초기화 해보자!
구조체와 달리 클래스는 멤버와이즈이니셜라이저 사용기 불가하기에 기본적인 이니셜라이저로 초기화를 하게된다. (자세한 내용은 공식문서의 초기화 파트 에서 확인 가능하다)
위의 정의코드에서 이미 class는 기본값이 지정되어 있으므로 따로 초기값을 전달할 필요가 없다.
var team2 = someClass()
구조체와 마찬가지로 클래스도 인스턴스가 생성이 되고 초기화되고 프로퍼티 값에 접근 하고 싶을 때는 dot syntax (.
) 를 사용하면 된다.
var team2 = someClass()
team2.name = "Manchester City"
team2.score = 92
print(team2.name) // Manchester City
print(team2.score) // 92
구조체와 클래스는 굉장히 비슷해보이고 실제로도 유사한 부분이 많다.
우선 공식문서에서 설명하는 공통점은 다음과 같다.
프로퍼티
정의한다메서드
를 정의한다서브 스크립트
정의한다초기화
정의한다확장
한다프로토콜
준수한다그리고 다음과 같은 차이점이 있다.
상속
이 가능하다.타입 캐스팅
을 사용하여 런타임에 클래스 인스턴스의 타입을 확인하고 해석할 수 있다.Deinitalizers
)을 사용할 수 있다.참조 카운팅
은 하나 이상의 클래스 인스턴스 참조를 허락한다.그리고 구조체와 클래스의 가장 큰 차이점은 구조체는 값 타입이고 클래스는 참조 타입이라는 것이다! 아래서 자세히 살펴보겠다.
값타입 (value type) : Structure, Enum, Tuple
참조타입(reference type) : Class, Closure
값타입과 참조타입은 메모리에 저장되는 방식에서 차이가 있다.
메모리에 대한 자세한 내용은 지난 포스팅 - [iOS] 메모리 구조 (Stack, Heap, Data, Code)에서 자세히 확인할 수 있다.
stack
에 실제 데이터가 저장heap
에 실제 데이터가 저장되고, stack
에는 heap 영역의 메모리주소가 저장따라서, 값타입은 값(실제 데이터)이 복사되어 전달되고, 참조타입은 값을 복사 하지 않고 참조(heap 영역의 주소)가 전달된다.
다음의 예시와 그림을 보면 조금 쉽게 이해 될 것이다.
/* Value Type (값타입) */
struct someStruct {
// properties and methods
var name = "Liverpool"
var score = 100
}
/* Reference Type (참조타입) */
class someClass {
// properties and methods
var name = "Manchester City"
var score = 92
}
var team1 = someStruct()
var team11 = team1 // ✅ 값타입이므로 별개의 새로운 인스턴스가 값을 복사하여 생성된다.
var team2 = someClass()
var team22 = team2 // ✅ 참조타입이므로 기존 인스턴스가 위치한 heap 주소(참조)만을 전달하고 실제 데이터는 heap 에 저장되어 있다
값 타입인 struct는 새로운 인스턴스가 값을 복사하여 stack 에 새로운 공간을 생성하므로 아래 코드에서 team1 과 team11의 프로퍼티는 각기 다른 값을 가진다.
/* Value Type (값타입) */
struct someStruct {
// properties and methods
var name = "Liverpool"
var score = 100
}
var team1 = someStruct()
var team11 = team1
team11.name = "hello"
print(team1.name) // Liverpool
print(team11.name) // hello
반면, 참조타입인 class는 데이터를 전달할때, 인스턴스의 참조를 복사하여 전달하므로 아래와 같이 값이 변동된다.
/* Reference Type (참조타입) */
class someClass {
// properties and methods
var name = "Manchester City"
var score = 92
}
var team2 = someClass()
var team22 = team2
team22.name = "hello"
print(team2.name) // hello
print(team22.name) // hello
let으로 선언된 인스턴스는, 인스턴스에 할당된 stack 영역의 메모리 공간을 변경하지 못하게 된다.
따라서, 값타입
의 let으로 선언된 인스턴스는 stack 영역에 실제 데이터를 저장하므로 인스턴스 속성 수정에 있어 다음과 같은 제약이 발생한다.
하지만, 참조타입
에서는 stack 영역에 실제 데이터가 위치한 heap 영역의 주소를 저장하기에 참조 타입 인스턴스들이 가르키는 대상을 수정하지 못할 뿐, 실제 데이터는 heap에 위치하기에 속성을 변경할 수 있다.
클래스는 인스턴스끼리 참조가 같은지 확인할때는 식별연산자 (Identity Operators)를 사용하면 된다. 클래스는 참조타입이므로 클래스를 가르키는 변수가 다수가 될 수 있으므로 ===
가 필요하다.
==
: 두 상수나 변수의 value가 같은지 비교===
: 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 참!==
: 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 참메모리 관점에서는 다음과 같이 설명할 수 있다.
==
: stack 영역의 값을 비교===
: heap 영역의 값을 비교let c1 = someClass()
let c2 = someClass()
let c3 = c1
print(c1 === c2) // false
print(c1 === c3) // true
애플공식문서에서는 다음의 경우 구조체를 사용하는게 좋다고 이야기 한다.