오일러 프로젝트 76

76번 문제는 예전 31번(영국화폐 조합의 수)와 사실상 같은 문제이다. 임의의 자연수 N 을 N보다 작은 자연수들의 합으로 나타내는 경우의 수를 분할수라고 하는데, 이는 결국 1…N-1 의 액면가를 가지는 동전들로 N 만큼의 금액을 만드는 것과 동일한 연산이다.

오일러 프로젝트 76 더보기

NSImage와 CGImage 변환하는 법

NSImage > CGImage로 변환하기

NSImagecgImage(forProposedRect:Context:hints:)라는 메소드를 가지고 있는데, 이는 어떤 영역에 그려질 최적의 CGImage 객체를 찾아서 리턴하는 기능을 수행한다.  이 때 모든 파라미터는 옵션이며, 전부 nil로 넣어도 상관없다. 다만 파라미터들은 NSImage가 가지고 있는 여러 개의 CGImage 표현형 중에서 어떤 것을 선택할지를 결정하는데 도움을 주는 힌트에 해당한다. 

proposedDestRect는 CG이미지를 사용할 영역에 대한 참조로 주로 어느 해상도(크기)에서 사용될 것인지를 정한다. 만약 nil을 전달하면 NSImage의 크기 영역을 기준으로 삼게 된다. context는 그래픽 컨텍스트이며, hints 값은 그외의 힌트가 된다. 

이미지 표현형을 통해서 얻기

사실 이 방법은 위의 메소드를 호출하는 것과 별반 다르지 않을텐데, NSImage가 이미지를 ‘다루는’데 목표를 둔 반면에 실제로 어떤 그림인지에 대해서는 신경쓰지 않는 특이한 구조에 착안하는 것이다. 표현되는 그림이 무엇인지는 representations 파라미터를 통해서 알 수 있는 것이다. 이는 [NSImageRep] 타입으로 실제 이미지를 나타내는 데이터들이 모여 있다. (그 중에는 비트맵도 있을 수 있고 벡터나 PDF 등 그 형식은 매우 다양할 수 있다.) 

하지만 해당 NSImage가 비트맵 이미지를 기반으로 생성되었다는 사실을 보장할 수 있다면, 해당 이미지의 표현형 중에서 NSBitmapImageRep 인 것을 고르고 그 중 하나 (대표적으로 가장 앞에 있는 것)의 cgImage 속성을 취하는 것이다. 

if let cgImage = (nsImage.representations as? [NSBitmapImageRep])?.first.cgImage {
  // use cgImage...
}

CGImage -> NSImage 로 변환하기

CGImageNSImageView등에서 표현하기 위해서는 다시 NSImage로 변환해야 한다. NSImage의 편의 이니셜라이저 중에는 init(cgImage:size:) 가 있는데 이것을 사용하면 된다. 

참고로 이 때 size 파라미터에 NSZeroSize를 사용하면, 주어진 CGImage의 픽셀 폭/높이를 기준으로 사이즈를 삼게된다. 

다른 한가지 방법으로는 앞에서 비트맵이미지 표현형(NSBitmapImageRep)을 사용한 방법을 거꾸로 한 것이 있다.  NSBitmapImageRepCGImage로 바로 생성이 가능하다. (또 CIImage로도!) 이렇게 만들어진 표현형을 빈 NSImage에 추가하는 것이다.

let rep = NSBitmapImageRep(cgImage: cgimage)
let image = NSImage()
image.addRepresentation(rep)


NSBitmpaImageRep의 사용법

CGImage는 CoreGraphics에서 사용하는 픽셀단위 비트맵이미지 데이터를 다루는 클래스이다. 이 클래스가 주로 이미지 렌더링이나 오프스크린 드로잉등에 사용되는 관계로 주요 API가 이러한 작업에 치중하고 있어서 실제로 이미지 파일로부터 데이터를 읽어서 생성하거나 데이터를 저장하는 작업은 클래스내에서 처리할 수 없다.

