본문 바로가기

학습 노트/iOS (2021)

194. Transformable

Transformable


영구 저장소에 저장된 데이터를 TableView에 표시한다.

Contact.swift

public class Contact: NSObject, NSCoding {
   @objc public var tel: String
   @objc public var fax: String
   @objc public var email: String
   
   init(tel: String, fax: String, email: String) {
      self.tel = tel
      self.fax = fax
      self.email = email
      
      super.init()
   }
   
   public static func sample() -> Contact {
      let telStr = String(format: "010-%04d-%04d", Int.random(in: 1000...9999), Int.random(in: 1000...9999))
      let faxStr = String(format: "02-%04d-%04d", Int.random(in: 1000...9999), Int.random(in: 1000...9999))
      let emailStr = String(format: "example%03d@chillog.page", Int.random(in: 0...999))
      
      return Contact(tel: telStr, fax: faxStr, email: emailStr)
   }
   
   public func encode(with aCoder: NSCoder) {
      aCoder.encode(tel, forKey: "tel")
      aCoder.encode(fax, forKey: "fax")
      aCoder.encode(email, forKey: "email")
   }
   
   public required init?(coder aDecoder: NSCoder) {
      tel = aDecoder.decodeObject(forKey: "tel") as! String
      fax = aDecoder.decodeObject(forKey: "fax") as! String
      email = aDecoder.decodeObject(forKey: "email") as! String
      
      super.init()
   }
}

Contact 클래스는 String 형식의 tel, fax, email 의 속성을 가지고,
무작위 정보를 생성하는 sample 메서드와 NSCoding를 활용하는 encode 메서드가 정의돼 있다.

Sample.xdatamodeld

Data Model의 EmployeeEntity를 편집한다.
새롭게 사용할 contact Attribute를 추가하는데,
Integer, Decimal 등의 기본 형식만 지원하기 때문에 새로 사용할
Contact 클래스의 형식을 사용하기 위해서는 Binary Data 혹은 Transformable을 사용해야 한다.

 Binary

Binary는 2진 데이터로
모든 형식의 Data를 Binary화 할 수 있어
형식의 제한 없이 사용할 수 있다는 장점이 있다.
단, 변환하고, 복구하는 과정 자체를 직접 구현해야 한다는 단점이 존재한다.

TransformableTableViewController.swift > tableView(didSelectRowAt:)

   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      tableView.deselectRow(at: indexPath, animated: true)
      
      let target = resultController.object(at: indexPath) as! EmployeeEntity
      if let contactData = target.contact {
          let contact = NSKeyedUnarchiver.unarchiveObject(with: contactData) as! Contact
          print(contact.tel)
          print(contact.email)
          print(contact.fax)
      } else {
        let newContact = Contact.sample()
        let data = NSKeyedArchiver.archivedData(withRootObject: newContact)
        tareget.contact = data
        DataManager.shared.saveMainContext()
   }

선택한 Cell의 contact 데이터를 최종적으로 Contact 클래스로 타입캐스팅 한다.
실제로 저장된 데이터 형식이 Contact 형식이기 때문으로 실제 속성에 접근하기 위해선
NSKeyedUnarchiver를 사용해 해당 타입으로 변환해야한다.

만약 선택한 Cell에 해당하는 데이터가 존재하지 않는다면 sample 메서드를 사용해
무작위 데이터를 생성하고 저장한다.
이 때는 반대로 NSKeyedArchiver를 사용한다.


결과


Transformable

Binary 형식이 변환의 구현을 직접 해야하는 반면,
Transformable은 Coredata가 할 수 있는 수준에서 Data 변환을 처리하는 방식이다.
단 이에도 한계가 있기 때문에 불가능한 수준이라면
직접 구현한 뒤 이를 CoreData가 사용할 수 있도록 설정해 줘야 한다.

현재 직원들의 연락처를 저장하는 형식인 Contact 클래스는 NSCoding 프로토콜을 사용하고 있다.
덕분에 Archiver를 사용해 변환 코드를 간편하게 생성할 수 있다.

ContactTransformer.swift

@objc(ContactTransformer)
class ContactTransformer: ValueTransformer {
	override class func transformedValueClass() -> AnyClass {
		return NSData.self
	}
	override class func allowsReverseTransformation() -> Bool {
		return true
	}
	override func transformedValue(_ value: Any?) -> Any? {
		return NSKeyedArchiver.archivedData(withRootObject: value!)
	}
	override func reverseTransformedValue(_ value: Any?) -> Any? {
		guard let data = value as? Data else {
			fatalError()
		}
		return NSKeyedUnarchiver.unarchiveObject(with: data)
	}
}

사용할 transformer는 ValueTransformer 프로토콜을 채용한다.
작성할 메서드는 4개로 역변환 코드가 필요 없는 경우를 제외하면
allowsReverseTransformation 메서드를 정의할 필요가 없어 3개가 된다.

tranformedValueClass()
NSData 형식이나 String을 반환한다.
이 과정을 통해 전달된 인스턴스가 Data 타입으로 저장된다.

allowsReverseTransformation()
보통은 역변환 코드가 함께 필요하기 때문에 기본값을 그대로 사용하도록 생략된다.
필요 없다면 지금처럼 오버라이드 해 false를 반환한다.

transformedValue(_ value: Any?) -> Any?
Entity의 attribute를 context에 저장할 때 호출된다.
파라미터로 전달된 데이터를 transformedValueClass에서 반환한 형식으로 변환해 반환한다.

reverseTransformedValue(_ value: Any?) -> Any?
저장소에 저장된 데이터를 실제 형식으로 변환해 반환한다.

이렇게 작성한 transformer는 objc로 인식할 수 있도록
첫번째 줄 처럼 키워드를 추가해 설정해야 한다.

Sample.xcdatamodeld

이제 작성한 transformer를 Coredata에서 사용할 수 있도록 설정해 줘야한다.
Transformer와 CustomClass, Module를 지정한다.
각각 자동으로 변환할 transformer, 변환 될 클래스, Module에 해당한다.

TransformableTableViewController.swift

   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      tableView.deselectRow(at: indexPath, animated: true)
       let target = resultController.object(at: indexPath) as! EmployeeEntity
	   if let contact = target.contact {
		   print(contact.tel)
		   print(contact.email)
		   print(contact.fax)
	   }else {
		   target.contact = Contact.sample()
		   DataManager.shared.saveMainContext()
	   }
      
   }

이제는 transformer가 알아서 변환을 진행하기 때문에
형변환을 구현했던 코드들이 사라졌고, 덕분에 훨씬 간결한 코드가 됐다.

지금처럼 CoreData에 저장되는 데이터를 Binary에서 Transformable로 바꾸는 경우
새로운 Model 버전을 생성해 migration을 진행해야 할 필요가 있다.

'학습 노트 > iOS (2021)' 카테고리의 다른 글

196. Data Validation  (0) 2022.05.30
195. Faulting & Uniquing  (0) 2022.05.24
193. Fetched Result Controller  (0) 2022.04.09
192. Expression  (0) 2022.03.30
191. Predicate Syntax  (0) 2022.03.24