Published on

๐Ÿ“ฑ iOS - ๋‹ค์šด์ƒ˜ํ”Œ๋ง

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

๋‹ค์šด์ƒ˜ํ”Œ๋ง์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌํ•˜๊ธฐ

์ด์ „ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์•Œ์•„๋ณด์•˜๋˜ PHPicker๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋˜ ์ค‘ ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋งˆ์ฃผ์ณค์Šต๋‹ˆ๋‹ค.

Swift๊ฐ€ ํ—ˆ์šฉํ•œ 4,194,304 ๋ฐ”์ดํŠธ๋ณด๋‹ค ํฐ ๋ฐ์ดํ„ฐ๋ฅผ UserDefaults์— ์ €์žฅํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค.

UserDefaults๋Š” ์ž‘์€ ๋ฐ์ดํ„ฐ๋“ค์„ ์ €์žฅํ•  ๋ชฉ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๋Š” ์ž์—ฐ์Šค๋Ÿฌ์šด ํ˜„์ƒ์ธ ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค.

์ง€๊ธˆ ์ €์žฅํ•˜๋ ค๋Š” ์ด๋ฏธ์ง€๋Š” ์›๋ณธ ์ด๋ฏธ์ง€ ๊ทธ ์ž์ฒด์˜€์œผ๋‹ˆ ๋‹น์—ฐํžˆ ์šฉ๋Ÿ‰์ด ์ปธ์„ ๊ฒƒ์ด๊ณ , ์ด๋ฏธ์ง€์˜ ์šฉ๋Ÿ‰์„ ์ค„์ด๋ ค๋ฉด ๋ณดํ†ต ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ๋ถ€ํ„ฐ ํ•˜๊ฒ ์ฃ ?

์ด๋ฏธ์ง€ ํฌ๊ธฐ์˜ ์ถ•์†Œ๋Š” ๊ฐ€์žฅ ์ง๊ด€์ ์œผ๋กœ ์ด๋ฏธ์ง€ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ  ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€์šฉ๋ฅ ๋„ ๋†’์ด๋Š” ์ข‹์€ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋„ ์ด๋ฏธ์ง€๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•๋ถ€ํ„ฐ ์ฐพ์•„๋ณด๊ฒŒ ๋˜์—ˆ์ฃ .

์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ์‹œ์˜ CPU์™€ ๋ฉ”๋ชจ๋ฆฌ ๋™์ž‘

ํ•˜์ง€๋งŒ ๊ณต๋ถ€๋ฅผ ํ•œ ์ง€ ์–ผ๋งˆ ์ง€๋‚˜์ง€ ์•Š์•„ ๋‹จ์ˆœํžˆ ์ด๋ฏธ์ง€๋ฅผ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•์€ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹ˆ๊ณ , ์˜คํžˆ๋ ค ์•ฑ์ด ๊ธฐ๊ธฐ์— ๊ณผ๋ถ€ํ•˜๋ฅผ ๋” ์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ ๊นŒ๋‹ญ์€ 2018๋…„์— ์ง„ํ–‰๋œ WWDC2018์—์„œ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

Image and Graphics Best Practices - WWDC18 - Videos - Apple Developer

์ด๋ฏธ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๊ฒŒ์‹œ๋ฌผ๋“ค์—์„œ ๋งŽ์ด ๋‹ค๋ฃจ๊ณ  ์žˆ๊ณ  iOS์˜ ํ™”๋ฉด ํ‘œํ˜„ ๋ฐฉ๋ฒ•์„ ํ•™์Šตํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ์„ธ์…˜์ž…๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” UIGraphicsImageRenderer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ PHPicker๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ์ด๋ฏธ์ง€ ์—ฌ๋Ÿฌ๊ฐœ์˜ ํฌ๊ธฐ๋ฅผ ์ค„์ด๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

var images: [UIImage] = []

var result in results {
  result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
    guard let image = object as? UIImage else { return }
    guard let resizedImage: UIImage = UIGraphicsImageRenderer(size: CGSize(width: 2_000, height: 2_000)).image { context in
      image.draw(in: CGRect(origin: CGPoint.zero, size: newSize))
    } else { return }
    images.append(resizedImage)
  }
}

๋‹จ์ˆœํžˆ ๋ณด๊ธฐ์—๋Š” ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ์—†์–ด๋ณด์ด์ฃ .

๋ฌธ์ œ๋Š” ๋‘ ๋ฒˆ์— ๊ฑธ์ณ์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