그나마 CIImageinit?(contentsOf:)를 제공하기 때문에 지원가능한 파일을 읽어들여서 인스턴스를 만드는 작업을 바로 수행할 수 있다. 다만 실제로 CIImage는 렌더링되는 실제 이미지라기 보다는 코어이미지 내의 필터를 적용하는 레시피로 기능하기 때문에 상황은 약간 다른다.

또한 iOS의 경우에는 주로 사용되는 UIImageCGImageCIImage로 이니셜라이저만으로 상호변환이 가능하며, JPEG 및 PNG 포맷 데이터도 간단하게 생성할 수 있기 때문에 문제가 되지 않는데, macOS의 경우에는 파일데이터와 이미지데이터가 모두 따로 놀기 때문에 혼선이 있을 수도 있고, 여러 모로 귀찮은 부분들이 많이 존재한다.

이번 시간에는 비트맵 이미지 데이터와 이미지 파일 사이를 연결해주는 NSBitmapImageRep에 대해서 좀 알아보도록 하자.

NSBitmapImageRep

NSBitmapImageRepNSImageRep의 서브클래스로 비트맵이미지를 렌더링하는 역할을 담당한다. NSImageRepNSImage의 실제 ‘그림 내용’이 되는 셈인데 왜 이런식으로 이미지를 다루고자 할 때 두 개의 클래스가 동원되어야 했을까?

이는 macOS의 그래픽환경의 역사와 관련이 깊다. 전통적으로 애플은 macOS에서 화면으로 표시될 수 있는 모든 것이, 그대로 프린터 등의 다른 장치로 표시될 수 있기를 원했다. 말 그대로 WYSIWYG가 OS의 그래픽 환경에서부터 그대로 구현되기를 원했던 것이다. 따라서 지금은 코어 그래픽이 된 쿼츠(Quartz)에서부터 ‘그래픽 컨텍스트’라는 개념을 도입하여 일련의 동일한 드로잉 명령으로 시각적 이미지를 생성하고, 출력되는 장치에 맞게 그 내용을 렌더링하는 것이다.

예를 들어 NSView를 예로들어보자. 화면에 그려지는 대부분의 뷰는 NSView의 서브클래스이고, 이들 뷰들은 draw(in:) 메소드를 통해서 자기 스스로를 그리는 방법을 알고 있다.

만약 화면에 그려진 내용을 출력하거나, PDF로 만들기를 원한다면 프로그래머는 그에 상응하는 드로잉 메소드를 따로 구현할 필요가 없다. 왜냐하면 프린터로 출력하거나 PDF 데이터로 출력할 때에도 NSView의 가족들은 화면에 자신을 그릴 때와 동일한 드로잉 명령을 사용하기 때문이다. 이 동작에서의 차이는 각각의 뷰들이 자신을 그리는 대상이 ‘서로 다른 컨텍스트’라는 것 뿐이라는 것이다.

이러한 맥락에서, 다양한 출력환경에 단일 이미지가 대응하는 최선의 방법을 생각해보자. 출력해상도가 높은 프린터와 같은 환경에 대응하기 위해서는 계단 현상이 없는 벡터 이미지를 사용하는 것이 가장 좋은 선택이 될 수 있다. 반면 잦은 갱신이 필요한 화면 디스플레이에서는 매번 벡터이미지를 비트맵으로 래스터화하는 것이 부담스러울 수 있으므로 적절한 해상도의 비트맵 이미지를 사용하는 것이 권장된다. 디스플레이 역시 고밀도 픽셀 (쉽게 말해 레티나) 환경이라면 더욱 큰 비트맵을 사용하는 것이 더 나은 품질의 결과를 보일 것이다.

이렇게 상황에 따라서 다른 소스를 쓰면서, 고수준 API는 하나로 통일하는 방법으로 NSImage는 일종의 ‘이미지의 배열’이면서 그 내부의 실제 ‘그림’하고는 상관없는 클래스가 되었다. NSImage는 여러 개의 “표현형”을 가지고 있으면서 이미지를 렌더링할 때 가장 최적의 표현형을 선택해주는 역할을 담당하는 셈이다.

