[TIL]04.24-2

rbw·2022년 4월 24일
0

TIL

목록 보기
11/97
post-thumbnail

Structures and Classes

먼저, Class 는 설계도입니다. 이것을 사용해서 객체를 만듭니다. 객체가 가진 여러가지 요소중에서, 프로그램에서 처리할 요소들을 도출하는 과정을 추상화라고합니다. 예를 들어 피트니스 멤버 프로그램은, 신장 체중 등록일을 저장하는 객체로 추상화 합니다.

추상화의 결과를 코드로 표현한 것을 Class 입니다. 객체의 특징과 상태를 속성으로, 동작은 메소드로 구현합니다.

클래스를 통해 생성된 객체를 인스턴스라고 합니다. 인스턴스끼리 상호작용 하도록 클래스를 구현합니다. 이 상호작용은 다른 객체의 속성을 바꾸거나 메소드를 호출하는것을 의미합니다. 이것을 메시지를 보낸다 라고 표현합니다. 애플이 제공하는 환경인 코코아 에서는 메시지를 받는 객체를 Receiver, 보내는 객체를 Sender라고 표현합니다.

구조체는 클래스와 달리 객체라고 부르지 않고 값이라고 부릅니다. Swift에서는 여기서 생성된 값도 인스턴스라고 부릅니다.

구조체와 클래스의 차이로는 구조체는 값 타입이며, 상속, 소멸자가 없고, 그리고 메모리 관리 방식에서 값 형식인 구조체는 인스턴스가 속한 스코프가 종료되면 메모리에서 자동적으로 제거 됩니다. 참조 형식인 클래스는 스코프와 관계없이 참조 카운팅에서 관리합니다.

필요한 경우를 잘 생각해서 무엇을 쓸지 판단해야 할듯

Initializer Syntax

생성자에 대해서 알아보겠습니다. 생성자가 종료되는 시점에는 모든 속성에 초기값이 저장되어 있어야 합니다

// 다음 문법
init(parameters) {
    statements
}

class Position {
    var x: Double
    var y: Double

    // 생성자는 모든 속성을 초기화 하여야 합니다.
    init () {
        x = 0.0
        y = 0.0
    }
    // 파라미터를 받는 모습
    init(value: Double) {
        x = value
        y = value
    }
}

Value Types and Reference Types

Swift에 값 형식에는 구조체, 열거형, 튜플이 있고 참조 형식에는 클래스, 클로저가 있습니다.

일반적인 구현 지침

객체 지향 프로그래밍에서는 주로 참조형식인 클래스로 구현합니다. 상대적으로 적은 데이터를 저장하고 상속이 필요하지 않다면 값 형식으로 구현합니다. 연관된 상수 그룹을 표현할 때는 열거형으로 표현하고, 코드 내에서 한번만 사용되는 형식은 튜플로 구현합니다. 나머지 값 형식은 구조체로 표현합니다.

함수형 프로그래밍과 프로토콜 지향 프로그래밍에서는 주로 구조체로 구현합니다. 상속을 구현해야하거나, 참조를 전달 해야 하는 경우에만 클래스로 구현합니다.

Idnetity Operator

두 인스턴스를 비교할 때 사용하는 항등 연산자. ===, !== 가 있습니다. 메모리 주소가 같다면 true를 리턴합니다 .

Nested Types

형식 내부에 선언하는 형식에 대해 알아보겠습니다. 예시로, String.CompareOptions 가 있습니다. String 내부에 구현되어 있고, String 내부 에서 사용하면 앞에를 생략하여 .CompareOptions만 표기해서 사용이 가능하다.

class One {
    // Nested Types 구조체
    struct Two {
        // Nested Types 열거형
        enum Three {
            case a

            class Four {

            }
        }
    }
    // One 내부에서는 앞에를 생략해서 선언 가능하다.
    var a = .Two()
}
let two: One.Two = One.Two()

Property

Lazy Stored Properties

지연(게으른) 저장 속성에 대해서 알아보겠습니다. 이는 속성이 처음 접근하는 시점에 초기화 됩니다.

문법은 다음과 같습니다. lazy var name: Type = DefaultValue

인스턴스가 생성된 후에 개별적으로 초기화가 되므로, 변수로 선언하는 특징이 있습니다. 그리고 선언 시점에는 표현식이 리턴하는 값이 속성형식과 동일한 값이 있어야 하고, 변수에 인스턴스를 초기화 해야합니다.

저장 속성을 클로저로 초기화 할때 다른 속성에 접근하려면 지연 저장 속성으로 선언해야 한다

struct eg{
    let date: Date = Date()

