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/
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/
4. Guard문의 바람직한 사용
다른 언어들에서 클린 코드(과도한 분기의 방지)를 위해 if를 통해 빠른 return을 해서 중복 분기를 제거하는 방법으로 이미 사용되던 것이었고, 스위프트는 이러한 방법을 쉽게 사용할 수 있도록 guard 문을 만들어 두었다.
guard clause
https://jamie95.tistory.com/99- 객체지향 생활체조 원칙인데, 좋은 내용이 많다! 난 아직 객체지향도 멀었구나 싶었다.
5. Combine을 이용한 Clean Architecture에 대한 감상
이번에 하는 프로젝트에서 Combine으로 Clean Architecture를 사용해 보았는데... 느낀점은 아래와 같다.
(1) RxSwift에서 지원하는데 Combine에는 없는 기능이 많다.
(2) 문법만 살짝 다를 뿐 원리는 동일하다.
(3) 구현에 어려운 점은 없다...
(1)로 인해 든 생각이... CombineCommunity에 있는 CombineCocoa에 Contribute를 해볼까 고민하고 있다.. 최근에는 거의 업데이트가 되지 않았는데 뭔가 새로운 대안이 생긴 것일까? 일단 한번 갈겨보고 생각해봐야지...
어쨌거나 RxSwift가 훨씬 편했고, RxSwift처럼 사용할 수 있도록 여러 클래스를 Extension했다. Combine의 장점은 성능이 훨씬 좋다...정도인 것 같다.
이번 글은 상당히 정리가 되지 않은 상태이지만... 천천히 하나씩 공부해보려고 한다.
'TIL' 카테고리의 다른 글
[TID] #9 - Nibmle, Quick, RxTest, RxNimble로 UseCase 및 ViewModel 테스트 코드 작성 (0) | 2022.11.09 |
---|---|
[TID] #8 - Tuist Project에 Firebase 의존성 추가하기 (6) | 2022.11.02 |
[TID] #6 - MVVM에서 Cell 내부 Control의 Binding / List API에 대한 생각 (0) | 2022.10.25 |
[TID] #5 - UseCase와 Interface Provider / Repository Pattern (0) | 2022.10.25 |
[TID] #4 - Tuist, Bundle, 동적/정적 라이브러리 (2) | 2022.10.19 |