Danny의 iOS 컨닝페이퍼
article thumbnail
Published 2022. 12. 28. 23:24
[iOS/Swift] Class와 Struct Xcode/Swift 문법

Class와 Struct는 프로퍼티메서드를 다룰 수 있는 틀입니다.

propert == 속성, method == 함수

 

Class & Struct

  • 객체를 찍어내는 틀(설계도, 청사진 등)이라고들 합니다 
  • 인스턴스: 설계도의 복제본. 즉, 인스턴스(객체, 실제 데이터)는 에서 찍어낸 결과물을 인스턴스라고 합니다.
    (인스턴스는 실제로 메모리에 할당되어 구체적 실체를 갖춘 것이라는 의미입니다.)
  • 특별히 클래스에서는 인스턴트를 객체(object)라고 부릅니다.
  • 초기화(init)나 이름 뒤에( ) 실행문으로 인스턴스의 생성을 할 수 있습니다.

 

Class & Struct의 차이점

가장 큰 차이는 메모리 저장 방식의 차이입니다. 

 

들어가기 앞서 간단히 메모리 구조가 뭔지 알아봅시다.

  • 코드(Code) : 순차적으로 한 줄씩 실행합니다.
  • 데이터(Data) : 전역 변수타입 속성을 저장합니다 (어디에서도 접근 가능하죠)
  • 힙(Heap) : 클래스의 객체, 클로저에서 사용됩니다. 데이터를 길게 저장하기 위해 사용합니다.
  • 스택(Stack) : 함수의 실행 시 필요한 데이터가 생성되고, 사용 완료 후 사라집니다.
                              값(value 타입)에서 사용되죠. 사용 후 메모리에서 제거되기 때문에 가볍습니다.

Class

  • 참조 타입입니다.(Reference Type)
  • 객체의 데이터를 힙(Heap) 영역에 저장됩니다.
  • (복사 시) 값을 전달하는 것이 아니라 주소를 전달합니다.
  • 힙에 저장되기 때문에 ARC시스템을 통해 메모리 관리를 해줘야 합니다.
  • 상속 가능합니다.

Struct

  • 값타입입니다.(Value Type)
  • 인스턴스의 데이터를 스택(Stack) 영역에 저장됩니다.
  • (복사 시) 값을 전달할 때마다 복사본을 생성 (다른 메모리 공간을 생성합니다.)
  • 스택 프레임 종료 시, 메모리에서 자동 제거합니다.
  • 상속이 불가합니다.

 

예제

우리는 초보니까 코드를 보면서 한 번 더 이해해 봅시다.

 

Class

class classPhone {
    var name: String
    var brand: String
    
    init(name: String, brand: String) {
        self.name = name
        self.brand = brand
    }
}

let phone = classPhone(name: "IPhone 14Pro", brand: "Apple")
let anotherPhone = phone

위와 같이 Class로 된 classPhone이 있습니다. 만약 복사된 이름을 바꾸면 어떤 일이 벌어질까요?

anotherPhone.name = "IPhone 11pro"

print("\(phone.brand)의 \(phone.name)입니다.")
print("\(anotherPhone.brand)의 \(anotherPhone.name)입니다.")

// Apple의 IPhone 11pro입니다.
// Apple의 IPhone 11pro입니다.

phoneanotherPhone 둘 다 모두 값이 같은 걸 확인할 수 있습니다.
그 이유는 Class는 참조형식. 즉, 값의 주소 자체를 복사하므로 이름을 변경한다 해도 값이 같을 수밖에 없는 거죠.

 

여기서 주소라고 하면 이해가 안 될 수도 있으니 간단하게 비유를 들어보겠습니다.

파일을 다른 사람에게 보낸다고 생각해 봅시다.

  • 링크로 저장해서 보낸다.(클래스, 참조타입)
    • 링크로 보내면 보낸 사람 말고는 수정이 불가능할 거예요.
    • 만약 보낸 사람이 파일을 수정한다면 다른 받는 사람들도 전부 바뀐 파일을 받게 될 거예요. 
  • 파일로 저장해서 보낸다.(구조체, 값타입)
    • 파일만 갖고 있다면 누구나 수정이 가능합니다. 사람마다 다른 파일을 갖고 있죠.

대충 감이 잡히시나요?

 

Struct

struct structPhone {
    var name: String
    var brand: String
}

let phone = structPhone(name: "IPhone 14Pro", brand: "Apple")
let anotherPhone = phone

위와 같이 Struct로 된 structPhone이 있습니다. 만약 복사된 이름을 바꾸면 어떤 일이 벌어질까요?

anotherPhone.name = "IPhone 11pro"
// Error: Cannot assign to property: 'anotherPhone' is a 'let' constant

에러가 나게 됩니다! 여기서 한 가지 특징을 또 알 수 있습니다.

let anotherPhone = phone

