달력

8

« 2025/8 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

1. Intro

: iOS App을 개발하면서 Ribs나 TCA같은 Architecture Pattern을 사용하지 않았습니다. 해당 Architecture에서 동의하지 못하는 부분도 있었지만, Architecture를 사용하기 위해 별도의 의존성을 추가해야 하는 것이 위험해 보였기 때문입니다. 

 

 작년 하반기에, Robert C. Martin이 Framework에 대해 이야기하는 영상을 보았습니다. Uncle Bob은 해당 영상에서 Framework를 사용했을 때 겪을 수 있는 문제들에 대해 이야기를 하였는데요, 영상을 보면서 제가 Ribs나 TCA Module에 대해 갖고 있던 생각이 정리가 되어서 글로 작성해보았습니다.

 

2. Framework의 문제점과 고민해볼 것들

: Framework가 무엇인지부터 정의해보죠. Ribs, TCA 처럼 개발자가 Application을 효율적으로 개발할 수 있도록 이런저런 것들을 제공하는 것을 Framework라고 부를 수 있습니다. Framework마다 정해진 규칙, 흐름대로 개발자가 Code를 작성하게끔 유도하므로(이런걸 Invasive Framework라고도 합니다)... Project에 있는 Code들의 일관성을 높일 수 있고, 동료들이 Code를 파악하기 쉬워지니 협업에도 도움이 됩니다.

 

Invasive Framework

: Application을 개발할 때, Framework에서 제공하는 API나 Type을 이용하여 개발하게끔 하는 Framework.

 

 Framework를 이용하여 Application을 개발할 때, 생산성이 높아지고 편리해졌다고 느낄 수 있습니다. 하지만 시간이 갈수록 Application은 Framework에 깊게 결합되고 혹시라도 Framework에서 문제가 생긴다면 대응이 어려워지게 됩니다. TCA를 예로들면, 현재도(2025-04-30 기준) Xcode에서 Indexing이 느려져서 개발에 불편함을 주고있고, Swift Version Up 대응이 늦어지기도 했었습니다(link). 그리고 어떤 Spec을 구현해낼 때... Framework가 구현에 필요한 API를 제공하지 않거나, 예상하지 않았던 Flow로 Code를 작성해야 한다면 Framework가 오히려 방해가 될 수도 있습니다.

 

 Framework를 사용하는게 무조건 나쁘다고 단정지을 순 없지만, Framework를 Project에 사용하기 전에 몇 가지는 고민해보는게 좋을 것 같습니다.

 

1. Framework를 사용하여 무엇을 얻고싶나요?

2. 사용하고자 하는 Framework가 그것들을 제공해주나요?

3. 그 Framework를 사용한다면 어떤 위험들을 감수해야 하나요?

 

 Framework 대신에 단어만 바꾸면 일반적으로 많이 생각해보는 것들이죠? 만일 저라면... Redux와 유사하게 State를 관리하는 Code를 많이 작성하게 될 것 같고, Project 내 Code의 일관성을 높이고 싶으며, 사용하고자 하는 Framework에 존재하는 문제가 나에게 큰 영향이 없는 상황이라면 TCA를 사용할 것입니다.

 

 Uncle Bob이 Framework에 대해 이야기하는 영상에서 이런 말을 했습니다. 좋은 Architect는 Production Code가 Framework에 과하게 의존하지 않게하여, Framework를 Safe & Unintrusive하게 유지할 수 있게한다구요. 그리고 Framework를 사용하려면, Framework를 만든 사람들이 사용자를 위해 Commit하지 않으니(꼭 그렇지는 않습니다만) 직접 Commit해야 한다는 것을 알고 있어야 한다고도 말했구요.

 

 Framework에 너무 많은 것을 의존하지 않고, 충분히 고민 후 사용합시다. Framework는 도구니까요.

 

99. Reference

1) The problem with frameworks - Uncle Bob, youtube (link)

2) Invasive and Non-Invasive Frameworks in Java (link)

:
Posted by syjdev
2025. 3. 23. 08:22

[Swift] associatedtype 이야기 iOS Development/Swift2025. 3. 23. 08:22

1. Intro

