<Swift> Chapt 10. 프로퍼티와 메서드

쑤야·2022년 9월 4일
0

Swift

목록 보기
3/3

1. 프로퍼티

# 저장 프로퍼티

  • 클래스 또는 인스턴스와 연관된 값을 저장하는 가장 단순한 개념의 프로퍼티
  • 저장 프로퍼티를 정의할 때 프로퍼티 기본 값과 초깃값을 지정해줄 수 있음

# 지연 저장 프로퍼티

  • 필요할 때 값이 할당되는 프로퍼티
  • 호출이 있어야 값이 초기화됨
  • lazy 키워드와 var 키워드를 사용
    • 상수는 인스턴스가 완전히 생성되기 전에 초기화해야 하므로 필요할 때 값을 할당하는 지연 저장 프로퍼티와는 맞지 않음
  • 지연 저장 프로퍼티를 잘 사용하면 불 필요한 성능 저하나 공간 낭비를 줄일 수 있음
  • 다중 스레드 환경에서 단 한 번만 초기화된다는 보장이 없는 것이 단점
    • 생성되지 않은 지연 저장 프로퍼티에 많은 스레드가 비슷한 시점에 접근한다면, 여러 번 초기화될 수 있음

# 연산 프로퍼티

  • 실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티
  • 접근자 및 설정자 역할도 수행
    • 접근자 : 인스턴스 내/외부의 값을 연산하여 적절한 값을 돌려주는 역할
    • 설정자 : 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 역할

메서드가 아닌 연산 프로퍼티를 사용하는 이유?

  • 인스턴스 외부에서 메서드를 통해 인스턴스 내부 값에 접근하려면 설정자, 접근자 메서드 2개를 구현해야 함
  • 이를 감수하고 메서드로 구현한다고 하더라도 두 메서드가 분산 구현되어 코드의 가독성이 나빠질 위험이 있음
  • 다만 연산 프로퍼티는 읽기 전용 상태로 구현하기는 쉽지만, 쓰기 전용 상태로 구현할 수 없다는 단점이 존재
    • 읽기 전용으로 구현하고 싶으면 get 메서드만 사용하면 됨
  1. 설정자, 접근자를 메서드로 분산 구현
struct CoordinatePoint{
	var x: Int
	var y: Int

	func oppositePoint() -> Self{
			return CoordinatePoint(x: -x, y: -y)
	}

	mutating func setOppositePoint(_ opposite: CoordinatePoint){
		x = -opposite.x
		y = -opposite.y
	}
}
  1. 연산 프로퍼티로 구현
struct CoordinatePoint{
	var x: Int
	var y: Int

	var oppositePoint: CoordinatePoint{
			**get** {
				return CoordinatePoint(x: -x, y: -y)
			}

			**set(opposite)** {
				x = -opposite.x 
				y = -opposite.y
			}
	}
}
  • 하나의 프로퍼티에 접근자와 설정자가 모두 모여있고, 해당 프로퍼티가 어떤 역할을 하는지 좀 더 명확하게 표현 가능
  • 관용적인 표현인 newValue로 매개변수를 대신할 수 있음
    • 이 경우에는 매개변수를 따로 표기하면 안됨

      	var oppositePoint: CoordinatePoint{
      			**get** {
      				return CoordinatePoint(x: -x, y: -y)
      			}
      
      			**set** {
      				x = -newValue.x //newValue.x로도 작성 가능
      				y = -newValue.y
      			}
      	}
      }

# 프로퍼티 감시자

  • 프로퍼티의 값이 새로 할당될 때마다 호출되며, 변경되는 값이 현재의 값과 같더라도 호출됨

willSet

  • 프로퍼티의 값이 변경되기 직전에 호출하는 메서드
  • willSet 메서드에 전달되는 전달인자는 프로퍼티가 변경될 값
  • 매개변수의 이름을 따로 지정하지 않을 경우 newValue 이름이 자동 지정됨

didSet

  • 프로퍼티의 값이 변경된 직후에 호출하는 메서드

  • didSet에 전달되는 전달인자는 프로퍼티가 변경되기 전의 값

  • 매개변수의 이름을 따로 지정하지 않을 경우 oldValue 이름이 자동 지정됨

  • didSet 감시자 코드 블록 내붸서 oldValue 값을 참조하지 않거나 매개변수 목록에 명시저그로 매개변수를 적어주지 않을 경우 didSet 코드 블록은 실행되지 않음

  • 클래스를 상속받았다면 기존의 연산 프로퍼티를 재정의하여 프로퍼티 감시자를 구현할 수도 있음

  • 연산 프로퍼티를 재정의해도 기존의 연산 프로퍼티 기능(접근자, 설정자, get, set)은 동작함

# 전역변수와 지역변수

  • 전역 변수와 지역 변수는 저장 변수라고 할 수 있음
  • 전역 변수 또는 전역 상수는 지연 저장 프로퍼티처럼 처음 접근할 때 최초로 연산이 이루어지기 때문에 lazy 키워드를 사용하여 연산을 늦출 필요가 없음
  • 반대로, 지역 변수 및 지역 상수는 절대로 지연 연산되지 않음