NSImage의 표현형으로서 기능하는 클래스가 NSImageRep이며, 이는 이미지 자체의 종류에 따라 NS*imageRep의 여러 서브클래스로 나눠진다. 대표적으로 많이 쓰이는 것이 NSBitmapImageRep, NSCIImageRep, NSPICTImageRep, NSPDFImageRep, NSEPSImageRep이다.

즉 만약 어떤 NSImage 객체가 비트맵, PDF, EPS 타입의 표현형을 모두 가지고 있다면, 화면에 비트맵으로 출력되거나 PDF로 만들어지거나, 프린터로 출력될 때 모두 가장 적절한 표현형을 자동으로 사용하여 최적의 결과로 렌더링될 수 있다는 것을 의미한다.

생성

NSBitmapImagRep은 크게 세 가지 방법으로 생성할 수 있다.

  • init?(data: Data)
  • init(cgImage: CGImage)
  • init(ciImage: CIImage)

데이터는 NSBitmapImageRep가 지원하는 타입의 파일 데이터를 넘겨주면 된다. 실질적으로 이미지 파일로부터 비트맵이미지를 생성하는 셈이다. 지원가능한 타입은 NSBitmapImageRep.FileType이라는 열거타입에 정의되어 있는데, .bmp, .gif, .jpg, .jpeg2000, .png, .tiff 가 있다.

CGImage, CIImage로의 변환

init?(cgImage:)를 사용하면 CGImage로부터 비트맵표현형을 만들어낼 수 있으며, cgImage:CGImage? 프로퍼티를 통해서 곧바로 CGImage 객체를 얻을 수 있다.  반대로 CIImage의 경우에는 init(bitmapImageRep:) 을 통해서 생성할 수 있다.

따라서 파일경로로부터 데이터를 읽어들여 CGImage를 만드는 과정은 다음과 같이 처리할 수 있다.

func readCGImage(from filepath: String) -> CGImage? {
  let url = URL(fileURLWithPath: filepath)
  if let data = try? Data(contentsOf: url),
     let rep = NSBitmapImageRep(data: data) {
    return rep.cgImage
  }
  return nil
}

이미지 파일로 저장하기

비트맵 이미지 표현형이 CIImage, CGImage로 자유롭게 변환가능하다는 것은 곧 해당 타입의 이미지들을 파일로 저장할 때, NSBitmapImageRep를 사용할 수 있다는 것을 의미한다.  representation(using:properties:)는 이미지 표현형으로부터 특정 이미지 타입의 파일 데이터를 생성한다.  (이 때 properties 파라미터는 거의 쓸 일이 없다고 보면 되는데, 궁금하다면 해당 레퍼런스를 찾아보자.)

// saving image
let url = ...
let rep = NSBitmapImageRep(cgImage: source)
if let data = rep.representation(using:.png, properties:[:]) {
  try? data.write(to: url)
}

aiohttp에서 큰 파일을 업로드하는 법

파일 업로드는 보통 요청의 body에 인코딩된 파일 데이터를 넣어서 POST 요청으로 서버에 전달되는데, aiohttp에서는 다음과 같이 post를 처리하는 핸들러를 사용해서 이를 처리할 수 있다.

async def store_mp3_handler(request):
  data = await request.post()
  mp3 = data['mp3']
  filename = mp3.filename
  mp3_file = mp3.file
  content = mp3_file.read()
  return web.Response(body=content, headers=
     MultiDict({ 'CONTENT-DISPOSITION': mp3_file}))

여기서 문제는 request.post() 메소드가 요청 데이터를 한꺼번에 메모리로 읽어들이기 때문에 메모리 부족으로 서버가 죽을 수 있는 상황이 있다는 것이다. 따라서 aiohttp에서 일반적으로 처리할 수 있는 요청의 크기는 2MB로 제한된다. 하지만 이 크기는 어지간한 사진 하나의 용량도 감당하기 어렵기 때문에 뭔가 다른 방법이 필요하다. (보통은 일종의 옵션 값 같은 걸로 최대 처리 요청 크기를 변경할 수 있을 줄 알았는데, 없었다.)

