본문 바로가기

Swift

some

728x90

SwiftUI에서 자주 보이는 친구입니다 ..

 

Overview

struct MyFirstView: View {
  var body: some View {
    Text("Hello world!")
  }
}

이렇게 SwiftUI에서 자주 보이는 some이라는 키워드가 있습니다.

some 키워드는 Swift 5.1에서 새로 나타난 기능인데 무엇인지 알아보도록 하겠습니다. 

 

And the some keyword that we use here is a switch feature that lets swift infer out inter return type automatically. 
(= some 키워드는 리턴 타입을 자동으로 그리고 빠르게 추론할 수 있는 스위치 기능입니다.)

some 키워드는 Swift 5.1의 새로운 기능입니다.
some 키워드는 computed property인 body안에 불투명한 타입이 있음을 나타냅니다.

✔️ 불투명한 타입은 Swift의 중요한 기능입니다.
✔️ 불투명한 타입이라는 것을 나타내는 some 키워드는 computed property 또는 함수의 구체적인 return type을 숨기는 것이 가능합니다.
✔️ 이를 통해 유연하고 간결하며 강력한 Swift 코드를 작성할 수 있게 됩니다. 

 

SwiftUI에서 사용되는 some 키워드를 살펴보겠습니다.

struct MyFirstView: View {
  var body: some View {
    Text("Hello world!")
  }
}

body property의 구현되는 이 타입은 MyFirstView를 사용하는 코드에 노출되지 않고 비공개로 유지됩니다.

 

.. 여기서 불투명한 타입이 무엇일까요?

 


더보기를 통해서 제너릭과 관련된 글을 볼 수 있습니다. 

더보기

Opaque Types & Generics

 

불투명한 타입과 제너릭은 연관이 있습니다. 

불투명한 타입은 보통 reverse generic type이라고 불리기도 합니다.

불투명한 타입을 사용하면 구현에서 구체적인 유형을 결정하게 됩니다.

 

✔️ Generic

func addition<T: Numeric>(a: T, b: T) -> T {
    return a + b
}

let resultA = addition(a: 42, b: 99)
let resultB = addition(a: 3.1415, b: 1.618)

제너릭을 사용하면 placeholder에 타입 및 제약 조건을 정의할 수 있으므로 함수가 강력한 타입을 유지하면서 다른 타입을 허용할 수 있습니다.

 

위의 코드에서 placeholder T를 사용하고 있으며 a, b 파라미터 모두 타입이 T입니다. 또한 함수 역시 T 타입의 값을 리턴하고 있습니다. 그리고 T 타입은 Numeric 프로토콜을 준수해야 합니다.

-> 그 결과, 이 함수는 integer, double 값 등이 들어갈 수 있습니다.

 

placeholder인 T는 말 그대로 자리를 맡아주는 역할입니다. 코드가 컴파일될 때 Swift는 이것을 Int 또는 Double 같은 정확한 타입으로 대체합니다.

✅ 이때, 호출자는 T의 기본 유형을 알고 있기 때문에 placeholder가 투명하다고 할 수 있습니다.

제너릭 타입의 경우에는 함수를 구현하는 내부에는 타입을 숨기다가 이 함수를 사용할 때 어떤 타입이 들어가는지 알 수 있습니다.

 

✔️ Some

protocol Shape {
    func describe() -> String
}

struct Square: Shape {
    func describe() -> String {
        return "I'm a square. My four sides have the same lengths."
    }
}

struct Circle: Shape {
    func describe() -> String {
        return "I'm a circle. I look like a perfectly round apple pie."
    }
}

func makeShape() -> some Shape {
  return Circle()
}

위의 코드를 살펴보면 Shape이라는 프로토콜을 정의했고 이 프로토콜을 준수하고 있는 구조체가 있습니다. 그리고 makeShape()이라는 메서드를 정의했습니다.

 

그리고 makeShape()는 Shape타입을 리턴합니다. 이 때, some 키워드를 사용해서 불투명한 타입을 나타내고 있습니다.

반환되는 구체적인 타입을 결정하는 것은 함수에게 달려 있습니다. 여기서 Circle 타입을 반환하게 됩니다.

 

let shape = makeShape()
print(shape.describe())

// "I'm a circle. I look like a perfectly round apple pie."

위의 코드에서 makeShape() 메서드를 호출하며 describe() 함수를 이용해서 출력하고 있습니다.

 

이러한 불투명한 타입은 type identitiy를 유지하고 있고 프로토콜을 그렇지 않습니다.

1. 불투명한 타입은 항상 하나의 구체적인 유형을 나타냅니다.

2. 프로토콜 타입은 프로토콜을 준수하는 여러 유형을 참조하는 것이 가능합니다.

 

