[스위프트 프로그래밍-9장] 구조체와 클래스

sanghee·2021년 11월 2일
0
post-thumbnail

이 글은 스위프트 프로그래밍(3판, 야곰 지음)을 읽고 간단하게 정리한 글입니다. 책에 친절한 설명과 관련 예제 코드들이 있으므로 직접 사서 읽기를 추천합니다.

9.0 소개

9.0.1 객체지향 프로그래밍 패러다임

컴퓨터 프로그래밍 패러다임의 한 종류이다.

OOP(Object-Oriented Programming)이라고도 불린다.

컴퓨터 프로그램을 보는 관점 변화: 명령어의 목록 → 여러 개의 독립된 단위인 객체의 모임

각각의 객체는 서로 메세지를 주고 받고 데이터를 처리함

프로그램을 유연하고 쉽게 변경 가능, 유지보수 간편, 직관적인 코드

중요 용어

클래스

같은 종류(또는 문제 해결을 위한)의 집단에 속하는 속성과 행위를 정의한 것이다.

객체지향 프로그램의 기본 사용자 정의 데이터 타입이다.

다른 클래스 또는 외부 요소와 독립적으로 디자인되어야 한다.

객체가 만들어지기 위한 청사진이다. 실제 메모리에 객체를 할당해 인스턴스를 만들기 위한 일종의 설계 코드이다.

객체

클래스의 인스턴스(실제로 메모리에 할당되어 동작하는 모양을 갖춘 것)이다.

자신 고유의 속성이 있으며, 클래스에서 정의한 행위를 할 수 있다.

스위프트에서는 객체라는 용어보다는 '클래스의 인스턴스'라는 표현을 사용한다.

메서드(또는 메시지)

객체가 클래스의 정의된 행위를 실질적으로 하는 함수이다.

메서드를 통해 객체에 명령어나 데이터를 전달할 수 있다.

이러한 행위를 '메서드를 호출한다' 혹은 '메세지를 전달한다'고 표현한다.

9.1 구조체

9.1.1 구조체 정의

📌 키워드 == struct

새로운 타입을 생성하는 것과 마찬가지기에, 이름은 대문자 카멜케이스를 사용한다.

struct 구조체 이름 {
    프로퍼티와 메서드들
}

9.1.2 구조체 인스턴스의 생성 및 초기화

기본적으로 생성되는 맴버와이즈 이니셜라이저(11장)를 사용한다(클래스는 불가능하여 직접 만들어야 한다).

  • 프로퍼티 이름만으로 매개변수를 갖는 이니셜라이저이다.

프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용한다.

struct Person {
    let name: String
    var age: Int = 99
}

var person = Person(name: "Lee")

print(person.age) // 99

person.name = "Kim" // ⛔️ Cannot assign to property: 'name' is a 'let' constant
person.age = 100

print(person.age) // 100
class Person {
    let name: String
    var age: Int = 99

    init(name: String) {
        self.name = name
    }
}

let person = Person(name: "Lee")

9.2 클래스

스위프트의 클래스는 부모클래스가 없더라도 상속 없이 단독으로 정의가 가능하다.

9.2.1 클래스 정의

📌 키워드 == class

새로운 타입을 생성하는 것과 마찬가지기에, 이름은 대문자 카멜케이스를 사용한다.

클래스는 상속받을 수 있 때문에 상속(18장)받는다면 이름 뒤에 콜론(:)을 써주고 부모클래스 이름을 명시한다.

class 클래스 이름: 부모클래스 이름 {
    프로퍼티와 메서드들
}

클래스 인스턴스의 상수 프로퍼티는 프로퍼티가 정의된 클래스에서만 초기화할 수 있다. 처음 할당된 이후로는 값을 변경할 수 없다.

기본 이니셜라이저는 기본값이 지정되어 있을 때 사용할 수 있다. 그렇지 않다면 위처럼 이니셜라이저를 작성해야 한다.

class Person {
    let name: String
    var age: Int
}

// Class 'Person' has no initializers
// Stored property 'name' without initial value prevents synthesized initializers
// Stored property 'age' without initial value prevents synthesized initializers
class Person {
    let name: String = "Lee" // 이후에 값을 변경할 수 없다
    var age: Int = 99
}

let person = Person()
class Person {
    let name: String
    var age: Int = 99

    init(name: String) {
        self.name = name
    }
}

let person = Person(name: "Lee")

9.2.2 클래스 인스턴스의 생성과 초기화

스위프트의 공식문서에서는 클래스의 인스턴스를 객체라고도 하지만 좀 더 한정적인 인스턴스라는 용어를 사용한다.

프로퍼티 값에 접근하고 싶다면 마침표(.)를 사용한다. 구조체와는 다르게 클래스의 인스턴스는 참조 타입이므로 상수 let으로 선언해도 내부 프로퍼티 값을 변경할 수 있다.

class Person {
    let name: String
    var age: Int = 99

    init(name: String) {
        self.name = name
    }
}

let person = Person(name: "Lee")

print(person.age) // 99

person.name = "Kim" // ⛔️ Cannot assign to property: 'name' is a 'let' constant
person.age = 100

print(person.age) // 100

9.2.3 클래스 인스턴스의 소멸

클래스는 참조타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제(소멸)된다(11장).

