이 글을 쓰게된 원인

  • 약 2만개의 이상의 DB를 대조해서 처리해야 했다.
  • 비교대상이 적은 경우에는 for-in, forEach, map 등 어떤 방법을 써도 큰 성능 차이가 없다.
  • 그런데 이번 경우처럼 2만개 정도 되는 DB를 대조 비교해야한다고 치면…(20,000 X 20,000 = 400,000,000)☠️
  • 정확히는 2만개의 DB를 서로 비교하여 삭제된 것, 새로 추가된 것, 업데이트 된 것 들을 각각 추려내어 이후에 과정을 진행하면 됐다.
  • 그래서 떠오른게 Set 이었다.
  • 그런데 여기서 문제가 발생하기 시작했다…

문제를 겪은 부분

class Model: Object {
    @objc dynamic var key = ""
    @objc dynamic var id = ""
    
    override static func primaryKey() -> String? {
        return "key"
    }
}
  • 우선 위처럼 아주 간단한 Model을 만들었다.
  • primaryKey는 유일해야하며, 수정될 수 없다.
  • 그런데 각 Model을 비교해야하니, 유일한 값인 primaryKey는 사용할 수 없었고 결국 비교를 위한 값으로 id를 추가했다.
  • 그리고는 테스트를 해봤다.
let model1 = Model()
model1.key = UUID().uuidString
model1.id = "hi"
            
let model2 = Model()
model2.key = UUID().uuidString
model2.id = "hi"

let set1 = Set([model1])
let set2 = Set([model2])

print(set1.intersection(set2)) // []
  • 결과는 당연히 빈칸이 나온다…
  • 왜냐면 기본적으로 아무것도 건드리지 않으면 같은지 다른지 판단할 기준이 없기 때문이다.
  • 그래서 isEqual(:)을 추가하게 됐다.
class Model: Object {
    @objc dynamic var key = ""
    @objc dynamic var id = ""
    
    override static func primaryKey() -> String? {
        return "key"
    }
    
	// 추가
    override func isEqual(_ object: Any?) -> Bool {
        if let otherModel = object as? Model {
            return self.id == otherModel.id
        } else {
            return false
        }
    }
}
  • 그리고는 다시 테스트 해봤다.
let model1 = Model()
model1.key = UUID().uuidString
model1.id = "hi"
            
let model2 = Model()
model2.key = UUID().uuidString
model2.id = "hi"

let set1 = Set([model1])
let set2 = Set([model2])

print(set1.intersection(set2)) // []
  • 👀띠용. 여전히 빈칸이다.
  • 아니야…뭔가 이상해. 잘못됐어 다시 한 번 해보자.
print(set1.intersection(set2)) // [Model { key = 8A399388-1FA2-4699-8258-5DA5DFCEC203; id = hi; }]
  • 오 역시 잘 나오네!
  • 다시 한 번 해볼까
print(set1.intersection(set2)) // []
  • ???????
  • 왜 때문인지 모르겠지만 실행할때마다 지맘대로 결과를 내려줬다…🤦‍♂️
  • 이 문제는 결국 Hashable에서 찾았다.
  • Set이 제대로 동작하려면 Hashable을 제대로 구현해야만 한다.
  • Hashable을 채택한 경우라면 Haseable한 프로퍼티와 == 함수를 구현해야만 한다.
  • Realm 객체는 이미 Hashable을 채택하고 있고, == 함수는 isEqual(:)로 구현되어 있다.
  • 그런데 Hashable한 프로퍼티는 제대로 지정되지 않아서 이런 랜덤한 결과를 내뱉고 있던 것이다.
  • 결국 간단하게 hash 프로퍼티를 override를 하면 된다.
class Model: Object {
    @objc dynamic var key = ""
    @objc dynamic var id = ""
    
	// 추가
    override var hash: Int {
        return id.hash
    }
    
    override static func primaryKey() -> String? {
        return "key"
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        if let otherModel = object as? Model {
            return self.id == otherModel.id
        } else {
            return false
        }
    }
}

  • 결국 이렇게 해서 랜덤이 아닌 늘 같은 값을 얻을 수 있었다.
  • 아주 간단한 문제이지만 이것 때문에 이틀이나 끙끙 앓았다.
  • Swift 관련 책을 보면서 늘 Hashable 이란 단어를 보긴 했지만, 뭔🐶소리야 하고 넘어갔는데 이참에 다음 글로 Hashable을 주제로 해야겠다.