request.multipart는 이러한 문제를 피하는 멀티파트 리더로 기능할 수 있다. 리더 객체를 생성한 다음에는 멀티파트 요청을 단위 콘텐츠 별로 읽어들일 수 있다. 개별 파트의 헤더를 먼저 읽고, 다시 최종적으로 file 필드로부터 버퍼로 파일의 일부 내용을 순차적으로 읽어서 처리할 수 있다.

async def store_mp3_handler(request):
  reader = await request.multipart()

  field = await reader.next()
  assert field.name == 'name'
  name = await field.read(decode=True)
  
  field = await reader.next()
  assert field.name == 'mp3'
  filename = filed.filename

  size = 0
  with open(os.path.join('/spool/yarrr-media/mp3/', filename), 'wb') as f:
    while True:
      chunk = await field.read_chunk()
      if not chunck:
        break
      size += len(chunk)
      f.write(chunk)
  return web.Response(text=f'{filename} sized of {size} successfully stored.')

델리게이트 패턴에서 제네릭으로 – Swift

(제목이 스포일러이긴한데…) 간단한 클래스를 하나 작성해보자. 0으로 시작하는 값에서 메소드를 하나를 호출하면 그 값을 1씩 증가시켜나가는 것이다.

class Counter {
  var value: Int = 0
  func increase() {
    value += 1
    print("value: \(value)")
  }
}

현실적으로는 별 쓸 데 없는 이 클래스를 사용하려 할 때, 값이 변할 때 수행하는 동작을 입맛에 맞게 커스터마이징하고 싶은 경우가 있을 수 있다. 물론 increase() 메소드를 그 때 그 때마다 변경하면 되지만, 소스를 직접 수정할 수 없는 서드파티가 이 클래스를 사용한다면, 델리게이트를 만들어서 값이 변하는 이벤트의 처리를 맡길 수 있다. 예를 들어 변경된 새 값이 짝수인 경우에만 출력한다던가, 화면 출력이 아닌 파일로 저장한다던가 혹은 네트워크를 통해 서버에 값을 업로드하려고 할 수도 있을텐데, 델리게이트가 해당 동작을 수행하도록 한다면 우리는 델리게이트가 지정된 메소드를 호출하게끔만 해주면 될 일이다.

델리게이트를 구현하기 위해서는 보통 델리게이트에 필요한 프로토콜을 정의하고, 해당 프로토콜을 따르는 델리게이트 클래스를 작성해야 한다. 예를 들어 이런 식이다.

class Counter {
  weak var delegate: CounterDelegate?
  func increase() {
    value += 1
    delegate?.valueDidChange(value)
  }
}

protocol CounterDelegate {
  func valueDidChange(_ value: Int)
}

class Foo: CunterDelegate {
  func valueDidChange(_ value: Int) {
    if value % 2 == 0 { print("value: \(value)") }
  }
}

물론, 위 코드는 정상적으로 컴파일 될 수 없다. Swift에서 weak로 정의되는 프로퍼티는 반드시 class 타입이어야 한다. 따라서 프로토콜 CounterDelegateclass를 상속받아 정의되어야 할 것이다.

