#2-2. 값타입과 참조타입, 타입 중첩 [스위프트]

인생노잼시기·2021년 5월 3일
0

🦅 스위프트 문법

목록 보기
6/13

값타입

메모리의 스택영역에 저장된다
복사된 인스턴스를 변경해도 변하지 않는다

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
  • struct, enum
  • Int, Double, String과 같은 기초 타입(Fundamental types)
  • Array, Set, Dictionary와 같은 컬렉션 타입(Collection types)
  • 값 타입으로 구성된 tuple

값 타입은 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)
  • class
  • closure

스택할당과 힙할당

스택은 값타입을 다루며
힙보다 데이터를 넣었다 빼는 구조가 단순해 속도가 빠르다

힙 할당 개선하기

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
}

Copy-on-Write(COW)

기본적으로 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

중첩 타입(Nested Types)

타입 내부에 새로운 타입을 선언해준 것
값 타입과 참조 타입의 혼용의 예시에서도 볼 수 있긴 한데...

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도 다뤄야 할 거 같은 느낌적인 느낌

profile
인생노잼

0개의 댓글