Swift 5.1
목표 = 사용 시점의 명확성(Clarity at the point of use)
새로운 사항 - Swift 전용 API의 유형에 접두사를 사용하지 않을 것!
타입 생성의 기본 개념
Entity를 중심
Scene에 나타내는 객체를 표현
RealityKit 엔진에 위치
개체의 모양 변경이나 이동 시 해당 엔진에서 개체를 직접 조작
=> 정체성이 있다
=> 참조 유형의 완벽한 사용
상자를 또 만들고 색상을 변경하면 이전 상자도 적용이 되어야 할까?
-> RealityKit은 값 타입으로 구현
struct Material {
public var roughness: Float
public var color: Color
public var texture: Texture
}
class Texture { ... }
값 타입은 내용을 복사 -> 내용이 참조라면?
-> 참조 사본을 복사하게 되는 것으로 texture를 공유하게 됨
-> Texture가 불변(let)이라면 괜찮지만 아니라면 참조도 값도 아니게 동작함
➡️ 구조체와 같은 값 타입이라고 무조건 값 처럼만 동작하는 것은 아니다
값 처럼 동작하고 싶다면 노출하는 참조 중 변경 가능한 것이 있는지 확인 ❕
Texture를 비공개(private)로 전환
연산 프로퍼티로 setter 내부에서 Texture 개체의 복사본을 만듦
객체에서 원하는 프로퍼티를 연산 프로퍼티로 노출
getter를 통해 setter에서 개체가 고유하게 참조되는지 확인하기
고유하지 않다면 해당 시점에서 Texture의 복사본을 만들기
=> 고유성 검사를 통해 참조 타입에서 원하는 프로퍼티를 노출하면서 복사 값 체계를 구현
값 타입은 Objective-C에도 있었지만 Swift에서는 enum과 같은 값 타입에도 프로토콜을 적용할 수 있다는 것이 다름
또한 제네릭을 사용하면 다양한 타입에서 코드를 공유할 수 있음
그렇다고 다짜고짜 프로토콜부터 만들라는 것은 아님
✅ 먼저 구체적인 타입을 사용해 사용 사례를 탐색하고 다른 타입에서도 같은 기능을 반복할 때 공유하고 싶은 코드를 찾고 제네릭을 사용해 공유 코드 제거
✅ 새로운 프로토콜을 만들기 보다 이미 존재하는 프로토콜을 확장하기
✅ 프로토콜 보다는 제네릭으로 만들기
기하학 API 만들기 (벡터 관련 작업을 하고 싶다)
SIMD
모든 요소에 대해 한 번에 효율적인 계산을 수행할 수 있는 튜플 프로토콜.
기하학 계산에 훌륭
프로토콜에 정의하면 모든 작업에 대한 기본 구현을 할 수 있음
새로운 기능을 얻고자 하는 경우 각 타입에 해당 프로토콜 적합성을 제공
일반적인 3단계 프로세스 프로토콜을 정의 -> 기본 구현을 제공 -> 여러 유형에 적합성을 추가
는 지루하다!
프로토콜이 정말 필요했을까?
새로운 사용자 정의 구현이 없다 = 프로토콜이 유용하지 않다
새 프로토콜에 기본 구현하는 대신 해당 제약 조건에 직접 확장해서 기본 구현하기
struct GeometricVector<Storage: SIMD> where Storage.Scalar: FloatingPoint {
typealias Scalar = Storage.Scalar
var value: Storage
init(_ value: Storage) { self.value = value }
}
프로토콜 없이 확장 기반으로 작업 시 감시 테이블이 없어 컴파일러가 더 빠르게 처리할 수 있다
protocol GeometricVector: SIMD
프로토콜 생성 = GeometricVector를 정의하고 SIMD를 정제한다
-> 이게 is-a 관계인가? GeometricVector이 SIMD 타입이다 라고 할 수 있나?
GeometricVector와 SIMD는 구현과 맥락적으로 다른 부분이 있음
(예를 들어 곱하기가 안 된다던지 모든 SIMD 타입에서 사용할 수 없음)
사용하기 쉬운 API 설계 시 has-a 관계로 구현하는 것이 좋음 => 구조체 안에 SIMD를 래핑하기
x, y, z에 대한 연산 프로퍼티 사용 넘 복잡하다
~~> Key path member lookup
하나의 타입에서 여러 연산 프로퍼티를 한 번에 노출하는 단일 subscript를 작성
@dynamicMemberLookup
표시프로퍼티 전달 외에도 유용하게 쓰일 수 있음
이전 예제 (COW 값 시맨틱을 사용해 Texture에서 특정 프로퍼티를 노출한 예제)에서 하나의 프로퍼티에서 작동하고 있음
-> 다른 프로퍼티에도 적용하고 싶다면 매번 같은 코드를 써야함 😇
모든 프로퍼티를 노출하고 싶다면? => Key path member lookup
Property wrappers는 Swift 5.1의 새로운 기능
Property wrappers의 기본 아이디어 = 연산 프로퍼티에서 코드를 효과적으로 재사용하자!
image
프로퍼티를 노출했지만 모든 사용자, 클라이언트가 값을 쓰는걸 원하지 않음
imageStorage
라는 내부 프로퍼티로 또 존재public lazy var image: UIImage = loadDefaultImage()
이런 걸 간단하게 하고 라이브러리로 사용하고 싶다 => Property wrapper로 만들자
public struct MyType {
@LateInitialized public var text: String
}
@LateInitialized
는 이전 예시와 같은 방식으로 구현되어 있음
@propertyWrapper
로 표시해 속성 래퍼임을 알림value
프로퍼티 갖기컴파일러는 개별 프로퍼티로 변환
속성 래퍼를 적용하여 사용하면 컴파일러는 해당 코드를 두 개의 개별 속성으로 변환
1) $ 접두사로 백업 저장소 프로퍼티 = 속성 래퍼 타입의 인스턴스
2) 연산 프로퍼티로 변환된 저장소 프로퍼티
참조 시맨틱과 mutable한 상태를 다룰 때 방어적인 복사를 수행하게 됨
수동으로 할 수 있지만 속성 래퍼로 구축하면 편리
public struct MyType {
@DefensiveCopying public var path: UIBezierPath = UIBezierPath()
}
최적화를 위해 DefensiveCopying를 확장할 수 있음
추가 사본을 피할 수 있도록 Copying 초기화 없이 $path에 할당하기
public struct MyType {
@DefensiveCopying(withoutCopying: UIBezierPath())
public var path: UIBezierPath
}
상용구를 더 없애기 위해 위와 같이 표현도 가능
프로퍼티 래퍼는 강력해서 데이터 저장 방법, 데이터 접근 방법을 결정할 수 있음
@UserDefault
- UserDefault를 사용하기 위한 논리 지정@ThreadSpecific
- 스레드 특화@Option
- 명령줄 옵션을 매우 간결하게 선언SwiftUI 전체에서 속성 래퍼를 광범위하게 사용해 View의 데이터 종속성을 설명
@State - 뷰의 로컬 상태를 도입
@Binding - 다른 곳에 있음을 알림
선언한 위치에서 이 데이터의 위치와 접근 방법에 대한 정책을 명시
당신은 데이터 위치를 신경쓰지 않아도 시스템이 이를 관리
struct SlideViewer: View {
@State private var isEditing = false
@Binding var slide: Slide
var body: some View {
VStack {
Text("Slide #\(slide.number)")
if isEditing {
TextField($slide.title)
}
//..
}
}
}
텍스트필드에서 slide.title을 편집할 수 있도록 전달하고 싶어!
$slide.title
-> 이 형태 = 백업 저장소 프로퍼티slide
프로퍼티에 Binding 래퍼를 적용했으므로 컴파일러가 합성해줌그건 Binding이고 title을 가지진 않음. title은 내 데이터 모델(Slide)의 일부임
-> KeyPath Member Lookup
접근 방식은 어차피 프레임워크에 의해 처리되니 우리에겐 중요하지 않음
Binding은 KeyPath Member Lookup도 지원
$sllide
하면 해당 바인딩 인스턴스를 가져옴 -> Binding<Slide>$slide.title
-> Binding<String>➡️ 바인딩을 통해 클래스 참조를 전달하려면 접두사 $를 붙이면 됨
오늘 한 이야기