우리가 위와 같이 구조체를 생성할 때 상수(let)로 만들어줘서 변경이 불가능한 겁니다.

구조체는 값타입이기 때문에 복사를 하게 되면 structPhone을 새로 생성(복사)해서 사용하게 되는 거죠.

그래서 변수(var)를 사용해야 합니다.

 

변수로 바꿔서 다시 결과 값을 보겠습니다.

let phone = structPhone(name: "IPhone 14Pro", brand: "Apple")
var anotherPhone = phone

anotherPhone.name = "IPhone 11pro"

print("\(phone.brand)의 \(phone.name)입니다.")
print("\(anotherPhone.brand)의 \(anotherPhone.name)입니다.")

// Apple의 IPhone 14Pro입니다.
// Apple의 IPhone 11pro입니다.

구조체는 새롭게 복사를 하게 되므로 서로 다른 값을 갖고 있게 되는 겁니다.

 

여기서 구조체는 한 가지 특징이 더 있습니다.

구조체의 메서드를 사용해서 자기 자신인 프로퍼티(이름, 브랜드)를 바꾼다고 생각해 봅시다.

struct structPhone {
    var name: String
    var brand: String
    
    func changePhone() {
        self.name = "Galaxy Z Flip"
        self.brand = "Samsung"
    }
}

// Error: Cannot assign to property: 'self' is immutable

위에서 말씀드렸다시피 구조체는 값타입입니다. 값을 복사해서 사용하는 게 목적이죠.

Swift에서는 기본적으로 구조체의 프로퍼티 수정 허용하지 않습니다.

그래서 자기 자신(프로퍼티)을 변경시키려면 특별한 키워드가 필요합니다. 바로 mutating 키워드입니다.

값 타입의 프로퍼티를 수정하려면 인스턴스 메서드에서 mutating 키워드를 사용해야 수정이 가능합니다.
struct structPhone {
    var name: String
    var brand: String
    
    mutating func changePhone() {
        self.name = "Galaxy Z Flip"
        self.brand = "Samsung"
    }
}

이와 같이 mutating키워드를 사용하게 되면 프로퍼티를 변경시킬 수 있습니다.

var phone = structPhone(name: "IPhone 14Pro", brand: "Apple")
phone.changePhone()

print("\(phone.brand)의 \(phone.name)입니다.")

// Samsung의 Galaxy Z Flip입니다.

 

 

추가 (중요치 않음)

아직 다루지 않은 이니셜라이저 관련 내용인데 몰랐던 걸 일단 여기에 간단히 추가하겠습니다.

 

고차함수 및 init 관련 내용입니다. 제가 보기 위해 쓴 글이라서 패스하셔도 됍니다.

 

 

오늘부터 저는 커피 장사를 하고 있습니다.

 

일단 장사를 하기 앞서 커피 메뉴판을 만들어 줬어요.

struct Menu {
    let coffee: String
    let size: String
}

 

커피 메뉴가 있으니 손님 이름과 함께 주문을 받아야 겠죠?

struct CoffeeOrder {
    let name: String
    let order: Menu
}

 

그런데 갑자기 손님이 들이닥쳐서 많은 처리하기 위해 주문 리스트도 바로 만들어 줬어요.

struct CoffeeOrderList {
    let orderList: [CoffeeOrder]
}

 

사람들이 메뉴를 고르고 줄을 서있네요. 빨리 주문을 받아 봅시다.

let daniel: Menu = Menu(coffee: "cappuccino", size: "small")
let basco: Menu = Menu(coffee: "espresso", size: "medium")
let nina: Menu = Menu(coffee: "latte", size: "large")

 

자 주문을 받기 위해서 CoffeeOrder로 생성자를 만들겠습니다.

여기서 유심히 봐야 하는게 실행인 소괄호를 "()" 하지 않으면 클로저 타입이라는 겁니다!

                               (Sting, Menu) -> CoffeeOrder

let order: (String, Menu) -> CoffeeOrder = CoffeeOrder.init

 

이와 같이 여러 방식으로 초기화가 가능하죠.

let order1 = order("Daniel", daniel)
let order2 = CoffeeOrder(name: "Basco", order: basco)
let order3 = CoffeeOrder.init(name: "Nina", order: nina)

 

이렇게 커피 주문리스트를 만들어 한눈에 볼수 있게 됬네요!

 

let orders: [CoffeeOrder] = [order1, order2, order3]

let ordersList = CoffeeOrderList(orderList: orders)

 

 

고차함수(map)을 이용해 정리 해봅시다.

 

다시 사람들이 기다리고 있습니다.

let daniel: Menu = Menu(coffee: "cappuccino", size: "small")
let basco: Menu = Menu(coffee: "espresso", size: "medium")
let nina: Menu = Menu(coffee: "latte", size: "large")

 

 

 

 

 

 

 

 

부족한 설명이지만, 조금은 이해 가셨나요?

틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!