Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
반응형
Archives
Today
Total
관리 메뉴

슈프림 블로그

[Swift] 객체를 추상화 하는 Struct와 Class 사용법, 차이는 무엇일까? 본문

iOS_Swift

[Swift] 객체를 추상화 하는 Struct와 Class 사용법, 차이는 무엇일까?

_슈프림 2020. 8. 30. 21:00
728x90

 

프로그램을 수많은 객체라는 기본 단위로 나누고, 이 객체들의 상호작용으로 서술하는 프로그래밍 방법론을 객체지향 패러다임이라고 한다. 객체를 만들기 위한 방법으로는 Struct (구조체)Class (클래스) 를 사용하는 방법이 있다. 구조체와 클래스는 프로퍼티메서드를 가지고 있고, 이를 통해 데이터와 기능들을 정의할 수 있다. 구조체와 클래스를 정의한다는 것은 새로운 데이터 타입을 정의하는 것이라고 할 수 있다. 둘의 사용법과 역할이 거의 비슷한데, 차이는 무엇일까?? 사용방법을 살펴 보면서 차이점을 알아보자.

 

구조체

구조체 정의

구조체는 struct 키워드로 정의한다. 구조체 명은 타입 명 이라고 생각할 수 있으므로, Int, Bool 과 같이 대문자 카멜 케이스로 작성하는 것이 좋다. 중괄호 안에는 프로퍼티메서드를 넣어주면 된다.

struct MyStruct {
    var property1: Int
    let property2: String

    func method1() {}
    func method2() -> Bool {
        return true
    }
}

 

구조체 이니셜라이즈

이니셜라이즈(init)는 인스턴스를 생성하기 위해서 사용되는 특수한 메서드이다. 이니셜라이즈는 인스턴스를 생성함과 동시에 객체의 프로퍼티 값을 초기화해주는 역할을 한다. 구조체는 기본적으로 생성되는 이니셜라이즈가 존재한다. 기본 생성 이니셜라이즈는 매개변수 명이 구조체의 프로퍼티 명과 동일하다. 이니셜라이즈는 사용자가 직접 구현부에서 정의하여 용도에 맞게 사용할 수도 있다.

위의 예제로 살펴보면 기본 이니셜라이즈는 다음과 같이 생겼다.

(실제로 코드 상에는 존재하지 않지만 사용자는 인스턴스를 생성할 때 사용할 수 있다.)

init(property1: Int, property2: String) {
    self.property1 = property1
    self.property2 = property2
}

그리고 위의 기본 이니셜라이즈를 사용하는 방법은 다음과 같다.

(init이라는 이니셜라이즈 메서드 명을 적는 것이 아니라 구조체 명을 사용하여 인스턴스를 생성한다.)

var myStruct = MyStruct(propert1: 0, property2: "Hello")

 

생성된 구조체 인스턴스의 프로퍼티 값 변경

구조체 인스턴스의 프로퍼티에 접근하고 싶다면 인스턴스명 뒤에 마침표(.)를 사용하여 접근할 수 있다. 하지만 접근지정자에 따라 접근이 불가능할 수 도 있는데 이는 나중에 따로 접근지정자 부분을 공부하여 다룰 예정이다. 간단하게만 설명하자면, 현재는 아무 접근 지정자가 붙어있지 않기 때문에 디폴트 값인 public으로 선언되어 있는 것이고, public은 프로그램 내의 어디서든지 접근이 가능하다는 의미이다. 지금은 신경 쓰지 않아도 된다!

 

마침표(.)을 사용하면 접근은 가능하지만, 값을 변경하는것은 var 키워드로 선언된 프로퍼티만 변경 가능하다. let은 인스턴스 생성 시 한번 초기화된 값으로, 더이상 변경할 수 없다. property1var 키워드로 선언되었기 때문에 변경할 수 있는 값이고, property2let 키워드로 선언되었으므로 변경하려고 하면 컴파일 에러가 발생한다.

print(myStruct.property1)    // 0
print(myStruct.property2)    // "Hello"

myStruct.property1 = 100
print(myStruct.property1)    // 100
myStruct.property2 = "Swift"    // ERROR!

 

구조체 인스턴스 메소드 호출

마찬가지로 마침표 (.)을 사용하여 메소드를 호출할 수 있다.

myStruct.method1()
myStruct.method2()

 


 

클래스

클래스는 구조체와 사용 방법이 거의 동일하다. 단, 한가지 다른 점은 구현부에서 프로퍼티값을 초기화 시켜주지 않을 경우 자동으로 기본 이니셜라이즈가 생성되지 않는다는 것이다. 프로퍼티를 선언과 동시에 초기화 시켜주어 파라미터가 없는 이니셜라이즈를 사용하거나, 사용자 정의 이니셜라이즈를 정의하여 사용해야 한다.

 

클래스 정의

