iOS 스터디 Part5 Properties

Advanced Swift (by Chris Eidhof) 책을 번역 후 참고하여 작성하였습니다.

Properties.

Swift에서는 값을 저장하는 Stored Properties와 함수와 유사(저장 공간을 제공하지 않고 getter/setter 하는 방법을 제공)한 Computed Properties로 구분

Properties는 변수처럼 생각하면 쉽다.

예를 들어, 아래의 record라는 배열은 Stored Properties이다.

import CoreLocation

struct GPSTrack {
    var record: [(CLLocation, Date)] = []
}

** 만약, 외부에서는 read-only 상태로 바꾸려면 private(set) 또는 fileprivate(set) 키워드를 사용할 수 있다.

get, set을 이용한 Computed Properties를 만들 수 있다.

extension GPSTrack {
 		var totalDistance: CLLocationDistance {
        get {
            var distance: CLLocationDistance = 0.0

            // write getter logic

            return distance
        }
        set(newDistance) {
            // write setter logic
        }
    }
}

Change Observers.

Stored Properties와 변수에 대한 willSetdidSet 핸들러를 구현할 수 있습니다.

이들은 속성가 설정될 때마다 호출(값이 변경되지 않아도)

willSet : 값이 저장되기직전에 호출됩니다.

didSet : 새로운 값이 저장된직후에 호출됩니다.

Property가 선언된 위치에서 정의되어야 합니다. 따라서 extension에서 Property Observer를 나중에 추가할 수는 없습니다.

willSet, didSet에 특정 값을 넘겨주지 않으면 각각 newValue, oldValue로 값을 받을 수 있다.

class StepCounter {    
	var totalSteps: Int = 0 {       
		willSet(newState) {            
			print("totalSteps: \\(newState)")        
		}   
		didSet(oldState) {      
			print("\\(totalSteps - oldState) step")         
		}    
	}
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200

// willSet이 동작 새로운 값 newValue = 200 (출력:totalSteps: 200)
// didSet이 동작 기존 값 oldValue = 0 (출력: (200 - 0) step)

서브클래스에서 관찰자를 추가하려면 속성을 오버라이드할 수 있습니다. 다음은 Robot 클래스의 state 속성을 오버라이드하여 willSet Observer를 추가하는 예제입니다.

class Robot {
    enum State {
        case stopped, movingForward, turningRight, turningLeft
    }
    var state = State.stopped
}

class ObservableRobot: Robot {
    override var state: State {
        willSet(newState) {
            print("Transitioning from \\(state) to \\(newState)")
        }
    }
}

var robot = ObservableRobot()
robot.state = .movingForward // 출력: Transitioning from stopped to movingForward

Swift에서의 Property Observer는 순수하게 컴파일 타임 특성입니다.

Lazy Stored Properties.

값을 게으르게(lazily) 초기화하는 것은 Swift에서 흔한 패턴 중 하나

사용할 때 초기화하는 패턴입니다. → 메모리를 효율적으로 관리할 수 있기 때문

  1. lazy 속성은 초기화가 완료된 후에야 초기 값이 설정될 수 있으므로 항상 var(값을 변경할 수 있는)로 선언되어야 합니다.

  2. lazy는 Stored Properties에만 사용 가능합니다. 최초 값을 올린 값을 사용

  3. struct, class에만 사용 가능하다(enum에는 불가능)

예를 들어, GPSTrack을 표시하는 뷰 컨트롤러가 있다고 가정해봅시다.

해당 트랙의 미리보기 이미지를 가지려면 해당 속성을 lazy로 만들어 첫 번째로 속성에 액세스할 때까지 비용이 많이 드는 이미지 생성을 지연시킬 수 있습니다.

class GPSTrackViewController: UIViewController {
    var track: GPSTrack = GPSTrack()
    lazy var preview: UIImage = {
        for point in track.record {
            // 어떤 비용이 많이 드는 계산 수행.
        }
        return UIImage(/* ... */)
    }()
}

  1. 속성이 처음 액세스될 때, 클로저가 실행되고(괄호에 주목), 그 반환 값이 속성에 저장하는 패턴을 사용하여 다른 Property를 클로저 내부에서 접근하는 방식을 취한다.

distanceFromOrigin를 lazy 계산된 속성으로 저장합니다.

struct Point {
    var x: Double
    var y: Double
    private(set) lazy var distanceFromOrigin: Double = (x*x + y*y).squareRoot()

    init(x: Double, y: Double) {
        self.x = x
        self.y = y
    }
}

Point를 생성하면 distanceFromOrigin 속성에 액세스하여 값이 계산되고 재사용을 위해 저장됩니다. 그러나 x 값을 변경하면 distanceFromOrigin에는 반영되지 않습니다.

var point = Point(x: 3, y: 4)
point.distanceFromOrigin // 5.0
point.x += 10
point.distanceFromOrigin // 5.0

lazy 속성에 액세스하는 것은 속성의 초기 값이 첫 액세스시에 설정되기 때문에 변이(mutation) 작업입니다.

Point 인스턴스를 참조하는 변수의 값이 변경되는 경우에는 var를 써야한다.(아래의 경우 var)

let immutablePoint = Point(x: 3, y: 4)
immutablePoint.distanceFromOrigin

Question.

  1. Lazy keyword를 enum에는 사용하지 못하는 이유?

  2. Property Observer가 컴파일 타임 특성인 이유?

Last updated