implicit optional unwrapping

암시적 언래핑은 옵셔널 타입 변수를 선언하면서 ? 대신 !를 선언한다. 특히 Objective-C API들은 객체를 리턴하는 경우가 많은데 이 때 객체들은 실질적으로 포인터이며 이는 항상 nil이 될 수 있으므로 옵셔널 타입이 된다. 즉, idAnyObject?가 되고, NSMutableArrayArray?가 된다는 의미이다. 그렇다고 해서 Objective-C API와 연동하는 코드 모든 곳에서 느낌표를 매번 붙여서 언래핑하는 것은 매우 불편하기 때문에 등장한 것이 아닌가 한다. (순수 Swift 코드에서는 나올 일이 별로 없을 것 같다.)

implicit optional unwrapping 더보기

swift 커스텀 연산자

커스텀 연산자 >->를 만들어 보자. 함수 외부에서 파라미터를 함수로 주입하는 연산자로 이는 swift에서 기본적으로 정의하지 않고 있는 연산자이다. 따라서 연산자를 먼저 선언해준다.

infix operater >-> {}

내용을 구현해준다. 내용은 제네릭 함수로…

func >-> <T, U>(left:T, right:(T)->U) -> U {
    return right(left)
}

이제 테스트해보자…

func someIncrement(a:Int) -> Int {
    return a + 3
}

4 >-> someIncrement
// 7

Swift The Basics

The Basics

상수와 변수

상수는 한 번 선언하면 변경되지 않는 값, 변수는 계속 변경할 수 있는 값이다. 상수는 let으로 선언하며, 변수는 var로 선언한다. 변수명 뒤에는 콜론을 써서 타입 어노테이션을 붙일 수 있다.

var currentLoginAttempt: Int = 0

var문 하나에서 여러 개의 변수를 초기화 하는 것도 가능하다.

var x = 0.0, y = 0.0, z = 0.0 

튜플을 도입하였으므로 파이썬의 튜플 언패킹과 비슷한 문법도 쓸 수 있다.

Swift The Basics 더보기

Swift에서 Monad와 Curried Function 사용하기

Functional Programing in Swift

Swift는 함수형 프로그래밍은 아니지만 함수형 언어들의 특성을 많이 따르고 있다. 그 중에 모나드와 커리드함수(부분적용함수)를 만드는 방법에 대해 살펴보자.

Swift에서 Monad와 Curried Function 사용하기 더보기

NSView의 내용을 이미지로 캡쳐하기

 

NSView의 내용을 비트맵 그래픽 파일로 캡쳐하는 방법에 대해 설명하겠다. 일전에 간략히 적어둔 내용이 있었는데, 잘못된 부분도 있고 예전엔 돼었는데 제대로 동작 안하는 부분도 있어서 다시 정리한다.

PDF로 캡쳐하는 방법도 있으니 살펴보도록 하자.

이미지를 만든다기 보다는 특정한 포맷으로 표현할 수 있은 이미지 표현형을 획득할 수 있으면(NSBitmapImageRep) 이 클래스의 representation(using:properties:)를 사용해서 이미지 파일을 위한 데이터를 생성할 수 있다. 여기에는 크게 두 가지 방법이 있다.

  1. 뷰 자체를 캡쳐하는 NSBitmapImageRep의 이니셜라이저를 사용하기
  2. 뷰의 비트맵 캐시를 추출하기

첫번째 방법은 NSBitmapImageRep의 이니셜라이저 중에서 init(focusedViewRect:)를 사용하는 것이다. Xcode9 기분으로 이 메소드는 뷰의 draw(_:) 내에서 쓸 때에만 유효한 표현형 값이 생성되고, 그 외의 컨텍스트에서는 (제아무리 해당 뷰에 대해서 lockFocus()를 호출하더라도) 제대로 된 데이터가 생성되지 않는다.

두 번째 방법은 뷰의 비트맵 캐시를 추출하는 것이다.

// in NSView's Subclass
func imageRep() -> NSBitmapImageRep? {
  if let rep = bitmapImageRepForCachingDisplay(in: bounds) {
    cacheDisplay(in: bounds, to: rep)
    return rep
  }
  return nil
}

흔히 하는 실수가 bitmapImageRepForCachingDisplay(in:) 을 호출한 결과를 바로 리턴하는 것인데, 이 메소드는 뷰 캐싱을 위한 이미지 표현형 객체를 생성할 뿐이지 그 속에 실제 콘텐츠를 그리지는 않는다. 실제 콘텐츠를 그려넣는 cacheDisplay(in:to:)를 사용해서 콘텐츠를 복사한 후에 이를 사용한다.

비트맵 캐시를 추출할 수 있다면 다음과 같은 식으로 저장하기 메소드를 만들 수 있다.

@IBAction func saveAsPNG(_ sender: Any?) {
  guard let rep = imageRep() else { return  }
  let panel = NSSavePanel()
  panel.allowedFileTypes = ["png"]
  
  let handler: (NSApplication.ModalResponse) -> Void = { res in 
    if res == .OK,
      let data = rep.representation(using:.png, properties:[:])
    {
      do {
        try data.write(to: panel.url!)
      } catch {
        NSLog("Error while saving file.")
      }
  }
  
  if let window = self.window {
    panel.beginSheetModal(for:window, completionHandler: handler)
  }
}

만약 draw(_:) 내에서 그래픽 컨텍스트를 이용하여 그림을 그리는 함수를 가지고 있다면, 비트맵 이미지 표현형을 생성한 후에 이를 바탕으로 이미지를 그려서 만들어도 된다. (이는 기본적으로 비트맵 그래픽 컨텍스트를 만들고 여기에 그림을 그려서 CGImage를 생성하는 방법과 완전히 동일하다.)

func offscreenImage(ofSize repSize: CGSize) -> NSImage {
  let offscreenRep = NSBitmapImageRep(
      bitmapDataPlanes: nil, // nil을 넘기면 자동으로 할당한다.
      pixelsWide: Int(repSize.width),
      pixelsHigh: Int(repSize.height),
      bitsPerSample: 8,
      samplesPerPixel: 4,
      hasAlpha: true,
      isPlaner: false,
      colorSpaceName: NSColorSpaceName.diviceRGB,
      bitmapFormat: .alphaFirst,
      bytesPerRow: 0,   // 8 * 4 * width / 8 인데 계산하지 않고 0 을 넘긴다.
      bitsPerPixel: 0)  // 역시 계산하지 않고 0을 넘긴다.
  
  let g = NSGraphicsContext(bitmapImageRep: offscreenRep!)
  NSGraphicsContext.saveGraphicsState()
  NSGraphicsContext.current = g

  let ctx = g!.cgContext
  let imageFrame = CGRect(origin: CGPoint.zero, size: repSize)
  ctx.setFillColor(NSColor.red.cgColor)
  ctx.fillEllipse(in: imageFrame)
  ...
  
  let image = NSImage()
  image.size = repSize
  image.addRepresentation(offscreenRep!)
  return image
}

참고자료

관련내용

UIView를 UIImage로 캡쳐하는 방법도 있다.