protocol CounterDelegate: class { ...

타입 메소드로 변경하기

많은 경우 델리게이트 패턴에서, 델리게이트 그 자체는 특정한 상태값을 유지할 필요가 없는 경우가 많다. 대신 델리게이트는 델리게이트 메소드를 제공하는 호스트의 역할을 담당하기 때문에 인스턴스로 만들어지는 경우가 많다. 만약, 델리게이트가 특정한 상태값을 유지하고 있을 필요가 없다면 델리게이트 메소드가 반드시 인스턴스 메소드여야 할 이유는 없을 것이다. 그렇다면 다음과 같이 델리게이트 구현을 바꾸는 것은 어떨까?

protocol CounterDelegate {
  static func valueDidChange(_ value: Int)
}

이 프로토콜은 클래스가 아닌 struct나 enum 타입들도 따를 수 있게 된다. 특히 인스턴스를 만들 필요가 없으니 enum 으로 다음과 같이 만들어볼 수 있겠다.

enum Foo: CounterDelegate {
  static func valueDidChange(_ value: Int) {
    if value % 2 == 0 {
      print("value: \(value)")
    }
  }
}

그렇다면 Counter는 어떻게 수정되어야 할까? delegate 프로퍼티는 특정한 타입의 객체가 아닌, 타입 그 자체를 가리켜야 하고, 따라서 delegate의 타입은 CounterDelegate가 아닌 CounterDelegate.Type을 사용하게 된다.

class Counter {
  var delegate: CounterDelegate.Type?
  var value: Int = 0 
  func increase() {
    value += 1
    delegate?.valueDidChange(value)
  }
}

다시 제네릭으로 옮겨가기

그렇게 따지고보면 delegate는 Counter 클래스의 연관 타입(associated type)으로 볼 수 있다. 그렇다면 굳이 프로퍼티로 정의할 필요 없이, Counter 자체가 제네릭 타입이면 되는 것이다. 즉 이를 테면 Delegate라는 연관 타입을 가지는데, 이 연관 타입은 CounterDelegate 프로토콜을 따라야 한다. 이는 클래스 선언 부분에서 제네릭에 대한 where 절을 통해서  델리게이트 타입이 해당 프로토콜을 따르는 것을 명시하면 된다.

class Counter<Delegate> where Delegate: CounterDelegate {
  var value: Int = 0 
  func increase() {
    value += 1
    Delegate.valueDidChange(value)
  }
}

이렇게 되면 델리게이트에 대한 프로토콜과, 그 프로토콜을 따르는 타입만이 존재한다. 델리게이트의 인스턴스는 필요하지 않으며, 오직 Counter를 생성할 때 연관되는 델리게이트의 타입만을 명시하면 된다. 이렇게 의존성을 주입하는 코드가 간결해지고, 다음과 같이 사용될 수 있다.

do {
  let c = Counter<Foo>()
  for _ in (0...20) {
    c.increase()
  }
}

한계

여기서 보인 예시는 델리게이트를 다른 객체 인스턴스가 아닌 타입 자체로 옮기면서 연관 타입이 위임의 대상이 되는 것을 통해서 제네릭으로 델리게이트 구현을 바꿀 수 있다는 개념을 보인 것이다. 즉 “이런게 된다”는 것이지 이것이 기존의 방법보다 반드시 좋다고는 볼 수 없다. 특히 현재 Swift 및 Cocoa의 기능에서는 몇 가지 한계가 있는데 우선 델리게이트 관계는 통상 인터페이스 빌더에서 많이 연결된다. 인터페이스 빌더에서 연결된다는 의미는 결국 Objective-C 런타임을 통해서 구성된다는 것이고, Objective-C에서는 제네릭을 지원하지 않기 때문에 IB에서는 제네릭을 통한 위임을 사용할 수 없다. 또한 현재의 Swift 타입 시스템은 concrete type만을 타입으로 인식한다. 즉 Counter<Foo>는 어떤 타입으로 특정됨에 비해서 타입 파라미터가 생략된 Counter는 아직까지는 그 자체로는 “타입을 만들기 위한 틀”, 즉 메타 타입이며 이는 타입으로 취급되지 않는다. (이 부분은 향후 Swift의 제네릭 시스템에서 higher-kinded 타입을 지원하게 되면 해결될 수 있을 것이다.) 따라서 실제로는 그럴 필요가 전혀 없겠지만, 델리게이트 타입이 Counter 스스로가 되도록 정의할 수 있는 직접적인 방법은 없다.