# 타입 프로퍼티

  • static 키워드 사용
  • 각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티
  • 타입 자체에 영향을 미치는 프로퍼티
  • 인스턴스 생성 여부와 상관없이 타입 프로퍼티의 값은 하나
  • 타입의 모든 인스턴스가 공통으로 사용하는 값, 모든 인스턴스에서 공용으로 접근하고 값을 변경할 수 있는 변수 등을 정의할 때 유용
  1. 저장 타입 프로퍼티
    • 변수 또는 상수로 선언 가능
    • 초깃값 반드시 설정해야 함
    • 지연 연산
      • 지연 연산이지만 lazy 키워드를 사용하지는 않음
    • 지연 저장 프로퍼티와 다르게 다중 스레드 환경이라고 하더라도 단 한 번만 초기화된다는 보장을 받음
  2. 연산 타입 프로퍼티
    • 변수로만 선언 가능
//저장 타입 프로퍼티
static var typeProperty: Int = 0

//연산 타입 프로퍼티
static var typeComputedProperty: Int {
	get .. 

	set ..
}

# 키 경로

  • 프로퍼티의 값을 바로 꺼내오는 것이 아니라 어떤 프로퍼티의 위치만 참조할 수 있도록 함
  • 키 경로를 사용하여 간접적으로 특정 타입의 어떤 프로퍼티 값을 가리켜야 할지 미리 지정해두고 사용할 수 있음
  • 키 경로를 잘 활용하면 프로토콜과 마찬가지로 타입 간의 의존성을 낮추는데 많은 도움을 줌

키 경로 타입

  • WritableKeyPath<Root, Value>

    • 값 타입에 키 경로 타입으로 읽고 쓸수 있는 경우에 적용됨
  • ReferenceWritableKeyPath<Root,Value>

    • 참조 타입, 즉 클래스 타입에 키 경로 타입으로 읽고 쓸 수 있는 경우에 적용됨
  • 키 경로는 역슬래시()와 타입, 마침표(.)로 구성

/타입이름.경로.경로.경로
class Person{
	let name: String
}

struct Stuff{
	var name: String
	var owner: Person
}

let ssooya = Person(name: "ssooya")
let macbook = Stuff(name: "MacBook Pro", owner: ssooya)

let stuffNameKeyPath = \Stuff.name
let ownerkeyPath = \Stuff.owner
let ownerNameKeyPath = ownerkeyPath.appending(path: \.name) //\Stuff.owner.name과 같은 표현

print(macbook**[keyPath: stuffNameKeyPath]**) //MacBook Pro
print(macbook**[keyPath: ownerNameKeyPath]**) //ssooya
  • 자신을 나타내는 키 경로인 \.self를 사용하면 인스턴스 그 자체를 표현하는 키 경로가 됨

  • 컴파일러가 타입을 유추할 수 있는 경우에는 키 경로에서 타입 이름을 생략할 수도 있음

  • 스위프트 5.2버전부터 (SomeType) → Value 타입의 클로저를 키 경로 표현으로 대체하여 사용 가능

let ssooya: Person = Person(name: "ssooya", nickname: "a")
let soyun: Person = Person(name: "soyun", nickname: "b")
let park: Person = Person(name: "park", nickname: "c")

let family: [Person] = [ssooya, soyun, park]
let names: [String] = family**.map(\.name)** //["ssooya", "soyun", "park"]

2. 메서드

# 인스턴스 메서드

  • 자신의 프로퍼티 값을 수정할 때 클래스의 인스턴스 메서드는 크게 신경 쓸 필요가 없지만, 구조체나 열거형 등은 값 타입이므로 메서드 앞에 mutating 키워드를 붙여서 해당 메서드가 인스턴스 내부의 값을 변경한다는 것을 명시해야 함
struct LevelStruct{

	var level: Int = 0

	**mutating** func levelUp(){
		level += 1
	}
}

self 프로퍼티

  • 인스턴스를 명확히 지칭하고 싶을 때 사용

  • 값 타입 인스턴스 자체의 값을 치환할 수도 있음

  • 클래스 인스턴스는 참조 타입이라서 self 프로퍼티에 다른 참조 할당 불가능

  • 구조체나 열거형 등은 self 프로퍼티를 사용하여 자신 자체를 치환할 수 있음

class LevelClass{
	var level: Int = 0
	
	/* 오류 발생
	func reset(){
		self = LevelClass() -> self 프로퍼티 참조 변경 불가능
	}
	*/
}

struct LevelStruct{
	var level: Int = 0
	
	mutating func reset(){
		self = LevelStruct() //self 프로퍼티 변경 가능
	}
}

# 타입 메서드

  • 타입 자체에 호출이 가능한 메서드를 뜻함

  • 메서드 앞에 static 키워드를 사용하여 타입 메서드임을 알릴 수 있음

  • 클래스의 타입 메서드static 키워드와 class 키워드를 사용할 수 있음

    • static으로 정의할 경우, 상속 후 메서드 재정의가 불가능
    • class로 정의할 경우, 상속 후 메서드 재정의가 가능함
  • 타입 메서드의 self 프로퍼티는 타입 그 자체를 가리킴

    • 인스턴스 메서드에서는 self가 인스턴스를 가리킴
  • 타입 프로퍼티를 제어하기 위해 사용할 수 있음

profile
CE 20

0개의 댓글