복사/붙여넣기를 지원하는 Swift 타입 작성하기 2

복사/붙여넣기를 지원하는 타입을 작성할 때, 해당 타입은 반드시 직렬화 및 역직렬화가 가능해야 했다. 그런데 NSPasteboardReading 프로토콜은 지정 이니셜라이저를 포함하고 있기 때문에 클래스를 직접 수정하거나 서브클래싱하지 않으면 이 방법을 적용할 수가 없다. 따라서 NSPasteboardItem을 대신 사용하는 방법을 적용해야 한다. 이 때 핵심은 해당 클래스가 어떤 모종의 방법을 사용해서 직렬화 및 역직렬화가 가능해야 한다는 점이다.

Swift의 새로운 데이터 인코딩 방법인 Codable 프로토콜을 사용해서 기존의 ‘간단한’ 데이터 타입을 복사/붙여넣기 하는 방법을 알아보도록 하자. 이 구현의 기본 아이디어는 다음과 같다.

  1. 모든 표준 Swift 타입들은 Codable 프로토콜을 지원한다.
  2. 모든 프로퍼티가 Codable인 임의의 타입은 ‘공짜로’ Codable 타입이 될 수 있다.
  3. 타입 자체가 Codable인 경우, JSON이나 프로퍼티 리스트로 간단하게 인코딩/디코딩이 가능하다.
  4. NSPasteboardItemDataProvider 프로토콜은 클래스 자체의 수정 없이 클립보드로 데이터를 넘겨줄 수 있다.

그렇다면 이전글에서 작성했었던 Person 클래스를 다시 한 번 다음과 같이 정의할 수 있다.

class Person: Codable
{
  var firstName: String
  var lastName: String
  var fullName: String { reutrn "\(firstName) \(lastName)" }
  
  // 인코딩되는 프로퍼티 키를 정의한다.
  enum CodingKeys : CodingKey {
    case firstName, lastName
  }

  init(firstName: String, lastName: String)
  {
    self.firstName = firstName
    self.lastName = lastName
  }
}

이제 여기에 NSPasteboardItemDataProvider 프로토콜을 적용하기 위해 확장해보자. 참고로 클립보드에 넘겨주는 타입 구분값은 일반 문자열 타입의 UTI가 아니라 NSPasteboard.PasteboardType 형식으로 정의해야 한다.

// UTI 추가를 위한 타입 확장
extension NSPasteboard.PasteboardType 
{ 
  static let person = NSPastebord.PasteboardType("com.sooop.person")
}


// Person 확장
extension Person: NSPasteboardItemDataProvider
{
  func pasteboard(_ pasteboard:NSPasteboard?, item: NSPasteboardItem, provideDataForType type: NSPasteboard.PasteboardType)
  {
    // 문자열일 때와 Person 타입을 요구할 때 
    // 각 타입의 데이터를 전달한다. 
    switch type {
    case .string:
      item.setString(fullName, forType: type)
    case .person:
      guard let plist = try? PropertyListEncoder().encode(self)
      else { return }
      item.setData(plist, forType: type)
    default:
      break
    }
  }
}

문자열과 Person, 두 가지 타입에 대해서 각각의 데이터를 페이스트보드 아이템에 넘겨주는 동작을 수행하도록 메소드를 작성했다. 이제 이 타입을 어떻게 복사하는지 살펴보자.

let person = Person(firstName: "Hello", lastName: "World")

let pboard = NSPasteboard.general
pboard.clearContents()

let item = NSPasteboardItem()
item.setDataProvider(person, forTypes: [.string, .person])
pboard.writeObjects([item])

NSPasteboardItem 객체를 하나 만들고, 복사할 값인 person을 데이터 프로바이더로 세팅해서 아이템 객체를 클립보드에 쓰는 것으로 복사가 완료된다. 이 코드를 실행한 후에 텍스트 편집기나 그외에 텍스트를 붙여넣을 수 있는 곳에서 붙여넣기 하면 “Hello World”가 붙여넣어 질 것이다.

다음은 Person 객체로 붙여넣기를 하는 방법이다. 클립보드에 복사된 Person 타입 객체가 있다면 다음의 코드로 사본을 얻게 된다.

let pboard = NSPasteboard.general

guard let item = pboard.pasteboardItems?.first
 , let plist = item.data(forType: .person)
 , let p = try? PropertyListDecoder().decode(Person.self, from: plist)
else { return }

NSLog("\(p.fullName) has been pasted.")