태그: ==, ===, Equatable, lazy, LazySequence, mutating, ObjectIdentifier, Operator
- This topic has 1개 답변, 2명 참여, and was last updated 4 years, 3 months 전에 by 야곰.
-
글쓴이글
-
-
TTOzzi참가자
- 글작성 : 10
- 답글작성 : 13
iOS 커뮤니티들의 기존 질문, 답변에 제가 찾은 정보와 찾으면서 참고했던 비슷한 질문들을 같이 정리해보았습니다.
잘못된 정보 수정이나 추가하고 싶은 답변이 있으시다면 댓글을 달아주시거나 Question-Archive 에 issue 나 PR 로 알려주시면 수정하겠습니다!Q.
== 와 === 의 차이가 무엇인가요?
==
와===
은 어떤 차이가 있나요?A.
- `==` 연산자는 인스턴스의 값이 같은지 확인하고, `===` 연산자는 참조하고 있는 인스턴스가 같은지 확인합니다.
- 참조 타입인 클래스는 여러 상수와 변수가 동일한 인스턴스를 참조할 수 있습니다. 클래스의 인스턴스는 힙(Heap) 영역에 할당되고 그 인스턴스를 참조하는 상수, 변수는 스택(Stack) 영역에 할당됩니다. `==` 연산자는 인스턴스가 서로 같은지를 의미하며, 두 비교 대상의 참조가 같을 수도 있지만, 꼭 같은 참조를 가져야만 하지는 않습니다. `===` 연산자는 참조 타입만 비교할 수 있지만, `==` 연산자는 값 타입도 비교할 수 있습니다. String, Int 와 같은 대부분의 Swift 표준 라이브러리 기본 타입들은 Equatable 프로토콜을 채택하고 있으나, 사용자 정의 클래스, 구조체는 Equatable 프로토콜을 채택하고 서로 같음을 판단하는 기준을 `static func == (lhs:, rhs:) -> Bool` 메소드를 구현함으로써 제공해주어야 합니다.
class Person: Equatable { let id: Int let name: String init(id: Int, name: String) { self.id = id self.name = name } static func == (lhs: Person, rhs: Person) -> Bool { return lhs.id == rhs.id } }
id 와 name 을 프로퍼티로 가지는 Person 클래스입니다. Equatable 프로토콜을 채택하여 id 프로퍼티의 값의 일치 여부로 서로 같음을 판단하도록 구현해주었습니다.
let person1 = Person(id: 5, name: "Bob") let person2 = Person(id: 5, name: "TTOzzi") person1 == person2 // return true
name 프로퍼티의 값이 다르더라도 id 값만으로 비교하므로 true 를 반환하는 것을 확인할 수 있습니다.
- `==` 와 달리 `===` 는 전역 함수로 이미 구현되어있습니다.
@inlinable public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return ObjectIdentifier(l) == ObjectIdentifier(r) case (nil, nil): return true default: return false } }
`===` 연산자의 내부 구현입니다. AnyObject 를 인자로 받도록 구현하여 비교 대상을 클래스 타입으로 제한합니다. 각 비교 대상으로 클래스 인스턴스의 고유한 ID 값인 ObjectIdentifier 를 생성하여 그 둘을 비교하도록 구현되어있습니다.
let person1 = Person(id: 5, name: "Bob") let person2 = Person(id: 5, name: "Bob") let person3 = person1 person1 === person2 // return false person1 === person3 // return true
person1 과 person2 의 비교에서 프로퍼티가 완전히 일치함에도 false 를 반환하는 것을 확인할 수 있습니다. 그에 반해 person3 는 person1 과 같은 인스턴스를 참조하므로 true 를 반환합니다.
- Swift 에서 값의 일치 여부를 판단하는 연산자의 구현에 대해 좀 더 알고 싶다면 Equatable.swift 를 참고해보세요.
참고할 만한 비슷한 질문, 자료
Q.
mutating 이 무엇인가요?
메소드 앞의 mutating 키워드의 의미가 무엇인가요?
A.
- 기본적으로 값 타입인 구조체와 열거형의 프로퍼티는 인스턴스 메소드 내에서 수정할 수 없습니다. 하지만 특정한 메소드 내에서 프로퍼티의 값을 변경해야 할 때가 있다면 메소드 앞에 mutating 키워드를 붙여 내부 프로퍼티를 수정하는 메소드를 만들 수 있습니다.
struct Point { var x = 0.0 var y = 0.0 func moveBy(x deltaX: Double, y deltaY: Double) { // 'self' is immutable 에러 발생 x += deltaX y += deltaY } }
x, y 좌표를 가지는 Point 구조체를 만들어 주었습니다. moveBy(x:y:) 메소드에서 Point 구조체 내부의 x, y 프로퍼티의 값을 수정하게 되면 에러가 발생합니다.
struct Point { var x = 0.0 var y = 0.0 mutating func moveBy(x deltaX: Double, y deltaY: Double) { x += deltaX y += deltaY } }
이럴 때, mutating 키워드를 메소드 앞에 붙여 메소드가 구조체의 값을 변경할 것임을 명시해야 합니다.
struct Point { var x = 0.0 var y = 0.0 mutating func moveBy(x deltaX: Double, y deltaY: Double) { self = Point(x: x + deltaX, y: y + deltaY) } }
self 에 새로운 인스턴스를 만들어 할당할 수도 있습니다. mutating 메소드로 내부 프로퍼티의 값을 변경하면 프로퍼티만 변경되는 것이 아니라, 프로퍼티가 변경된 완전히 새로운 인스턴스를 만들어 기존 인스턴스를 대체하므로 각각의 프로퍼티를 수정하는 것과 self 에 새로운 인스턴스를 만들어 할당하는 것은 완벽하게 똑같이 작동합니다.
let somePoint = Point() // Cannot use mutating member on immutable value 에러 발생 somePoint.moveBy(x: 1, y: 2)
같은 맥락으로 mutating 메소드를 호출한다는 것은 기존의 인스턴스를 새로운 인스턴스로 대체한다는 것을 의미하므로 구조체가 상수로 선언되어 있다면 mutating 메소드를 호출할 수 없습니다.
- 구조체와 같이 열거형도 똑같이 동작하며 다음과 같이 활용할 수 있습니다.
enum TriStateSwitch { case off, low, high mutating func next() { switch self { case .off: self = .low case .low: self = .high case .high: self = .off } } } var ovenLight = TriStateSwitch.low ovenLight.next() // high ovenLight.next() // off
참고할 만한 비슷한 질문, 자료
Q.
lazy 가 무엇인가요?
Swift 에서 lazy 키워드의 의미가 무엇인가요?
A.
- lazy 키워드로 변수를 선언하여 해당 변수가 처음 요청될 때 계산되는 변수를 만들 수 있습니다.
class Properties { var property: Int = Properties.someExpensiveFunction("property") lazy var lazyProperty: Int = Properties.someExpensiveFunction("lazyProperty") static func someExpensiveFunction(_ name: String) -> Int { // 복잡한 로직... print("calculating \(name)...") return 0 } }
복잡한 계산 로직을 갖고, 많은 리소스를 소모하는 메소드 someExpensiveFunction 의 반환 값을 프로퍼티로 가지는 Properties 클래스입니다.
let properties = Properties()
생성자로 인스턴스를 생성하면, property 만 생성되는 것을 확인할 수 있습니다.
let properties = Properties() print(properties.lazyProperty)
lazy 로 선언한 프로퍼티는 프로퍼티에 접근할 때 생성됩니다. 이처럼 프로퍼티의 초깃값을 계산하는 데에 많은 리소스를 소모하는 경우, 인스턴스를 생성할 때 계산하지 않고 프로퍼티가 필요할 때 계산을 수행하도록 구현하기 위해 사용합니다.
- 추가로 인스턴스의 초기화가 완료될 때까지 값을 알 수 없는 외부 요인에 따라 프로퍼티의 초깃값이 달라질 때도 유용하게 사용합니다.
- lazy 프로퍼티는 처음 접근할 때만 계산되고, 그 후엔 값을 저장합니다. 하지만 프로퍼티가 초기화되지 않은 상태에서 여러 스레드에서 동시에 프로퍼티에 접근하는 경우, 프로퍼티를 초기화하기 위한 계산이 여러 번 이루어질 수 있습니다.
let properties = Properties() for _ in 1...10 { DispatchQueue.global().async { print(properties.lazyProperty) } }
여러 개의 백그라운드 스레드에서 동시에 lazyProperty 에 접근하는 코드입니다.
lazyProperty 의 초기화가 여러번 이루어진 것을 확인할 수 있습니다. 이런 경우, 중간에 값의 변경이 이루어져 예상치 못한 결과를 초래할 수 있으므로 주의해야 합니다.
- lazy 프로퍼티와 비슷하게 동작하는 LazySequence 도 존재합니다. 배열, 문자열 등 Sequence 프로토콜을 채택한 타입이라면 기본적으로 lazy 에 구현되어 있습니다. LazySequence 는 map, filter 와 같은 고차함수를 사용했을 때, 시퀀스 전체를 계산하지 않고, 접근한 인덱스의 값을 가져오는 데 필요한 계산만 수행합니다.
func double(_ number: Int) ->; Int { print("calculating \(number)...") return number * 2 }
인자로 받은 정수에 2를 곱해 반환하는 메소드입니다.
let doubled = [1, 2, 3].map { double($0) }
일반적인 배열에 map 을 사용하면 계산된 배열을 doubled 에 저장할 때 배열의 모든 값을 계산합니다.
let lazyDoubled = [1, 2, 3].lazy.map { double($0) } print(lazyDoubled[1])
배열에 구현되어있는 LazySequence 에 map 을 사용하였습니다. 접근한 값을 가져오는 데 필요한 계산만 수행하는 것을 확인할 수 있습니다. LazySequence 는 lazy 프로퍼티와는 달리 값을 저장하지 않고 매번 계산합니다. 이를 인지하고, 상황에 맞게 적절하게 사용하면 불필요한 계산을 피하고 성능을 향상시킬수 있을 것입니다. 좀 더 자세한 정보와 활용법은 LazySequence.swift 를 참고하세요.
참고할 만한 비슷한 질문, 자료
- Swift: Properties
- Lazy property’s memory management
- What are lazy variables?
- Why and when to use lazy with Array in Swift?
- 이 게시글은 야곰에 의해 4 years, 3 months 전에 수정됐습니다. 이유: 시스템 오류 수정
2020-08-31 오전 1:51 #28923 -
야곰키 마스터
- 글작성 : 37
- 답글작성 : 579
멋진 글 고맙습니다!!ㅎㅎ
mutating
에 관해서는 야곰닷넷의 강좌 스위프트 프로그래밍 – 구조체에서도 살펴볼 수 있습니다 😀2020-08-31 오후 2:15 #28951
-
-
글쓴이글
- 답변은 로그인 후 가능합니다.