3부: Swift의 연산자 오버로딩 및 결과 빌더를 사용하여 자동 레이아웃 DSL 작성 ️

여기에서 이 기사를 볼 수도 있습니다.
지난 번에 DSL의 주요 부분을 구축하여 최종 사용자가 제약 조건을 산술 방식으로 표현할 수 있도록 했습니다.
이 기사에서는 이에 대해 다음을 포함하도록 확장합니다.
- 결합된 앵커 — 크기, 중심, horizontalEdges, verticalEdges
- 삽입 - 사용자가 결합된 가장자리 앵커에 삽입을 설정할 수 있습니다.
- Result Builder는 다양한 앵커의 출력을 처리하여 제약 조건 일괄 처리 및 활성화를 쉽게 허용합니다.
먼저 1부에서 정의한 LayoutAnchor 프로토콜을 준수하는 한 쌍의 앵커를 보유할 유형을 만듭니다.
여기서는 앵커 배열을 유지하도록 LayoutBlock을 수정하는 것을 고려했습니다. 그런 다음 2개의 이러한 블록에서 제약 조건을 생성할 때 앵커를 함께 압축하고 반복하여 앵커를 서로 제약하고 관련 상수/승수를 전달할 수 있습니다.
2가지 단점이 있습니다.
- 기본 앵커가 있는 단일 표현식도 제약 조건 배열을 반환합니다. 이는 사용자 관점에서 DSL을 복잡하게 만듭니다.
- 사용자는 단일 앵커가 있는 복합 앵커(2개 또는 4개 앵커 포함)를 사용하려고 시도할 수 있습니다. 추가 앵커를 무시하여 이를 처리할 수 있습니다. 컴파일러는 경고를 생성하지 않습니다. 그러나 이러한 작업과 그에 따른 제약 조건은 의미가 없습니다. 이것은 최종 사용자 코드에 실망스러운 버그를 도입할 가능성이 있습니다. 우리가 피하고 싶은 것입니다!!
11단계: LayoutAnchorPair 프로토콜을 확장합니다.
이 확장은 이전에 정의된 LayoutAnchor 프로토콜 확장과 동일한 목적을 수행합니다. 기본 유형의 메서드를 호출하고 결과 제약 조건을 반환하는 래퍼 역할을 합니다.
여기서 주요 차이점은 각 메서드가 2개의 LayoutAnchor 유형의 제약 조건을 결합하는 제약 조건 배열을 반환한다는 것입니다.
LayoutAnchorPair에 전달하는 앵커는 LayoutAnchor 유형으로 제한되므로 기본 구현을 쉽게 제공할 수 있습니다.
또한 상수 대신 이러한 메서드는 각 제약 조건에 서로 다른 상수를 제공하는 기능을 제공하는 EdgeInsetPair를 사용합니다.
각 메서드는 constant1을 매핑하여 anchor1과 constant2를 anchor2에 제한합니다.
13단계: 구체적인 LayoutAnchor 유형을 만듭니다.
Part 1 & 2에서는 기본 NSLayoutAnchors가 LayoutAnchor 프로토콜을 준수하도록 만들었기 때문에 구체적인 LayoutAnchor 유형을 생성할 필요가 없었습니다. 하지만 여기에서는 AnchorPair 프로토콜을 준수하는 자체 앵커를 제공해야 합니다.
이전에 사용된 typealiases에 대한 알림:
EdgeInsetPair 프로토콜을 만족하는 중첩 유형을 정의합니다. 이는 AnchorPair 관련 유형 요구 사항인 삽입을 충족합니다. 이 프로토콜을 준수하는 구체적인 유형은 삽입을 설정하기 위해 작업 오버로딩 중에 사용됩니다.
계산된 속성을 사용하여 LayoutAnchorPair 및 EdgeInsetPair 프로토콜을 준수합니다. 이러한 계산된 속성은 LayoutAnchorPair 및 EdgeInsetPair의 내부 속성을 반환합니다.
여기에서 삽입 유형이 제공하는 상수가 이 앵커 쌍에 정의된 앵커와 일치하는지 확인하는 것이 중요합니다. 특히 마지막 단계에서 정의된 확장의 컨텍스트 에서 constant1은 anchor1을 제한하는 데 사용됩니다 .
이 "일반" 프로토콜을 사용하면 모든 앵커 쌍에 유효한 하나의 프로토콜 확장을 정의할 수 있습니다. 위에서 논의한 규칙을 따른다면. 동시에 확장 외부에서 더 의미 있는 앵커별 레이블(하단/상단)을 사용할 수 있습니다. 예를 들어 연산자 오버로드를 정의할 때입니다.
대체 솔루션은 모든 유형에 대해 별도의 확장을 포함하는 것입니다. 이는 앵커 수가 제한되어 있기 때문에 그리 나쁘지 않습니다. 그러나 나는 여기서 게을러서 추상적인 해결책을 만들려고 노력했습니다. 어느 쪽이든 이것은 라이브러리 내부에 있는 구현 세부 사항이며 향후 변경 없이 변경할 수 있습니다.
다른 디자인이 최적일 것 같다면 댓글을 남겨주세요.
추가 아키텍처 결정 사항:
- 인셋은 앵커 외부에서 초기화되고 사용되므로 별도의 유형이어야 합니다.
- 중첩된 유형은 네임스페이스를 보호하고 깔끔하게 유지하는 동시에 Inset 유형 구현이 특정 PairAnchor 구현에 의존한다는 사실을 강조하는 데 사용됩니다.
참고: 삽입을 추가하는 것은 식에 상수를 추가하는 것과 다릅니다.
EdgePair 인터페이스의 일부로 반환하기 전에 상단 상수에 빼기 연산자를 추가한 것을 눈치 채셨을 것입니다. 마찬가지로 XAxisAnchorPair 구현은 후행 앵커에 마이너스를 추가합니다. 상수를 뒤집는다는 것은 인셋이 같은 방향으로 각 가장자리를 이동하는 대신 인셋으로 작동함을 의미합니다.