: 현재는 iOS 개발 실무를 하고있지 않아서, 알고있던 지식들이 희미해지는 것을 느낍니다. 그냥 놓아버리기에는 아깝다고 생각하여, 공부삼아 정리한 내용들을 tistory에 정리해나가고자 합니다. 첫 시작은 associatedtype으로 정했습니다.

 

2. associatedtype syntax의 등장

: associatedtype syntax가 등장하기 전, typealias를 이용하여 Associated Type(protocol에서 사용하는 type placeholder)를 선언했었습니다.

 

protocol MyProtocol {
    typealias Item
}

 

 이렇게 작성된 Code를 처음 본다고 가정해봅시다. Item은 type 별칭일까요? 아니면, Associated Type일까요? 헷갈립니다. 이를 해결하기 위해, 누군가가 Associated Type을 선언하기 위한 syntax를 제안했었습니다(SE-0011, link).

 

protocol MyProtocol {
    associatedtype Item
}

 

 이 제안은 받아들여졌고, Swift 2.2부터 associatedtype을 사용할 수 있게 되었습니다.

 

3. associatedtype과 constraint

: 개발자들은 associatedtype syntax 추가에 만족하지 않고, 보다 복잡한 제약조건을 associatedtype에 추가할 수 있길 원했습니다. 그중 몇몇 개발자들이 associatedtype에 직접 where 조건을 추가하는 것을 제안(SE-0142, link)했었고, 이는 받아들여져서 Swift 4.0부터 아래와 같은 Code를 작성할 수 있게 되었습니다.

 

protocol Human {
    associatedtype Age where Age: Equatable
    func introduce() -> Age
}

struct Syj: Human {
    func introduce() -> Int {
        return 0
    }
}

 

 얼마지나지 않아, associatedtype과 관련된 기능이 하나 더 추가됩니다. 이전까지 associatedtype은 자신이 속한 protocol을 제약조건으로 사용할 수 없었습니다. 이것에 대해 이야기하는 제안서(SE-0157, link)를 보면, 아래와 같은 Code 작성이 불가능했었습니다.

 

// SE-0157이 받아들여지기 전에는 Compile되지 않는 Code였음.
protocol Sequence {
    associatedtype SubSequence: Sequence
        where Iterator.Element == SubSequence.Iterator.Element, SubSequence.SubSequence == SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence

 

 그래서 어쩔 수 없이 아래에 작성한 Code처럼, 좀 장황하게 Code를 작성했어야 했었습니다.

 

protocol Sequence {
    associatedtype SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence
}

struct SimpleSubSequence<Element> : Sequence {
    typealias SubSequence = SimpleSubSequence<Element>
    typealias Iterator.Element = Element
}

struct SequenceOfInts : Sequence {
    func dropFirst(_ n: Int) -> SimpleSubSequence<Int> {
        // ...
    }
}

 

 SE-0157은 채택되었고 Swift 4.1부터 associatedtype은 자신이 속한 protocol을 제약조건으로 사용할 수 있게 되었습니다.

 

4. Opaque Result Type

: 2019년, 어떻게하면 Swift Generic의 사용성을 더 개선할 수 있을지를 이야기해보는 글이 Swift Forum에 올라왔었습니다(link - Improving the UI of generics). Forum에 내용중 하나를 제안했었는데(SE-0244, link), return에 대한 Type-Level Abstraction 문제를 이야기하고 해결책을 제시했었습니다.

 

 제안서에서 말하는 문제가 무엇일까요? 아래의 Code를 살펴봅시다.

 

protocol Shape {
    func draw(to: Surface)
}

struct Rectangle: Shape { /* ... */ }
struct Union<A: Shape, B: Shape>: Shape { /* ... */ }
struct Transformed<S: Shape>: Shape { /* ... */ }

protocol GameObject {
    associatedtype Shape: Shapes.Shape
    var shape: Shape { get }
}

 

 위의 Code에서, GameObject protocol을 따르게 하려면 아래와 같이 장황하게 Return type을 명시해야 했었습니다.

 

struct Star: GameObject {
    var shape: Union<Rectangle, Transformed<Rectangle>> {
        return Union(Rectangle(), Transformed(Rectangle()))
    }
}

 

