본문 바로가기

TIL

[TID] #7 - Protocol Conforms to Hashable / DiffableDataSource의 Header Reload / Combine 사용한 Clean Architecture 감상 / Contributor가 되고 싶다!

1. 특정 Protocol 자체를 Hashable하게 만드는 방법

특정 Protocol을 채택하기만 해도 Hashable을 Conforming하게 할 수 있도록 시도를 해보았다.. 프로토콜 초기구현을 통해서 할 수 있을 줄 알았는데... 오개념으로 인해 잘못된 시도를 했는데.. 뭘 몰랐던 것인지 보자!

 

DiffableDataSource의 요구사항

@available(iOS 13.0, tvOS 13.0, *)
@preconcurrency @MainActor open class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UICollectionViewDataSource
where SectionIdentifierType : Hashable, SectionIdentifierType : Sendable,
ItemIdentifierType : Hashable, ItemIdentifierType : Sendable { 
    public typealias CellProvider = (_ collectionView: UICollectionView, _ indexPath: IndexPath, _ itemIdentifier: ItemIdentifierType) -> UICollectionViewCell?
    @MainActor public init(collectionView: UICollectionView, cellProvider: @escaping UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider)
}

위를 보면 SectionIdentifierType과 ItemIdentifierType가 Hashable, Sendable해야 한다.

생성자는 프로토콜로 정의되어 있고, 여기에 구체 타입을 넣어줘야 실제 생성이 가능하다.

저게 사실 생성자라는 것을 망각했다.. 제네릭에 생성자에는 구체 타입만 넣을 수 있다...

 

CellProvider가 typealias로 정의되어 있는데 저런 식으로 클로저를 간단하게 정의하는 것.... 참신하다.. 갈길이 멀다.

 

Equatable : Hashable이 따르는 Protocol

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

extension Equatable {
    public static func != (lhs: Self, rhs: Self) -> Bool
}

프로토콜의 요구사항으로 ==을 구현해야 한다. 좌우 항의 Equality를 반환하는 Bool 반환 메서드이다.

static func ==은 구현을 해줘야 하고, static func !=는 구현을 해주지 않아도 된다.

 

Hashable : AnyHashable이 따르는 프로토콜

public protocol Hashable : Equatable {

    /// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
    ///   conform to `Hashable`, implement the `hash(into:)` requirement instead.
    var hashValue: Int { get }

    /// Implement this method to conform to the `Hashable` protocol. The
    /// components used for hashing must be the same as the components compared
    func hash(into hasher: inout Hasher)
}

주석을 읽어보니 hashValue가 아닌 hash 함수를 구현해야 한다!

 

음...그런데 생각해보니 Hashable을 채택하는 것만으로도 Hashable하게 사용하는 것이 가능했던 것 같은데...?

/// Conforming to the Hashable Protocol
/// ===================================
///
/// To use your own custom type in a set or as the key type of a dictionary,
/// add `Hashable` conformance to your type. The `Hashable` protocol inherits
/// from the `Equatable` protocol, so you must also satisfy that protocol's
/// requirements.
///
/// The compiler automatically synthesizes your custom type's `Hashable` and
/// requirements when you declare `Hashable` conformance in the type's original
/// declaration and your type meets these criteria:
///
/// - For a `struct`, all its stored properties must conform to `Hashable`.

예예.. 주석을 읽어보니 struct의 경우에는 Hashable을 채택하게 하면, 안에 있는 저장 프로퍼티들의 값을 combine하여 hash를 해주도록 extension을 구현해두셨나 봅니다.

 

그래서 프로토콜이 Hashable하게 하는 것이 왜 안되는데?

Hashable 내부에는 Generic이 존재하고, 이를 명시해줄 구체 타입이 필요한데 프로토콜은 구체 타입이 아니기 때문이다...라고 결론내렸는데 아직 some과 any에 대해서 조금 더 공부해야 할 것 같아서 이부분은 대충 보고 넘어가주세요 틀렸을수도 있으니까..

 

Hashable : AnyHashable이 따르는 프로토콜

@frozen public struct AnyHashable {

    /// Creates a type-erased hashable value that wraps the given instance.
    ///
    /// - Parameter base: A hashable value to wrap.
    public init<H>(_ base: H) where H : Hashable

    /// The value wrapped by this instance.
    ///
    /// The `base` property can be cast back to its original type using one of
    /// the type casting operators (`as?`, `as!`, or `as`).
    ///
    ///     let anyMessage = AnyHashable("Hello world!")
    ///     if let unwrappedMessage = anyMessage.base as? String {
    ///         print(unwrappedMessage)
    ///     }
    ///     // Prints "Hello world!"
    public var base: Any { get }