왼쪽 이미지에서 파란색 보기의 모든 가장자리 앵커는 빨간색 보기에 40을 더한 값과 동일하게 설정됩니다. 결과적으로 파란색 보기의 크기는 같지만 두 축을 따라 40씩 이동합니다. 이는 제약 조건 측면에서 의미가 있지만 그 자체로는 일반적인 작업이 아닙니다.
뷰 주위에 삽입을 설정하거나 뷰 주위에 패딩을 추가하는 것이 훨씬 더 일반적입니다. 따라서 결합된 행위자를 위한 API를 제공하는 맥락에서 더 의미가 있습니다.
오른쪽 이미지에서 이 DSL을 사용하여 파란색 보기를 빨간색 보기에 40을 더한 것과 같도록 설정했습니다. 이것은 위에서 설명한 상수의 반전으로 달성됩니다.
14단계: View & LayoutGuide를 확장하여 복합 앵커를 초기화합니다.
이전과 마찬가지로 호출 시 LayoutBlocks 를 초기화하는 계산된 속성으로 View 및 LayoutGuide 유형을 확장 합니다.
15단계: 가장자리 삽입이 있는 표현식을 허용하도록 +/- 연산자를 오버로드합니다.
수평 가장자리 및 수직 가장자리 앵커의 경우 사용자가 삽입을 지정할 수 있기를 바랍니다. 이를 달성하기 위해 우리 는 이 DSL의 대부분의 사용자에게 이미 친숙한 UIEdgeInsets 유형을 확장합니다.
확장 기능을 사용하면 위/아래 또는 왼쪽/오른쪽 삽입만으로 초기화할 수 있습니다. 나머지는 기본적으로 0입니다.
또한 edgeInsets 를 저장하기 위해 LayoutBlock 에 새 속성을 추가해야 합니다 .
다음으로 입력에 대한 연산자를 오버로드합니다: LayoutBlock with UIEdgeInsets .
사용자가 제공한 UIEdgeInsets 인스턴스를 구체적인 LayoutAnchorPair 의 일부로 정의된 관련 중첩 유형에 매핑 합니다.
UIEdgeInset 유형 의 일부로 사용자가 전달한 추가 또는 잘못된 매개변수는 무시됩니다.
15단계: 비교 연산자를 오버로드하여 구속 관계를 정의합니다.
원칙은 이전과 동일합니다. LayoutBlocks 및 LayoutAnchorPair 입력 에 대한 관계 연산자를 오버로드 합니다.
사용자가 한 쌍의 가장자리 인셋을 제공하면 이를 사용하고, 그렇지 않으면 LayoutBlocks 상수에서 한 쌍의 일반 인셋을 생성합니다. 일반 삽입 구조는 다른 삽입과 달리 측면 중 하나를 부정하지 않는 래퍼입니다.
16단계: 치수 레이아웃AnchorPair
단일 차원 앵커와 마찬가지로 한 쌍의 차원 앵커( widthAnchor 및 heightAnchor ) 는 상수로 제한될 수 있습니다. 따라서 이 사용 사례를 처리하려면 별도의 연산자 오버로드를 제공해야 합니다.
- DSL 사용자가 높이와 너비를 동일한 상수로 고정하여 정사각형을 만들 수 있도록 합니다.
- DSL 사용자가 SizeAnchorPair 를 CGSize 유형으로 고정할 수 있도록 허용합니다. 뷰가 정사각형이 아니기 때문에 대부분의 경우에 더 적합합니다.
17단계: 결과 빌더를 사용하여 [NSLayoutConstraint] 및 NSLayoutConstraint 유형을 처리합니다.
복합 앵커는 흥미로운 문제를 만듭니다. 식에서 이러한 앵커를 사용하면 제약 조건 배열이 생성됩니다. 이것은 DSL의 최종 사용자에게 지저분해질 수 있습니다.
우리는 상용구 코드 없이 이러한 제약 조건을 함께 일괄 처리하고 개별적으로 또는 별도의 일괄 처리가 아닌 한 번에 효과적으로 활성화하는 방법을 제공하고자 합니다.
Swift 5.4에 도입된 결과 빌더(함수 빌더라고도 함)를 사용하면 일련의 구성 요소에서 암시적으로 '빌드 블록'을 사용하여 결과를 빌드할 수 있습니다. 실제로 이들은 Swift UI의 기본 구성 요소입니다.
이 DSL에서 최종 결과는 NSLayoutConstraint 객체의 배열입니다.
결과 빌더가 개별 제약 조건과 제약 조건 배열을 하나의 배열로 해결할 수 있는 빌드 기능을 제공합니다. 이 모든 논리는 DSL의 최종 사용자에게 숨겨져 있습니다.
이러한 기능의 대부분은 swift-evolution 결과 빌더 제안 예제 에서 직접 복사 했습니다. 이 DSL에서 제대로 작동하는지 확인하기 위해 단위 테스트를 추가했습니다.
이 모든 결과를 넣으면 다음과 같습니다.
결과 빌더를 사용하면 클로저 내에 추가 제어 흐름을 포함할 수 있습니다. 이는 어레이로는 불가능합니다.
읽어주셔서 감사합니다! 이 기사를 작성하는 데 며칠이 걸렸습니다. 따라서 새로운 것을 배웠다면 이 저장소에 ⭐를 표시해 주시면 감사하겠습니다!
나에게 조언이 있다면 부끄러워하지 말고 경험을 공유하세요!
이 DSL의 최종 버전에는 한 쌍의 AnchorPair 유형으로 구성된 4차원 앵커가 포함되어 있습니다.
여기에서 모든 코드를 찾을 수 있습니다.