    // lazy를 생략한다면 date를 초기화 하는 시점이 같아지므로 에러가 발생
    lazy var formattedDate: String = {
        let f = DateFormatter()
        f.dateStyle = .long
        f.timeStyle = .medium
        return f.string(form: date)
    }()
}

초기화 하기 무거운 변수에 주로 사용하면 좋을 듯 하다

Computed Property

계산 속성에 대해 알아보겠습니다. 이 속성은 메모리 공간을 가지지 않습니다. 대신 다른 속성의 값을 읽고 계산해서 리턴하거나 속성으로 전달된 값을 다른 속성에 저장합니다. 이 뜻은 var로 선언 해야 한다는 뜻입니다.

선언 시점에 기본값을 할당할 수 없기 때문에, 형식추론을 사용할 수 없고, 그러므로 타입을 꼭 명시해야한다.

get은 해당 프로퍼티에 접근하면 호출이되고, set은 해당 프로퍼티에 값을 할당 하면 호출이 된다.

// 문법
var name: Type {
    get {
        statements
        return expr
    }
    // 파라미터 생략 가능, `newValue` 라는 값으로 전달이 가능하다.
    set(name) {
        statements
    }
}

// 읽기 전용 계산 속성
// 내부에서 자료형 다음 할당 기호 없이중괄호가 온다면 읽기-전용 계산 속성이라고 보면 된다.
var age: Int {
    let calendar = Calendar.current
    ...
    return year
}

속성에 추가 기능을 구현하기에 편할듯하다

Property Observer

속성을 감시하는 Property Observer에 대해 알아보겠습니다.

willSet은 속성에 값이 저장되기 직전에 호출되고, 새로 저장되는 값은 파라미터로 전달됩니다. 파라미터를 생략하면 newValue라는 값이 기본 파라미터를 제공합니다.

didSet은 값이 저장된 직후에 호출됩니다. 이전 값이 파라미터로 전달됩니다. 파라미터를 생략하면 oldValue라는 기본 파라미터를 제공합니다.

이는 변수 저장 속성에 추가가 가능하다. 지연 저장 속성과, 계산 속성에도 추가가 불가능 하다. 한 가지 예외로 계산 속성의 하위 클래스에서 계산 속성을 오버라이딩 하고 프로퍼티 옵저버를 구현하는 것은 가능하다.

// 문법
var name: Type = DefaultValue {
    willSet(name) {
        statements
    }
    didSet(name) {
        statements
    }
}

값이 변경되거나 되기 직전에 추가 기능을 구현하기 편할 듯 하다

Typed Property

형식 자체의 속성을 뜻합니다. 모든 인스턴스가 공유하는 하나의 공간만 생성합니다. 기본적으로 지연 속성입니다.

모든 인스턴스에서 해당 속성이 필요한 경우 사용하면 좋을 듯 하다

Stored Type Property

// 저장 형식 속성의 문법, 속성에 최초로 접근할 때 초기화 됩니다.
static var name: Type = DefaultValue
static let name: Type = DefaultValue

// 접근 하는 코드, 인스턴스 속성이 아니기 때문이다.
TypeName.propertyName 

class Math {
    static let pi = 3.14
}
Math.pi

Computed Type Property

// 스태틱은 서브클래스에서 오버라이딩이 금지된다.
static var name: Type {
    get {
        statements
        return expr
    }
    set(name) {
        statements
    }
}

// 클래스 라는 이름으로 선언하여서, 클래스에서 제한적으로 사용이 가능하다, 오버라이딩 가능
class var name: Type {
    get {
    statements
        return expr
    }
    set(name) {
        statements
    }
}

Self

self는 인스턴스 내부에서 접근하면 해당 인스턴스에 접근합니다. 타입 멤버 내부에서 접근한다면 형식 자체에 접근합니다.

// 문법
self // 인스턴스 자체 접근
self.propertyName 
self.method()
self[Index]
self.init(parameters) // 동일한 형식에 있는 다른 생성자를 호출할 때 사용합니다. self 생략 불가능

// self를 생략할 수 없는 경우
class Size {
    var width = 0.0
    var height = 0.0

    // 파라미터 이름을 다르게 하면 self를 적을 필요는 없지만 가독성이 좋아질듯 ?
    func update(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    // 클로저에서 인스턴스 멤버에 접근하려면 self를 캡처 해야합니다.
    func doSomething() {
        let c = { self.width * self.height }
    }
}

struct Size {
    var width = 0.0
    var height = 0.0

