3. Variables and Simple Types (2)

Seoyoung Lee·2022년 7월 30일
0
post-thumbnail
post-custom-banner

Computed Variable Initialization

변수의 초깃값을 계산하기 위해 여러 줄의 코드를 작성하고 실행해야 할 때가 있다. 이를 구현하는 가장 간단하고 빠른 방법은 define-and-call 익명 함수를 사용하는 것이다.

class RootViewController: UITableViewController {
	let cellBackgroundImage : UIImage = {
		return imageOfSize(CGSize(width: 320, height: 44)) {
			// ...
		}
	}()
	// ...
}

위 예시처럼 인스턴스 프로퍼티를 초기화할 때도 이런 방법을 사용할 수 있다.

그럼 define-and-call을 사용해서 초기화하는 대신 인스턴스 메소드를 만들고 이를 호출해서 인스턴스 프로퍼티를 호출하는 방법은 어떨까?

class RootViewController: UITableViewController {
	let cellBackgroundImage : UIImage = self.makeTheImage() // compile error
	func makeTheImage() -> UIImage {
		return imageOfSize(CGSize(width: 320, height: 44)) {
			// ...
		}
	}
}

어림도 없다. 컴파일 에러가 발생한다.

인스턴스 프로퍼티가 초기화되는 시점에는 인스턴스가 아직 존재하지 않는다. 따라서 프로퍼티를 선언할 때 self 를 참조할 수 없다. define-and-call 익명 함수를 사용하는 건 가능하지만 여기서도 self 를 참조할 수는 없다.

Computed Variables

우리가 지금까지 봐왔던 변수들은 모두 stored variable이다. 스위프트에서 변수는 완전히 다르게 동작할 수 있다. 다시 말해 연산을 할 수 있다. 이는 변수가 값이 아니라 함수를 가지고 있음을 의미한다.

setter 라는 함수는 변수가 할당될 때 호출되고 getter 라는 함수는 변수가 참조될 때 호출된다.

var now: String { // 1
	get { // 2
		return Date().description // 3
	}
	set { // 4
		print(newValue) // 5
	}
}
  1. 반드시 let 이 아닌 var 로 선언되어야 한다. 또한 자료형도 명시해야 한다.
  2. getter 함수의 이름은 get 이다.
  3. getter 함수는 반드시 변수의 타입과 같은 타입의 값을 반환해야 한다. getter 함수의 코드가 한 줄이라면 return 키워드를 생략해도 된다.
  4. setter 함수의 이름은 set 이다.
  5. setter 함수는 한 개의 파라미터를 가진 함수처럼 동작한다. 기본적으로 이 파라미터는 newValue 라는 이름으로 setter 함수에 전달된다.
now = "Howdy"
print(now)

computed variable을 사용하는 법은 다른 변수와 다르지 않다. 다만 변수의 값을 할당하거나 변수를 사용할 때는 setter와 getter 함수가 호출된다.