 shape의 type이 장황하죠? 정확한 Return type을 명시하는 것은 별로 중요하지 않고, Shape protocol을 따른다는 것이 중요합니다. 따라서 지금의 저 장황한 Return type은 Code를 읽는 사람에게도 별로 도움이 되지 않습니다. 그래서 SE-0244에서는 다음과 같은 syntax를 제안합니다.

 

struct Star: GameObject {
  var shape: some Shape {
    return Union(Rectangle(), Transformed(Rectangle()))
  }
}

 

 SE-0244는 채택되었고, Swift 5.1부터 some Protocol syntax를 사용할 수 있게 되었습니다.

 

5. Primary Associated Types

: Protocol은 associatedtype을 가질 수 있지만 constraint를 설정하려면 where을 이용해야 했었습니다.

 

func process<S: Sequence>(sequence: S) where S.Element = String { 
    // ... 
}

func concatenate<S : Sequence>(_ lhs: S, _ rhs: S) -> S where S.Element == String {
    // ...
}

 

 위의 Code는 단순하지만, where이 길어진다면 가독성이 떨어지게 되겠죠. 개선의 필요성과 해결책을 이야기한 제안서(SE-0346, link)에서는 primary associatedtype을 Protocol 선언 시 명시하고, generic type처럼 사용할 수 있는 Syntax를 제안했습니다.

 

// 기존 Syntax
protocol Container {
    associatedtype Item
    func append(_ item: Item)
}

struct StringContainer: Container {
    typealias Item = String
    func append(_ item: String) { }
}

func process<T: Container>(container: T) where T.Item == String { }

// SE-0346에서 제안하는 Syntax
protocol Container<Item> {
    func append(_ item: Item)
}

struct StringContainer: Container<String> {
    func append(_ item: String) { }
}

func process(container: some Container<String>) { }

 

 확실히, SE-0346에서 제안한 Syntax가 Code 가독성을 개선시켜주는 것 같네요. SE-0346은 채택되었고 Swift 5.7부터 primary associatedtype을 정의할 수 있게 되었습니다.

 

 이후, SE-0358(link)에서는 Swift Standard Library의 여러 Protocol에 primary associatedtype을 정의하자고 제안하였습니다. SE-0358도 Swift 5.7에 채택되었습니다.

 

99. Reference

1) Swift Evolution - Proposals

  1. SE-0011 : replace typealias associated (link)
  2. SE-0142 : associated types constraints (link)
  3. SE-0157 : recursive protocol constraints (link)
  4. SE-0244 : opaque result types (link)
  5. SE-0346 : light weight same type syntax (link)
  6. SE-0358 : primary associated types in stdlib (link)

 

2) Swift Documents

  1. protocol (link)
  2. opaque types (link)

 

3) Others

  1. Improving the UI of generics (link)
  2. Generics manifesto (link)
:
Posted by syjdev
2024. 11. 16. 20:02

UIImage RenderingMode iOS Development/ETC2024. 11. 16. 20:02

1. Intro

: 몇달 전 이야기입니다. 저는 공부하는 차원에서 만들고있는 iOS App이 있는데요. 기능도 적고, Spec도 아주 단순한 App이어서 iOS 18에서도 별다른 문제가 없을 줄 알았었어요.

 

pic1. iOS 18 전후의 비교

 

 돋보기 icon이 iOS 18부터는 다른 색상으로 표시되고 있네요. 저는 아래와 같은 code로 돋보기 icon을 노출시키고 있었습니다.

 

1
2
3
let icon = UIImage(systemName: "magnifyingglass")
icon.withTintColor(UIColor(Color.grau).withAlphaComponent(0.6), 
                   renderingMode: .alwaysOriginal)
cs

 

 renderingMode를 alwaysOriginal로 적용했네요. 별도의 tintColor를 적용하면서 alwaysTemplate로 적용하게 작성했었던 것 같은데 실수했었나 봅니다. alwaysTemplate을 적용하도록 수정함으로써 이 문제는 쉽게 해결되었습니다.

 

 해결하고보니 문득 생각이 들었습니다. 저는 renderingMode에 대해 얼마나 알고 있을까요? renderingMode에 적용할 수 있는 automatic, alwaysOriginal, alwaysTemplate의 차이에 대해 설명할 수 있을까요? 이번 기회에 정리해보면 좋겠다고 생각하여 정리해보았습니다.

 

 