    public static func != (lhs: AnyHashable, rhs: AnyHashable) -> Bool
}

extension AnyHashable : Equatable {
    /// - Parameters:
    ///   - lhs: A type-erased hashable value.
    ///   - rhs: Another type-erased hashable value.
    public static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool
}

extension AnyHashable : Hashable {

    public var hashValue: Int { get }

    public func hash(into hasher: inout Hasher)
}

그래서 결국에는 AnyHashable을 이용하고, 나중에 타입캐스팅을 통해 DataBinding 하는 방법을 선택할 수밖에 없었다.

 

https://emptytheory.com/2021/01/11/using-ios-diffable-data-sources-with-different-object-types/

 

Using iOS Diffable Data Sources with Different Object Types

Since iOS 13, we’ve been able to revamp the way our table and collection views work by using diffable data source classes (UITableViewDiffableDataSource and UICollectionViewDiffableDataSource…

emptytheory.com

2. Diffable DataSource의 Header Reload

Diffable의 Diff에 대해 생각해 보았을 때, 단순히 같은 데이터(item)를 가지고 apply해도 Hashable Protocol에 의해 동일하다고 판단되면 Reload가 되지 않는다. Diffable DataSource에서는 Header에 따로 접근해서 Reload할 방법이 없기에, 임시 방편으로 view의 reference를 VC에 두는 방법으로 해결했다. 하지만 section을 아예 갈아치우는 방법도..생각해보자..

 

3. Some과 Any에 대해

프로토콜을 공부하다가 Some과 Any에 대한 내용이 나왔다. opaque type...뭐 이런 것들이 나오는데 여기에 대해서 따로 공부할 예정.

https://swiftsenpai.com/swift/understanding-some-and-any/

 

Understanding the "some" and "any" keywords in Swift 5.7 - Swift Senpai

Feeling confused about how to use the "any" and "some" keywords, and what are their differences? This article will clear things up for you.

swiftsenpai.com

 

4. Guard문의 바람직한 사용

다른 언어들에서 클린 코드(과도한 분기의 방지)를 위해 if를 통해 빠른 return을 해서 중복 분기를 제거하는 방법으로 이미 사용되던 것이었고, 스위프트는 이러한 방법을 쉽게 사용할 수 있도록 guard 문을 만들어 두었다.

 

guard clause

https://www.google.com/search?q=guard+clause&oq=guard+clause&aqs=chrome..69i57j0i512l7j0i30l2.2561j0j15&sourceid=chrome&ie=UTF-8 

 

guard clause - Google 검색

In the use of a clause guard, the logic of the conditions is normally inverted and, depending on the complexity of the condition, it is quite complex to ...

www.google.com

https://jamie95.tistory.com/99- 객체지향 생활체조 원칙인데, 좋은 내용이 많다! 난 아직 객체지향도 멀었구나 싶었다.

 

[Java] 객체지향 생활 체조 원칙 9가지 (from 소트웍스 앤솔러지)

1. 한 메서드에 오직 한 단계의 들여쓰기만 한다. 한 메서드에 들여쓰기가 여러 개 존재한다면, 해당 메서드는 여러가지 일을 하고 있다고 봐도 무관하다. 메서드는 맡은 일이 적을수록(잘게 쪼

jamie95.tistory.com

 

5. Combine을 이용한 Clean Architecture에 대한 감상

이번에 하는 프로젝트에서 Combine으로 Clean Architecture를 사용해 보았는데... 느낀점은 아래와 같다.

(1) RxSwift에서 지원하는데 Combine에는 없는 기능이 많다.

(2) 문법만 살짝 다를 뿐 원리는 동일하다.

(3) 구현에 어려운 점은 없다...

 

(1)로 인해 든 생각이... CombineCommunity에 있는 CombineCocoa에 Contribute를 해볼까 고민하고 있다.. 최근에는 거의 업데이트가 되지 않았는데 뭔가 새로운 대안이 생긴 것일까? 일단 한번 갈겨보고 생각해봐야지...

 

어쨌거나 RxSwift가 훨씬 편했고, RxSwift처럼 사용할 수 있도록 여러 클래스를 Extension했다. Combine의 장점은 성능이 훨씬 좋다...정도인 것 같다.

 

이번 글은 상당히 정리가 되지 않은 상태이지만... 천천히 하나씩 공부해보려고 한다.