    // 인스턴스가 새 인스턴스로 교체 됩니다. 클래스에서는 사용 불가
    mutating func reset(value: Double){
        self = Size(width: value, height: value)
    }
}

self의 핵심은 현재 인스턴스에 접근하는 특별한 속성이고, 타입 멤버에서 사용하면 형식 자체를 나타낸다는 것이다.

Super라는 속성은 상속과 관련되어 있으므로 클래스에서만 사용 가능하다. 슈퍼클래스에 있는 멤버에 접근시에 사용한다.

Self Type

Self 라는 타입에 대해 알아보겠습니다.

// 예시 
extension Int {
    // 타입 속성이 확장하는 타입과 동일하다면 Self로 표기가 가능하다.
    static let zero: Self = 0
}

Self 키워드를 사용하면 타입에 의존하지 않는 범용 코드로 만들 수 있는 장점이 있다.

비슷한 경우에 복사 붙여넣기 하기 편할 듯. 새로 만드는것에 부담이 덜 한듯 하다

Property Wrapper

프로퍼티 래퍼는 속성에 접근하는 코드를 별도의 타입으로 분리하고 여러 곳에서 재사용하게 도와 주는 특성이다. wrappedValue 라는 프로퍼티를 추가 해야만한다.

// 예시
struct PlayerSetting {
    // 밑에서 생성한 프로퍼티 래퍼를 적용하는 과정
    // 적용후에는 initialSpeed에 저장되는 값은 UserDefaults에 저장된다
    @UserDefaultsHelper(key: "initialSpeed", defaultValue: 1.0)
    var initialSpeed: Double
    var supportGesture: Bool

    func resetAll() {
        // _ 를 붙여줘야 프로퍼티 래퍼에 접근이 가능하다. 생략하면 래핑된 값으로 접근해서 에러 발생
        _initialSpeed.reset()
        ...
    }

}
@propertyWrapper
struct UserDefaultsHelper<Value> {
    let key: String
    let defaultValue: Value

    var wrappedValue: Value {
        get {
            UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        }
        set {
            UserDefaults.standard.setValue(newValue, forKey: key)
        }
    }
    func reset() {
        UserDefaults.standard.setValue(defaultValue, forKey: key)
    }
    
    // 이름에 주의
    var projectedValue: Self { return self }
}

Projected Value

타입 외부에서 프로퍼티 래퍼에 접근하는 경우에 필요한 값이다.

var currentSetting = PlayerSetting()

// 타입 외부에서 $를 사용하여 projected value에 접근한다.
// projected value가 없으면 제공 x
currentSetting.$initialSpeed.reset()

Property Wrapper 초기화, 제약

@propertyWrapper
class SimpleWrapper {
    var wrappedValue: Int

    var metadata: String?

    init() {
        print(#function)
        wrappedValue = 0
        metadata = nil
    }

    // 인자라벨을 wrappedValue로 맞춰줘야 속성을 선언한 타입에서 초기화가 가능하다. 이유로는,
    // 밑의 MyType에서 기본값을 저장하면, 파라미터가 하나인 생성자를 자동으로 호출하는데
    // 인자라벨이 wrappedValue인 생성자를 호출하기 때문이다.
    init(wrappedValue value: Int) {
        wrappedValue = value
        metadata = nil
    }
    
    init(wrappedValue: Int, metadata: String?) {
        self.wrappedValue = wrappedValue
        self.metadata = metadata
    }
}

struct MyType {
    @SimpleWrapper
    var a: Int = 123
    // 위 아래 래퍼속성은 동일하다.
    @SimpleWrapper(wrappedValue: 456)
    var b: Int

    // 가독성이 좋은 초기화 방법
    @SimpleWrapper(wrappedValue: 123, metadata: "number")
    var c: Int 

    @SimplerWrapper(metadata: "number")
    var d: Int = 123
}

let t = MyType()

프로퍼티 래퍼 속성은 속성을 선언한 타입이 아니라 자신이 스스로 초기화 한다. 위의 MyType 에서 초기화 하려면 let t = MyType(a: 12) 로 선언해야 하는데, 프로퍼티 래퍼 속성에 해당 생성자가 있어야만 가능하다. init(){ ... } 만 있는 경우에는 에러가 발생한다.

제약으로, 전역 범위에서 프로퍼티 래퍼는 사용이 불가하다. 그리고 선언시에 lazy, @NSCopying, @NSManaged, weak, unowned 키워드를 같이 사용은 불가능하다. 그리고, 계산된 속성에서는 프로퍼티 래퍼를 적용이 불가하다, 서브 클래스에서 프로퍼티 래퍼가 적용된 속성을 오버라이딩 하는것도 불가하고 프로토콜이나 익스텐션에 선언되어 있는 속성에는 프로퍼티 래퍼를 적용할 수 없습니다.

profile
hi there 👋

0개의 댓글