📌 제너릭 타입과 반대로 불투명한 타입은 함수 내부에서는 반환 타입을 정확히 알 수 있지만 밖에서는 반환 값의 타입을 정확히 알지 못하게 숨기는 것입니다. 


예시를 통해서 알아보도록 하겠습니다. ⬇️

protocol Shape {
    func describe() -> String
}

struct Square: Shape {
    func describe() -> String {
        return "I'm a square. My four sides have the same lengths."
    }
}

struct Circle: Shape {
    func describe() -> String {
        return "I'm a circle. I look like a perfectly round apple pie."
    }
}

위와 같이 Shape란 프로토콜에 Shape를 따르는 여러 도형이 있다고 했을 때, 

func makeShape() -> Shape {
  return Circle()
}

이런 식으로 코드가 있다면, 정확히 어떤 도형을 리턴하는지 모릅니다. 이런 경우를 불투명하다고 말합니다. 즉, makeShape()는 불투명한 타입입니다.

 

그러나 Swift는 불투명한 타입을 리턴하는 것을 허용하지 않습니다.

그렇기 때문에 위의 코드를 그대로 작성하면, 아래와 같은 에러가 나옵니다.

 Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements

 

에러를 해결하기 위해서 어떻게 해야 할까요?

func makeShape() -> some Shape {
  return Circle()
}

위와 같이 some을 사용하면 해결됩니다. 

✅ 타입을 미리 지정해서 함수의 내부를 변화시키는 제너릭 타입과는 정 반대로 함수 내부의 코드에 따라서 구체적으로 리턴 타입이 달라지게 됩니다.

 

그런데, 이 키워드를 정확히 어떤 경우에 써야 하는지 감이 잘 안 올 수 있습니다.

간단히 예를 들어보겠습니다.

 

Int형 타입의 배열을 리턴하는 함수를 만들어보겠습니다.

func testFunc() -> Array<Int> {
    return [1, 2, 3]
}

이렇게 함수를 만들 수 있습니다.

 

그리고 이후에 딕셔너리로 수정해야 할 경우, textFunc()를 사용하는 다른 코드들을 수정하는 등의 번거로운 작업이 필요합니다.

이럴 때, some을 써주면 됩니다.

// 요청 사항 전달 전 
func testFunc() -> some Collection {
    return [1, 2, 3]
}

// 요청 사항 전달 후
func testFunc() -> some Collection {
    return ["a": 1, "b": 2, "c": 3]
}

함수 내부의 코드만 수정되었고 함수 밖의 내용은 수정할 필요가 없습니다.

 

이렇게 수정함으로써 testFunc()에 의존하는 다른 코드들을 수정하는 번거로운 작업이 필요 없게 되었고 개발자들은 편하게 작업을 할 수 있습니다.

 

🤔 그렇다면, 왜 SwiftUI에서 some을 쓰는 것일까요?

만약, SwiftUI에서 some을 쓰지 않는다면, 매번 함수 리턴 값을 VStack, HStack, Button, Text 등의 구체적인 뷰 값을 명시해줘야 하므로 귀찮은 작업이 될 것입니다. 이를 some 키워드를 사용해서 UIComponent 코드에 대한 유연성을 부여한 것입니다. 

 

var body: some View {
    VStack(alignment: .leading) {
        Text("My hovercraft is full of eels")
            .font(.headline)
        Text("Mijn luchtkussenboot zit vol paling")
            .font(.subheadline)
    }
}

위와 같은 코드가 있다고 할 때, some View로 선언된 view의 타입은 사실 VStack <TupleView<(Text, Text)>>와 같은 형태입니다.

 

만약 중간에 뷰를 다르게 구성하고 싶다면, 이것의 타입은 점점 더 복잡해질 것입니다.

body의 타입을 구체적으로 입력할 수는 있지만 그러면 view의 구성이 달라질 때마다 해당 타입을 계속해서 업데이트해야 합니다.

 

some 키워드를 사용하면 view를 선언하고 컴파일 시점에 구체적인 타입을 결정하는 것이 가능하므로 생산성에 용이합니다. (개발자의 이의도를 불투명하게 유지하는 것이 생산성에 쉽다고 합니다.)

 

SwiftUI는 불투명한 리턴 타입이라고 불리는 기능에 크게 의존합니다.

이러한 뷰는 view를 만들 때마다 작동하는 것을 볼 수 있으며 이 내용은 View 프로토콜을 준수하는 하나의 특정 타입이지만, 이것이 무엇인지 말하지 않는다는 것을 의미합니다. 

'Swift' 카테고리의 다른 글

프로토콜  (0) 2022.08.02
프로퍼티 - 저장 프로퍼티  (0) 2022.07.27
Any/AnyObject  (0) 2022.04.29
mutating  (0) 2022.04.29
Generic  (0) 2022.04.29