소멸되기 직전에 deinit이라는 메서드(디이니셜라이저)가 호출된다.

  • 클래스당 하나만 구현 가능하다.
  • 매개변수와 반환 값을 가질 수 없기에 소괄호도 적지 않는다.

메모리에서 해제되기 직전에 처리할 코드를 넣는다.

class Person {
    let name: String
    var age: Int = 99

    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("Person 클래스의 인스턴스가 소멸됩니다.")
    }
}

var person: Person? = Person(name: "Lee")

person = nil // Person 클래스의 인스턴스가 소멸됩니다.

9.3 구조체와 클래스 차이

공통점

값 저장을 위한 프로퍼티나 기능 실행을 위한 메서드를 정의할 수 있다.

서브스크립트 문법을 통해 구조체 또는 클래스가 갖는 값(프로퍼티)에 접근하도록 서브스크립트를 정의할 수 있다.

이니셜라이저를 정의할 수 있다.

익스텐션을 통해 확장할 수 있다.

특정 프로토콜을 준수할 수 있다.

구조체에는 없는 클래스 특징

구조체는 값 타입이고, 클래스는 참조 타입이다!

상속할 수 있다.

타입캐스팅은 클래스의 인스턴스에만 가능하다.

디이니셜라이저는 클래스의 인스턴스에만 가능하다.

참조 횟수 계산은 클래스의 인스턴스에만 적용된다.

서브스크립트

struct Person {
    let fingers: Int
    
    subscript(index: Int) -> Int {
        return fingers * index
    }
}

let person = Person(fingers: 5)

print(person[2]) // 10

프로토콜

class Person: Comparable {
    let name: String
    let age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    
    static func < (lhs: Person, rhs: Person) -> Bool {
        lhs.age < rhs.age
    }
    
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == rhs.age
    }
}

let oldPerson = Person(name: "Lee", age: 99)
let youngPerson = Person(name: "Kim", age: 9)

print(oldPerson > youngPerson) // true
print(oldPerson <= youngPerson) // false

9.3.1 값 타입과 참조 타입

구조체는 값 타입이고, 클래스는 참조 타입이다. 값 타입과 참조 타입의 가장 큰 차이는 '무엇이 전달되느냐'이다.

예를 들어 어떤 함수의 전달인자로 값 타입의 값을 넘긴다면, 전달될 값이 복사된다. 반면에 참조 타입으로 값을 전달한다면 참조(주소, 포인터와 유사한 개념)가 전달된다.

값 타입, 참조 타입

struct StructPerson {
    let name: String
    var age: Int
}

var structPerson = StructPerson(name: "Lee", age: 99)
let structFriend = structPerson1 // 값을 복사했기에 별개의 값을 갖는다.

structPerson.age = 100

print(structPerson.age) // 100
print(structFriend.age) // 99
class ClassPerson {
    let name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let classPerson = ClassPerson(name: "Lee", age: 99)
let classFriend = classPerson // classPerson의 참조를 할당한다.

classPerson.age = 100

print(classPerson.age) // 100
print(classFriend.age) // 100 classPerson을 참조하기에 값이 변동된다.

함수

값 타입의 데이터를 함수의 전달인자로 전달하면 메모리에 전달인자를 위한 인스턴스가 생성되는데, 여기에는 전달하려는 값이 복사되어 들어간다.

참조 타입의 데이터를 전달하면 기존 인스턴스의 참조를 전달한다.

func getOlder(_ person: StructPerson) {
    person.age += 1 // ⛔️ Cannot assign to property: 'person' is a 'let' constant
}
func getOlder(_ person: ClassPerson) {
    person.age += 1
}

getOlder(classPerson1)

print(classPerson.age) // 101
print(classFriend.age) // 101

===

클래스의 인스턴스끼리 참조가 같은지 확인할 때 사용한다.

let classPerson = ClassPerson(name: "Lee", age: 99)
let classFriend = classPerson
let classOtherPerson = ClassPerson(name: "Kim", age: 100)

print(classPerson === classFriend) // true
print(classPerson === classOtherPerson) // false

9.3.2 스위프트의 기본 데이터 타입은 모두 구조체

스위프트의 기본 데이터 타입은 모두 구조체로 이루어져 있다. 모두 값 타입이다.

@frozen public struct String {

    /// Creates an empty string.
    ///
    /// Using this initializer is equivalent to initializing a string with an
    /// empty string literal.
    ///
    ///     let empty = ""
    ///     let alsoEmpty = String()
    @inlinable public init()
		/// ...

9.4 구조체와 클래스 선택해서 사용하기

애플은 가이드라인에서 다음 조건 중 하나 이상에 해당한다면 구조체를 사용하는 것을 권장한다.

  • 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적일 때
  • 캡슐화된 값을 참조하는 것보다 복사하는 것이 합당할 때
  • 구조체에 저장된 프로퍼티가 값 타입이며 참조하는 것보다 복사하는 것이 합당할 때
  • 다른 타입으로부터 상속을 받거나 자신을 상속할 필요가 없을 때

이런 몇 가지 상황을 제외하면 클래스로 정의하여 사용한다.

스위프트의 복사 처리

스위프트의 기본 데이터 타입이 모두 구조체라서 비효율적이라고 생각할 수 있으니 스위프트는 꼭 필요한 경우에만 '진짜 복사'를 한다. 적절히 알아서 효율적으로 처리해준다.

profile
👩‍💻

0개의 댓글

관련 채용 정보