// 프로퍼티를 모두 선언과 동시에 초기화 해 주어야 한다.
class MyClass1 {
    var property1: Bool = true
    let property2: Float = 0.0
    
    func method1() { ... }
}

// 또는 사용자정의 이니셜라이즈를 만들어 주어야 한다.
class MyClass2 {
    var property1: String
    let property: Double
    
    init() {
         self.property1 = "Swift"
         self.property2 = 0.0
    }
    
    init(property1: String, property2: Double) {
    	self.property1 = property1
        self.property2 = property2
    }
    
    func method1() { ... }
}

 

클래스 인스턴스 생성

var myClass1 = MyClass1()
var myClass2 = MyClass2(property1: "Hello Swift", property2: 1.0)

 

 

클래스 프로퍼티 접근 및 메소드 호출

var로 선언된 프로퍼티는 변경 가능

myClass1.property1 = false
myClass2.property1 = "Value Changed"

let으로 선언된 프로퍼티는 변경 불가

myClass1.property2 = 100	// Error!
myClass2.property2 = 100 	// Error!

메소드 호출

myClass1.method1()
myClass2.method1()

 


 

구조체와 클래스의 차이

위에서 보았듯이 구조체와 클래스는 사용방법도 거의 비슷하고 생김새도 비슷하다. 그렇다면 가장 큰 차이점은 무엇일까?? 구조체는 값 타입(value-type)이고 클래스는 참조 타입(reference-type)이라는 것이다. 그 외의 차이점은 다음과 같다.

  • 구조체는 값 타입, 클래스는 참조 타입
  • 상속 : 클래스만 가능
  • 타입 캐스팅 : 클래스의 인스턴스만 가능

 

값 타입 vs 참조 타입

어떤 함수의 전달 인자로 값 타입 인자를 넘기면, 전달될 값이 복사되어 전달된다. 함수 내에서 전달된 값 타입 인자를 변경하려고 하면 변경되지 않는다. 하지만 참조 타입 인자는 값을 복사하지 않고 참조(주소)가 전달된다. 함수 내에서 참조타입 인자를 변경 가능하다.

 

구조체는 값 타입이므로 copiedStruct는 originStruct의 값만 복사해왔기 때문에 별도의 값을 갖는다. 즉, copiedStruct의 값이 변경되도 originStruct의 값에는 영향을 미치지 않는다.

var originStruct = MyStruct(property1: 1, property2: "string")
var copiedStruct = originStruct	// 값만 복사 됨

// copiedStruct 변경 전
print("originStruct.property1:", originStruct.property1)	// originStruct.property1: 1

// copiedStruct 변경 후
copiedStruct.property1 = 100
print("copiedStruct.property1:", copiedStruct.property1)	// copiedStruct.property1: 100
print("originStruct.property1:", originStruct.property1)	// originStruct.property1: 1

클래스는 참조 타입이므로 copiedClass와 originClass는 같은 인스턴스를 참조하므로, 둘중 하나의 값만 변경되도 모두 같은 값을 갖는다.

var originClass = MyClass1()
var copiedClass = originClass

// copiedClass 값 변경 전
print("originClass.property1:", originClass.property1)	// originClass.property1: true

// copiedClass 값 변경 후
copiedClass.property = false
print("copiedClass.property1:", copiedClass.property1)	// copiedClass.property1: false
print("originClass.property1:", originClass.property1)	// originClass.property1: false

 

구조체는 값 타입이므로 인스턴스를 let으로 선언했을 때 프로퍼티의 값을 변경할 수 없다. var로 선언된 프로퍼티라도 인스턴스가 let이라면, 값이 고정되어버리므로 내부적인 값도 변경할 수 없게 되는 것이다.

let immutableStruct = MyStruct(property1: 0, property2: "Hello")
immutableStruct.property1 = 100	// Error!
// property1은 var로 선언되었지만, immutableStruct가 let으로 선언되었기 때문에 변경할 수 없다.

클래스는 참조 타입이므로 인스턴스를 let으로 생성해도 프로퍼티를 변경할 수 있다. 물론 var로 선언된 프로퍼티만 변경 가능하다.

let mutableClass = MyClass1()
mutableClass.property1 = false	// 변경 가능!

 


 

Summary

Struct와 Class의 공통점

  • 데이터를 구조화하여 프로퍼티메소드를 갖는다.
  • 이니셜라이즈를 통해 상태를 초기화한다.
  • 특정 기능의 제한을 두기 위해 프로토콜을 준수할 수 있다.

Struct와 Class의 차이점

  • Struct는 값 타입, Class는 참조 타입이다.
  • Struct는 자동으로 이니셜라이즈를 생성해주지만, Class는 구현부에서 상태를 모두 초기화하지 않으면 이니셜라이즈를 자동으로 생성해 주지 않고 직접 구현해야 한다.
  • 상속은 Class만 가능하다.
  • 타입캐스팅은 Class만 가능하다.
반응형
Comments