1. ์›๋ณธ์„ ๋กœ๋“œํ•œ๋‹ค.

guard let image = object as? UIImage else { return }

์ฒซ ๋ฒˆ์งธ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ๋ฅผ ์ค„์ด๋Š” ๋ชฉ์ ์ด ๋ฌด์—‡์ด์˜€๋‚˜์š”?

๋ฉ”๋ชจ๋ฆฌ์— ๋ถ€ํ•˜๋ฅผ ๋œ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ์˜€์ฃ .

์œ„ ๋ฐฉ๋ฒ•์€ ์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜ํ–‰๋งŒ ๋œ๋‹ค๋ฉด ๊ทธ ์ดํ›„์—๋Š” ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋ฅ ์„ ๋‚ฎ์ถฐ์ค„๊ฒ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด์„œ ์šฐ์„  ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ ๋กœ๋“œํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ ํ•œ ๋ฒˆ ๋กœ๋“œํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…๋“ค์„ ์ข…๋ฃŒ์‹œํ‚ค๊ฑฐ๋‚˜ ์‹ฌํ•˜๋ฉด ์•ฑ ์ž์ฒด๋ฅผ ์ข…๋ฃŒ์‹œํ‚ค๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์œ„ ์ฝ”๋“œ๋งŒ ๋ด๋„ PHPicker๋Š” ์„ ํƒํ•œ ๋ฐ์ดํ„ฐ๋ฅผ UIImage ํƒ€์ž…์œผ๋กœ์„œ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ตณ์ด ํฌ๊ธฐ๊ฐ€ ํฐ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์•„๋ฌด๋Ÿฐ ์ฒ˜๋ฆฌ ์—†์ด UIImage๋กœ ๋ฐ”๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋˜ ๊ฒƒ์ด์ฃ !

2. ์ž‘์€ ์ด๋ฏธ์ง€๋ฅผ ์ง์ ‘ ๊ทธ๋ฆฐ๋‹ค.

image.draw(in: CGRect(origin: .zero, size: newSize))

๋‘ ๋ฒˆ์งธ ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค.

์œ„ ๋ฐฉ๋ฒ•์€ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์™€ ๊ทธ ์ด๋ฏธ์ง€์˜ ์ž‘์€ ๋ฒ„์ „์„ draw ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ๊ทธ๋ ค์ค˜์•ผ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜ํ”ฝ ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— CPU์™€ ๋ฉ”๋ชจ๋ฆฌ ๋‘ ํ•˜๋“œ์›จ์–ด ๋ชจ๋‘์—๊ฒŒ ์ž‘์—…์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ด๊ณ , ์ด ๋˜ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๋Š” ๊ฒƒ๊ณผ๋Š” ๊ฑฐ๋ฆฌ๊ฐ€ ๋จผ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์šด ์ƒ˜ํ”Œ๋ง

๊ทธ๋Ÿฌ๋ฉด ๋‹จ์ˆœํžˆ ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ง• ํ•˜๋Š” ๋ฐฉ๋ฒ• ์™ธ์— ์–ด๋–ค ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ์š”?

๋ฐ”๋กœ ๋ฐ์ดํ„ฐ๋ฅผ UIImage๋กœ ๋ฐ”๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ CGImage ํƒ€์ž…์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋””์ฝ”๋”ฉํ•  ํŒŒ์ผ(data buffer) ์ž์ฒด์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์ค„์ด๋Š” ๋‹ค์šด์ƒ˜ํ”Œ๋ง ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ ์ด ๋ฐฉ๋ฒ•์€ Core Graphics์ด๋ผ๋Š” ํ•œ ์ธต ๋” low-levelํ•œ ๋‚ด์šฉ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ์ข€ ๋” ๊ธธ๊ณ  ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค.

func downsample(at url: URL, to pointSize: CGSize, scale: CGFloat) -> Data? {

  let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary

  guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil }

  let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale

  let downsampleOptions = [
    kCGImageSourceCreateThumbnailFromImageAlways: true,
    kCGImageSourceCreateThumbnailWithTransform: true,
    kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
  ] as CFDictionary

  guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }

  let data = NSMutableData()

  guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { return nil }

  let isPNG: Bool = {
    guard let utType = cgImage.utType else { return false }
    return (utType as String) == UTType.png.identifier
  }()

  let destinationProperties = [
    kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
  ] as CFDictionary

  CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
  CGImageDestinationFinalize(imageDestination)

  return data as Data
}

์–ด์งˆ์–ด์งˆํ•˜์ฅฌ..? ๐Ÿ˜ต

๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ ๋‘ ํŒŒํŠธ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ ์‚ดํŽด๋ด…์‹œ๋‹ค.

1. CGImage ์ƒ์„ฑ

let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let source = CGImageSourceCreateWithURL(url as CFURL, sourceOptions) else { return nil }

let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [
  kCGImageSourceCreateThumbnailFromImageAlways: true,  
  kCGImageSourceShouldCacheImmediately: true,
  kCGImageSourceCreateThumbnailWithTransform: true,
  kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary

guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }

downsampling์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

์‚ฌ์‹ค์ƒ ํ•„์ˆ˜์ ์ธ ๋กœ์ง๋“ค์€ ์ด ์•ˆ์— ๋‹ค ์žˆ์Šต๋‹ˆ๋‹ค.

Option flag๋ฅผ ์ •์˜ํ•ด์ฃผ๊ณ  ํ•ด๋‹น ์˜ต์…˜์„ ํ†ตํ•ด CGImage๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

  • kCGImageSourceShouldCache: false ์ด๋ฏธ์ง€ ์†Œ์Šค์— ๋Œ€ํ•œ ์ฐธ์กฐ๊ฐ’๋งŒ์„ ์ƒ์„ฑํ•˜๊ณ  CGImageSource๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ์ฆ‰์‹œ ๋””์ฝ”๋”ฉ์„ ์ง„ํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • kCGImageSourceShouldCacheImmediately: true ์•ž์„  ์˜ต์…˜๊ณผ๋Š” ๋ฐ˜๋Œ€๋กœ ๋‹ค์šด์ƒ˜ํ”Œ๋ง ๋กœ์ง์ด ์‹œ์ž‘๋จ๊ณผ ๋™์‹œ์— ๋””์ฝ”๋”ฉ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  • kCGImageSourceCreateThumbnailWithTransform: true ๋‹ค์šด์ƒ˜ํ”Œ๋ง๋œ ์ด๋ฏธ์ง€๋ฅผ ์›๋ณธ ์ด๋ฏธ์ง€์™€ ๊ฐ™์€ ํ˜•ํƒœ๋ฅผ ๊ฐ–๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

2. PNG ํ€„๋ฆฌํ‹ฐ ๋ณด์กด

let data = NSMutableData()

guard let imageDestination = CGImageDestinationCreateWithData(data, UTType.jpeg.identifier as CFString, 1, nil) else { return nil }

let isPNG: Bool = {
  guard let utType = cgImage.utType else { return false }
  return (utType as String) == UTType.png.identifier
}()

let destinationProperties = [
  kCGImageDestinationLossyCompressionQuality: isPNG ? 1.0 : 0.75
] as CFDictionary

CGImageDestinationAddImage(imageDestination, cgImage, destinationProperties)
CGImageDestinationFinalize(imageDestination)

return data as Data

์ด ๋ถ€๋ถ„์€ ์‚ฌ์‹ค ์„ ํƒ์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.

PNG ํ™•์žฅ์ž๋Š” ๋ณดํ†ต ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๊ฐ™์ด ์ด๋ฏธ์ง€์˜ ํ’ˆ์งˆ์ด ์ค‘์š”ํ•œ ์ด๋ฏธ์ง€๋“ค์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ด๋ฏธ์ง€์˜ ํ’ˆ์งˆ์ด ์†์ƒ๋œ๋‹ค๋ฉด ์ด๋ฏธ์ง€์— ํฌํ•จ๋œ ํ…์ŠคํŠธ ๋“ฑ์˜ ์‹œ์ธ์„ฑ, ๊ฐ€๋…์„ฑ์ด ์ค‘์š”ํ•œ ์š”์†Œ๋“ค์— ์˜ํ–ฅ์ด ๊ฐˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

PNG ํ™•์žฅ์ž๋ผ๋ฉด 1.0์˜ ์†์ƒ ์—†๋Š” ์••์ถ•์„, ๊ทธ ์™ธ์˜ ํ™•์žฅ์ž๋ผ๋ฉด 0.75์˜ ์••์ถ•๋ฅ ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋””์ฝ”๋”ฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

PHPicker์™€์˜ ์‚ฌ์šฉ

์ด์ „ PHPicker๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

itemProvider.loadObject(ofClass: UIImage.self) { image, error in
  // do something
}

์ด๋ฒˆ ๊ฒŒ์‹œ๋ฌผ์—์„œ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•์€ URL ํƒ€์ž…์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, error in
  // do something
}