메모리의 스택
영역에 저장된다
복사된 인스턴스를 변경해도 변하지 않는다
struct Point {
var x: Int
var y: Int
}
var a = Point(x: 0, y: 0)
var b = a
b.x += 10
print(a.x) //0
print(b.x) //10
값 타입은 enum을 제외하고는 모두 struct로 구현되어 있다!!!
데이터는 메모리의 힙
영역에 저장된다
그 데이터를 가리키는 주소값은 스택
영역에 저장된다
class Point {
var x: Int = 0
var y: Int = 0
}
let a = Point()
let b = a
b.x += 10
print(a.x)
print(b.x)
스택은 값타입을 다루며
힙보다 데이터를 넣었다 빼는 구조가 단순해 속도가 빠르다
힙 할당 개선하기
String은 값타입이지만 힙할당이 발생한다(String은 값타입으로 스택할당이 발생하지만 이를 구성하고 있는 Character 타입은 힙영역에 저장된다)
값타입인 구조체(struct)를 사용하면 프로퍼티가 enum타입이므로 구조체의 인스턴스를 생성하면 스택할당이 발생한다.
enum Emotion { case happy, sad, angry }
enum Species { case human, bear, dragon }
var cachedEmoji = [Attributes: UIImage]() //String -> Attributes로 변경
func generateEmoji(_ emotion: Emotion, _ species: Species) -> UIImage {
let key = Attributes(emotion: emotion, species: species)
if let image = cachedEmoji[key] {
return image
}
...
}
struct Attributes: Hashable { //Hashable 사용자 타입을 딕셔너리(컬렉션)의 키값으로 사용하기 위해
var emotion: Emotion
var species: Species
}
기본적으로 Collection 타입에 COW가 구현되어 있다.
값타입의 유일무이한 인스턴스의 생성 시 불필요한 복사를 줄이자
인스턴스를 복사하지 않고 처음에는 참조한 후, 수정이 발생한 경우에만 실제로 복사한다.
var array1: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(UnsafeRawPointer(array1)) // 0x0000000100704210
var array2 = array1
print(UnsafeRawPointer(array2)) // 0x0000000100704210
array2.removeLast() // Copy-on-Write
print(UnsafeRawPointer(array1)) // 0x0000000100704210
print(UnsafeRawPointer(array2)) // 0x0000000100404e70 수정 후 변화됨
직접 구현하기
struct User {
var name: String
var age: Int
}
class DataWrapper { //class라서 참조타입으로 만들어준다.
var data: T
init(data: T) {
self.data = data
}
}
struct COWUser {
private var dataWrapper: DataWrapper
init(data: T) {
self.dataWrapper = DataWrapper(data: data)
}
var data: T {
get {
return self.dataWrapper.data
}
set { //dataWrapper의 참조가 유일한지 확인한다.
guard isKnownUniquelyReferenced(&dataWrapper) else {
dataWrapper = DataWrapper(data: newValue)
return
}
dataWrapper.data = newValue
}
}
}
let user = User(name: "user1", age: 24)
var cowUser1 = COWUser(data: user)
var cowUser2 = cowUser1 // 메모리 주소 그대로
cowUser2.data.age = 22 // 메모리 주소 변경됨
값 타입 안의 참조 타입
//참조타입 클래스
class HighSchool: CustomStringConvertible {
var description: String {
return "\(name) High School"
}
var name: String
init(name: String) {
self.name = name
}
}
//값타입 구조체가 참조타입인 HighSchool타입의 프로퍼티를 포함한다.
struct Student {
var highSchool: HighSchool
}
//하나의 HighSchool클래스 인스턴스 생성
let swiftHighSchool = HighSchool(name: "Swift")
//두 개의 Student 구조체 인스턴스 생성, 클래스 인스턴스를 생성자의 인자로 사용
let student1 = Student(highSchool: swiftHighSchool)
let student2 = Student(highSchool: swiftHighSchool)
//클래스타입(참조타입)인 highSchool 프로퍼티는 주소값을 전달한다.
student2.highSchool.name = "Next"
print(student1.highSchool.name) //Next
print(student2.highSchool.name) //Next
참조 타입 안의 값 타입
struct Company {
var name: String
}
class Product {
var name: String
var company: Company //참조 타입 안의 값 타입
init(name: String, company: Company) {
self.name = name
self.company = company
}
}
//하나의 Company구조체 인스턴스 생성
let apple = Company(name: "Apple")
//두 개의 Product클래스 인스턴스 생성, 구조체를 인자로 사용
let iPhone = Product(name: "iPhone12", company: apple)
let macbookAir = Product(name: "macbook Air", company: apple)
//값 타입 구조체는 값을 복사한다.
macbookAir.company.name = "MicroSoft"
print(iPhone.company.name) //Apple
print(macbookAir.company.name) //MicroSoft
타입 내부에 새로운 타입을 선언해준 것
값 타입과 참조 타입의 혼용의 예시에서도 볼 수 있긴 한데...
Person클래스 안에 Job이라는 enum을 선언하고,
Student클래스가 Person클래스를 상속받는다면,
Person.Job과 Student.Job은 동일하게 사용할 수 있다.
특히나 왜 사용하냐면
이름이 같더라도 역할이 달라야 할 때 사용하면 좋다.(GameType, GameInfo)
아래는 예시코드이다.
struct Sports {
enum GameType {
case football, basketball
}
var gameType: GameType
struct GameInfo {
var time: Int
var player: Int
}
var gameInfo: GameInfo {
switch self.gameType {
case .basketball:
return GameInfo(time: 40, player: 5)
case .football:
return GameInfo(time: 90, player: 11)
}
}
}
struct ESports {
enum GameType {
case online, offline
}
var gameType: GameType
struct GameInfo {
var location: String
var pakage: String
}
var gameInfo: GameInfo {
switch self.gameType {
case .online:
return GameInfo(location: "www.liveonline.co.kr", pakage: "LoL")
case .offline:
return GameInfo(location: "제주", pakage: "SA")
}
}
}
var basketball: Sports = Sports(gameType: .basketball)
print(basketball.gameInfo) //GameInfo(time: 40, player: 5)
var sudden: ESports = ESports(gameType: .offline)
print(sudden.gameInfo) //GameInfo(location: "제주", pakage: "SA")
let someGameType: Sports.GameType = .football
let anotherGameTyep: ESports.GameType = .online
//let errorIfYouWantIt: Sports.GameType = .online //오류 Type 'Sports.GameType' has no member 'online'
메모리 측면에서 ARC도 다뤄야 할 거 같은 느낌적인 느낌