2. UIImage.RenderingMode

: renderingMode는 color 정보를 이용해서 image를 어떻게 나타낼지를 조절하는 mode입니다. 총 3가지가 있는데, 각각은 다음과 같습니다.

 

automatic

: image 속성에 따라 template 또는 original로 rendering. 여기서 말하는 image 속성은 어떤 것을 말하는 것일까요?

 

pic2. image 속성

 

 `Render As` 부분에서 적용할 수 있는 image 속성입니다. original이나 template은 알겠는데, default는 뭘까요? default로 적용하게 되면 image가 어떤 context로 사용되는 지에 따라 `Render As Original Image`로 적용될수도, `Render As Template Image`로 적용될수도 있습니다. 몇가지 case를 정리해보죠.

 

1) UIImageView : `Render As Default` => `Render As Original Image`

2) UIButton : `Render As Default` => `Render As Template Image`

3) UITabBarItem : `Render As Default` => `Render As Template Image`

4) UINavigationItem : `Render As Default` => `Render As Template Image`

 

 button이나 그와 유사한 곳에서 image를 rendering하는 경우 `Render As Default`는 `Render As Template Image`가 된다 정도로 정리할 수 있겠네요.

 

 여담이지만, 저는 alwaysTemplate을 적용하도록 수정하여 문제를 해결했었는데요. 해당 image는 UISearchBar에 rendering되는데, 이건 `button이나 그와 유사한 곳`에 속하는 case여서 그냥 tintColor만 적용(renderingMode는 automatic)해도 됩니다.

 

alwaysOriginal

: image의 원본 그대로 rendering.

 

alwaysTemplate

: image가 갖고있는 color 정보는 무시되고, tintColor에 따라 color가 변경되어 rendering.

 

 

3. tintColor를 template image에 적용하는 Mechanism

: `Render As Template Image`로 설정된 image이거나 renderingMode가 alwaysTemplate인 경우, tintColor에 따라 image의 color가 변경되어 rendering 될 수 있습니다. 그런데 image의 각 pixel마다 RGB 값이나 Alpha 값이 다를텐데요, 어떤 기준으로 각 pixel에 tintColor를 적용하는 것일까요? Alpha 값이 0이냐 아니냐에 따라 tintColor가 적용되는 방식이 달라집니다.

 

불투명한 pixel (Alpha > 0)

: tintColor의 RGB 값이 적용되고, Alpha 값은 유지됩니다.

 

투명한 pixel (Alpha == 0)

: tintColor의 RGB 값이 적용되지 않습니다.

 

 template image를 rendering할 때, tintColor를 명시하지 않으면 어떤 color가 적용될까요? UIImage에 renderingMode를 alwaysTemplate으로 적용하고, tintColor는 적용하지 않을 수도 있습니다.

 

1
let icon = UIImage(systemName: "magnifyingglass")?.withRenderingMode(.alwaysTemplate)
cs

 

 image에 tintColor를 적용하지 않았다면, 부모 View의 tintColor가 적용되게 됩니다. 만약 부모 View에도 tintColor가 적용되어 있지 않았다면 tintColor가 적용된 부모 View를 찾다가, 끝내 찾지 못한다면 System Blue Color가 적용되게 됩니다.

 

 

4. SwiftUI와 RenderingMode

: UIKit의 UIImage와 비슷합니다. tintColor 대신에 foregroundColor를 적용한다는 차이만 있습니다.

 

1
2
3
Image(systemName: "magnifyingglass")
    .renderingMode(.template)
    .foregroundColor(.gray)
cs

 

 

 

 

5. Reference

1) https://developer.apple.com/documentation/uikit/uiimage/renderingmode

2) https://developer.apple.com/documentation/uikit/uiimage/providing_images_for_different_appearances

 

 

:
Posted by syjdev