응용된 문법

  1. set 함수의 파라미터 이름을 바꿀 수 있다.

    set (val) { // ...
  2. setter 함수는 생략이 가능하다. setter 함수를 생략하면 변수는 읽기 전용 변수가 된다. 즉 let 변수와 같아져 값을 할당하려고 하면 컴파일 에러가 발생한다.

  3. getter 함수는 반드시 있어야 한다. 단, setter가 없으면 get 과 중괄호는 생략이 가능하다. return 키워드 역시 상황에 따라 생략이 가능하다.

    var now: String {
    	Date().description
    }

Computed Properties

실제로 computed variable은 인스턴스 프로퍼티에서 많이 사용한다. 연산 프로퍼티가 자주 유용하게 사용되는 경우는 다음과 같다.

Facade for a longer expression

필요할 때마다 값이 즉시 연산되어야 하는 경우에는 읽기 전용 연산 프로퍼티로 만드는 것이 좋다.

Facade for an elaborate calculation

연산 프로퍼티의 getter는 여러 줄의 코드를 캡슐화 할 수 있으며, 메소드를 프로퍼티로 변환해준다.

var authorOfItem: String? {
	guard let authorNodes =
		self.extensionElements(
			withXMLNamespace: "http://www.tidbits.com/dummy",
			elementName: "app_author_name")
		else { return nil }
	guard let authorNode = authorNodes.last as? FPExtensionNode
		else { return nil }
	return authorNode.stringValue
}

위 예시에서 authorOfItem 프로퍼티 안에서 XML를 파싱하고 값을 추출한다. 이를 func authorOfItem() -> String? 메소드로 선언할 수도 있지만 메소드는 과정을 나타내는 반면 연산 프로퍼티는 더 직관적으로 어떤 으로 특징짓는다.

Facade for storage

연산 프로퍼티는 저장 프로퍼티의 앞에 서서 저장 프로퍼티의 문지기 역할을 할 수 있다. 이는 Objective-C의 accessor method와 유사하다.

이는 저장 프로퍼티의 값을 얻거나 변경할 때 추가로 다른 작업을 해야 하는 경우 유용하다.

private var _pp: Int = 0
var pp: Int {
	get {
		self._pp
	}
	set {
		self._pp = max(min(newValue,5),0)
	}
}

연산 인스턴스 프로퍼티의 getter와 setter는 다른 인스턴스 멤버를 참조할 수 있다. 중요한 것은 일반적으로 저장 프로퍼티의 이니셜라이저는 가능하지 못하다는 것이다. 연산 프로퍼티에서는 가능한 이유는 getter와 setter 함수가 인스턴스가 실제로 존재하기 전에는 호출이 되지 않기 때문이다.

Property Wrappers

동일한 로직을 갖는 연산 프로퍼티가 여러 개가 있다면 중복되는 코드가 매우 많아질 것이다. property wrapper 를 사용하면 공통되는 기능을 한 곳에 위치시킬 수 있다.

property wrapper를 선언할 때는 타입 앞에 @propertyWrapper 을 붙여주어야 한다. 또한 wrappedValue 라는 연산 프로퍼티를 반드시 가져야 한다.

@propertyWrapper struct Clamped {
	private var _i: Int = 0
	var wrappedValue: Int {
		get {
			self._i
		}
		set {
			self._i = Swift.max(Swift.min(newValue,5),0)
		}
	}
}

위와 같이 property wrapper를 선언한 후부터는 getter나 setter 없이 위 구조체와 같은 이름의 속성(@Clamped)으로 표시된 연산 프로퍼티를 선언할 수 있다.

@Clamped var p

p 는 연산 프로퍼티이기 때문에 초기화되지 않아도 된다. 또한 getter와 setter 역시 없어도 되며, 타입 역시 명시하지 않아도 된다. Clamped 구조체의 wrappedValue 연산 프로퍼티가 다 알아서 해주기 때문이다.

뒤에서는 실제 Clamped 인스턴스가 생성된다. self.p 에 값을 할당하면 할당된 값은 Clamped 인스턴스 wrappedValue 의 setter를 통과해서 Clamped 인스턴스의 _i 프로퍼티에 저장된다.

self.p 의 값을 가져올 때는 Clamepd 인스턴스의 wrappedValue getter에서 리턴된 값을 가져오는 것이며, 이는 Clamped 인스턴스의 _i 프로퍼티의 값과 같다.

property wrapper를 사용하면 연산 프로퍼티의 패턴을 캡슐화할 수 있으며 같은 동작을 하는 또 다른 @Clamped 프로퍼티를 선언할 수 있다. 또한 이런 패턴들에 이름을 붙임으로써 연산 프로퍼티의 역할을 명시해줄 수도 있다.

*property wrapper 더 알아보기

profile
나의 내일은 파래 🐳
post-custom-banner

0개의 댓글