'분류 전체보기'에 해당하는 글 132건

제목: Unsafe Swift: Using Pointers and Interacting with C


스위프트는 디폴트로 메모리 세이프하다. 메모리 세이프 하다는 의미는 메모리에 직접 접근하는 것을 막아주고, 당신이 사용하기 전에 모든것이 초기화 되어있음을 보장한다는 뜻이다. 핵심은 "디폴트"이다. 언세이프 스위프트는 여러분이 필요할때 포인터를 이용해서 메모리에 직접 다룰 수 있게 해준다.

이 튜토리얼은 스위프트의 이른바 "언세이프"라 불리는 소용돌이의 여행에 데려다 줄것이다. "언세이프"라는 용어는 종종 혼란을 만든다. 이것이 당신이 도작하지도 않을, 위험하고 나쁜 코드를 작성하고 있다는 의미가 아니라, 오히려 컴파일러가 도와줄 수 있는 부분의 한계를 뛰어넘고 추가적인 주의를 필요로하는 코드를 작성한다는 의미이다.

C같은 언세이프 언어와 함께 작업할때, 추가적인 런타임 성능이 필요할때, 혹은 그냥 그 내부를 살펴보고 싶을때 이러한 기능이 필요함을 발견할 수 있을 것이다. 이 주제가 한걸음 더 나아간 주제이긴 하지만 여러분이 합리적인 스위프트 언어 지식을 가지고 있다면, 따라올 수 있을 것이다. 또한 C언어 경험이 도움이 될것이지만 필수조건은 아니다.

시작하기
이 튜토리얼은 3가지 플레이그라운드로 구성된다. 첫번째 플레이그라운드에서는 메모리 레이아웃(Memory Layout)을 살펴보고 언세이프 포인터를 사용해보는 코드를 몇개 만들어 볼 것이다. 두번째 플레이그라운드에서는 스위프트 인터페이스로 데이터 압축을 스트리밍하는 저수준 C API를 가볍게 다룰것이다. 마지막 플레이그라운드에서는 arc4random에대한 독립적인 대안의 플랫폼을 만들 것인데, 언세이프 스위프트를 사용했지만 사용자들이 그 세부내용은 모르도록 만들것이다.

UnsafeSwift라는 새 플레이그라운드를 생성하면서 시작해보자. 이 튜토리얼의 모든 코드들은 플랫폼에 의존하지 않기 때문에 아무 플랫폼이나 선택해도 된다. Foundation 프레임워크를 불러왔는지 확인하자.

메모리 레이아웃

샘플 메모리샘플 메모리


언세이프 스위프트는 메모리 시스템에 직접적으로 동작한다. 메모리는 일련의 상자라고 생각할 수 있고(실제로 10억개의 상자이다), 각 상자 안에는 숫자가 들어있다. 각 상자는 그것과 연관된 유일한 메모리 주소를 가진다. 저장소의 가장 작은 주소로 가능한 단뒤는 한 바이트이며, 한 바이트는 8비트로 이루어져있다. 8비트는 0에서 255의 값을 저장할 수 있다. 프로세서는 메모리 워드(word)에는 효율적으로 접근할 수 있는데, 워드는 보통 1바이트 이상이다. 64비트 시스템에서는 한 워드가 8바이트 혹은 64비트의 길이이다.

스위프트는 여러분의 프로그램에서 어떤것의 크기나 계열(alignment)에대해 이야기해주는 메모리 레이아웃(Memory Layout) 기능을 가지고 있다.

여러분의 플레이그라운드에 아래의 코드를 추가하자.
MemoryLayout<Int>.size          // returns 8 (on 64-bit)
MemoryLayout<Int>.alignment     // returns 8 (on 64-bit)
MemoryLayout<Int>.stride        // returns 8 (on 64-bit)

MemoryLayout<Int16>.size        // returns 2
MemoryLayout<Int16>.alignment   // returns 2
MemoryLayout<Int16>.stride      // returns 2

MemoryLayout<Bool>.size         // returns 1
MemoryLayout<Bool>.alignment    // returns 1
MemoryLayout<Bool>.stride       // returns 1

MemoryLayout<Float>.size        // returns 4
MemoryLayout<Float>.alignment   // returns 4
MemoryLayout<Float>.stride      // returns 4

MemoryLayout<Double>.size       // returns 8
MemoryLayout<Double>.alignment  // returns 8
MemoryLayout<Double>.stride     // returns 8
MemoryLayout<Type>은 컴파일시간에 특정 Type의 size, alignment, stride를 셜정하는 제네릭 타입이다. 반환된 값은 바이트 단위이다. 예를들어 Int6size에서는 2바이트이고 alignment도 같다. 이것은 2로 나누어 떨어지는 주소에서 시작해야한다는 의미이다.

예를들어 Int16100이라는 주소에 할당할 수 있지만 1이 주소에 할당하는 것은 필요 alignment를 위반하는 것이기 때문에 불가능하다. 만약 Int16들을 함께 모은다면 stride 간격에 모일 것이다. 이 기본 타입을 위해 sizestride와 같다.

다음으로 사용자가 정의한 구조체의 레이아웃을 살펴보고 아래를 플레이그라운드에 추가하자.

struct EmptyStruct {}

MemoryLayout<EmptyStruct>.size      // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride    // returns 1

struct SampleStruct {
  let number: UInt32
  let flag: Bool
}

MemoryLayout<SampleStruct>.size       // returns 5
MemoryLayout<SampleStruct>.alignment  // returns 4
MemoryLayout<SampleStruct>.stride     // returns 8
빈 구조체의 size0이다. 이것은 alignment1이기때문에 어떤 주소에도 가능하다.(모든 자연수는 1로 나누어진다) stride는 특이하게도 1이다. 그 이유는 여러분이 만든 각 EmptyStructsize0이더라도 유용한 메모리 주소를 가져야하기 때문이다.

SampleStruct의 경우는 size5이지만 stride8이다. 이것은 alignment 필요조건의 4바이트 바운더리에의해 그렇게된다. 이것을 고려해볼때 스위프트가 할 수 있는 최고의 일은 8바이트 간격으로 묶는 것이다.

다음을 플레이그라운드에 추가하자.

class EmptyClass {}

MemoryLayout<EmptyClass>.size      // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.stride    // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.alignment // returns 8 (on 64-bit)

class SampleClass {
  let number: Int64 = 0
  let flag: Bool = false
}

MemoryLayout<SampleClass>.size      // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.stride    // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.alignment // returns 8 (on 64-bit)
클래스들은 참조 타입이므로 Memory Layout은 참조 크기가 8바이트라고 알려준다.

메모리 레이아웃에대해 더 알고싶다면 Mike Ash의 멋진 글을 보자.

포인터
한 포인터는 한 메모리 주소를 가지고있다. 메모리에 직접 접근하는 타입은 "Unsafe"라는 접두를 가지는데, 따라서 이 포인터 타입은 UnsafePointer라 부른다. 이런식으로 추가적인 타이핑이 성가셔 보일수도 있다. 그러나 이것은 정의되지 않은 행동을 만들 수도 있게 해놓았을때, 컴파일러 없이 메모리 접근의 체크를 하고 있다고 (단지 크레쉬를 예방할 뿐만 아니라) 당신이나 코드를 읽는 사람이 알게 해준다.

스위프트 설계자는 C의 char*와 동일한 UnsafePointer 타입을 만들 수 있게 해놓았다. char*은 구조화되지 않은 방법으로 메모리에 접근할 수 있다. 스위프트는 그렇게 하지 않고, 대게 몇몇개의 포인터 타입을 가지는데, 각각은 다른 기능과 목적을 가진다. 포인터 타입을 가장 알맞게 사용하면, 더욱 의도에 맞게 소통하고, 더 낮은 에러, 그리고 정의되지 않은 동작을 피할 수 있게 도와준다.

언세이프 스위프트 포인터는 그 기능이 예상가능한 네이밍 형식으로, 그 포인터의 특징이 무엇인지 알 수 있게 도와준다. 가변적(mutable)인지 불가변적인지, raw한지 typed인지, 버퍼 스타일인지 말이다. 아래와같이 전체적으로 8가지 조합이 있다.



다음 섹션들에서 우리는 이 포인터 타입들에대해 배워볼 것이다.

Raw 포인터 사용하기
아래 코드를 여러분의 플레이그라운드에 추가하자.
// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count

// 2
do {
  print("Raw pointers")
  // 3
  let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
  // 4
  defer {
    pointer.deallocate(bytes: byteCount, alignedTo: alignment)
  }
  // 5
  pointer.storeBytes(of: 42, as: Int.self)
  pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
  pointer.load(as: Int.self)
  pointer.advanced(by: stride).load(as: Int.self)
  // 6
  let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
  for (index, byte) in bufferPointer.enumerated() {
    print("byte \(index): \(byte)")
  }
}
이 예제는 두 정수를 저장하고 불러오기 위해 언세이프 스위프트 포인터를 사용한다. 여기서 무슨 일이 일어나는지에대한 설명이다.
  1. 이러한 상수들은 사용된 값들을 종종 들고 있다.
    • count는 저장하기 위해 정수를 가지고 있다.
    • strideInt 타입의 stride를 가지고 있다.
    • alignmentInt 타입의 alignment를 가지고 있다.
    • byteCount는 필요한 모든 바이트 수를 가지고 있다.
  2. 스코프 레벨을 추가하기위해 do 블럭을 추가하였기 때문에, 나중에 예제에서 변수 이름을 재사용할 수 있다.
  3. UnsafeMutableRawPointer.allocate 메소드는 필요한 바이트를 할당하는데 사용한다. 이 메소드는 UnsafeMutableRawPointer를 반환한다. 저 타입의 이름에서 알 수 있듯, 포인터는 (가변의) Raw 바이트를 불러오고 저장하는데 사용될 수 있다.
  4. defer 블럭은 포인터가 적절하게 해제되었는지 확인하기위해 추가된 부분이다. 여기서 ARC는 도움이 되지 않는다. 여러분은 스스로 메모리 관리를 해야한다! 여기에서 defer에대해 더 읽어보길 바란다.
  5. storeBytesload 메소드는 바이트를 저장하고 불러오는데에 쓰인다. 두번째 정수의 메모리 주소는 그 포인터의 stride 바이트를 증가시켜서 계산된디.포인터는 stride 하기때문에, (pointer+stride).storeBytes(of: 6, as: Int:Self)로 포인터 계산 또한 할 수 있다.

  1. UnsafeRawBufferPointer는 메모리가 마치 바이트의 모음인것처럼 접근할 수 있게 해준다. 이 의미는 바이트들을 돌면서 차례로 접근할 수 있게 해주고, 서브스크립트를 이용해 접근하며 filter, map, reduce와같은 멋진 메소드까지 사용할 수 있게 한다. 이 버퍼 포인터는 raw 포인터를 초기화한다.

Typed 포인터 사용하기
typed 포인터를 사용하면 이전의 예제를 간단하게 만들 수 있다. 아래 코드를 플레이그라운드에 추가하자.
do {
  print("Typed pointers")
  let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
  pointer.initialize(to: 0, count: count)
  defer {
    pointer.deinitialize(count: count)
    pointer.deallocate(capacity: count)
  }
  pointer.pointee = 42
  pointer.advanced(by: 1).pointee = 6
  pointer.pointee
  pointer.advanced(by: 1).pointee
  let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
  for (index, value) in bufferPointer.enumerated() {
    print("value \(index): \(value)")
  }
}
아래의 다른 점들을 인지하자.
  • UnsafeMutablePointer.allocate 메소드를 사용하여 메모리를 할당한다. 이 제네릭 파라미터는 포인터로 Int 타입의 값을 불러오고 저장하는데 사용될 것이라는 것을 스위프트에게 알려준다.
  • typed 메모리는 사용하기전과 소멸하기전에 반드시 초기화되어야한다. 이것은 initialize 메소드와 deinitialize 메소드로 할 수 있다. Update: atrick라는 유저가 달아놓은 커멘트처럼 소멸은 non-trivial 타입들만 필요로 한다. 이 말은, 여러분이 non-trivial의 무언가를 바꿀 경우에 소멸을 가지고 있는 것이 여러분 코드의 훗날을 위해 좋은 방법이다. 또한 컴파일러가 이것을 최적화할것이기 때문에 항상 비용이 발생하지 않는다.
  • typed 포인터는 pointee라는 프로퍼티를 가지고 있는데, 이것은 값을 불러오고 저장할때 타입 세이프한 방법을 제공하는 프로퍼티이다.
  • typed 포인터를 증가시키면, 여러분의 숫자의 값을 원하는대로 증가해가며 나타낼 수 있다. 그 포인터는 그 타입의 포인터가 가리키는 값을 기반으로 옳바른 stride를 계산할 수 있다. 다시말해 포인터 계산 또한 가능하다. (pointer+1).pointee=6 이런것 또한 가능하다.
  • typed 버퍼 포인터와 같은 점은, 바이트 대신에 값을 차례로 반복해갈 수 있다.

Raw 포인터를 Typed 포인터로 변환하기
typed 포인터는 항상 직접 초기화 할 필요가 없다. 또한 raw 포인터로부터 만들어질 수 있다.

여러분의 플레이그라운드에 아래 코드를 추가하자.
do {
  print("Converting raw pointers to typed pointers")
  let rawPointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
  defer {
    rawPointer.deallocate(bytes: byteCount, alignedTo: alignment)
  }
  let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
  typedPointer.initialize(to: 0, count: count)
  defer {
    typedPointer.deinitialize(count: count)
  }

  typedPointer.pointee = 42
  typedPointer.advanced(by: 1).pointee = 6
  typedPointer.pointee
  typedPointer.advanced(by: 1).pointee
  let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
  for (index, value) in bufferPointer.enumerated() {
    print("value \(index): \(value)")
  }
}
이 예제는 이전 것과 비슷한데, 다른점은 처음에 raw 포인터를 생성한다는 것이다. typed 포인터는 필요한 Int 타입으로 메모리를 바인딩하면서 생성된다. 메모리를 바인딩함으로서 타입 세이프한 방법으로 접근할 수 있게 된다. 메모리 바인딩은 typed 포인터를 생성할때 이미 완료된다.

예제에서 남은 부분은 이전것과 동일하다. 한번 typed 포인터의 세계에 발을 드리는 순간 그 예시로 'pointee'를 사용할 수 있다.

한 인스턴스의 바이트를 뽑아내기
종종 현재 있는 인스턴스의 타입에서 바이트를 조사하고 싶을 것이다. 이것은 withUnsafeBytes(of:) 메소드를 호출하여 조사할 수 있다.

아래 코드를 플레이그라운드에 추가하자.
do {
  print("Getting the bytes of an instance")
  var sampleStruct = SampleStruct(number: 25, flag: true)

  withUnsafeBytes(of: &sampleStruct) { bytes in
    for byte in bytes {
      print(byte)
    }
  }
}
이 코드는 SampleStruct 인스턴스의 raw 바이트를 출력해낸다. withUnsafeBytes(of:) 메소드는 UnsafeRawBufferPointer에 접근할 수 있게 해주는데, 클로저 안에서 사용할 수 있다.

withUnsafeBytes 또한 ArrayData의 인스턴스 메모리로서 사용할 수 있다.

체크섬 계산하기
withUnsafeBytes(of:)를 사용하면 결과를 반환할 수 있다. 이것의 사용에대한 예제는 구조체에서 32비트 체크섬의 바이트를 계산하는 것이다.

아래 코드를 플레이그라운드에 추가하자.
do {
  print("Checksum the bytes of a struct")
  var sampleStruct = SampleStruct(number: 25, flag: true)
  let checksum = withUnsafeBytes(of: &sampleStruct) { (bytes) -> UInt32 in
    return ~bytes.reduce(UInt32(0)) { $0 + numericCast($1) }
  }
  print("checksum", checksum) // prints checksum 4294967269

}
reduce를 호출해서 모든 바이트를 합쳐서 ~연산자로 비트를 뒤집는다. 특별히 강력한 에러 감지는 아니지만 그 컨셉을 보여준다.

언세이프 클럽에서의 규칙
정의되지 않은 행동을 피하기위해 언세이프한 코드를 작성할때 주의를 기울여야한다. 아래 나쁜 코드의 몇 예시가 있다.

withUnsafeBytes로부터 포인터를 반환하지마라!
// Rule 1
do {
  print("1. Don't return the pointer from withUnsafeBytes!")

  var sampleStruct = SampleStruct(number: 25, flag: true)

  let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
    return bytes // strange bugs here we come ☠️☠️☠️
  }

  print("Horse is out of the barn!", bytes)  /// undefined !!!
}
절때 포인터를 withUnsafeBytes(of:) 클로저 밖으로 내보내지 마라. 그 코드가 오늘은 동작할지라도...

오직 한번에 한 타입을 바인드하라!
// Rule 2
do {
  print("2. Only bind to one type at a time!")

  let count = 3
  let stride = MemoryLayout<Int16>.stride
  let alignment = MemoryLayout<Int16>.alignment
  let byteCount =  count * stride
  let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)

  let typedPointer1 = pointer.bindMemory(to: UInt16.self, capacity: count)

  // Breakin' the Law... Breakin' the Law  (Undefined behavior)
  let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)

  // If you must, do it this way:
  typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) {
    (boolPointer: UnsafeMutablePointer<Bool>) in
    print(boolPointer.pointee)  // See Rule 1, don't return the pointer
  }
}

badpunbadpun


절때 한번에 두가지 연관된 타입을 메모리에 바인딩하지마라. 이것을 Type Punning(역자: 컴퓨터 과학에서, 타입 펀닝(type punning)이란 언어의 범주에서 달성하기가 힘들거나 불가능한 기능을 만들기 위해 언어의 형식 시스템을 우회하는 프로그래밍 기법을 말함)이라 부르며 스위프트는 pun을 좋아하지 않는다. 대신에, withMemoryRebound(to: capacity:)같은 메소드로 임시적인 메모리를 리바인드할 수 있다. 또한 이 룰은 trivial 타입(Int와같은)부터 non-trivial 타입(클래스와같은)까지 리바인드하는 것은 불법이라고 말하고 있다. 그러지 말자.

하나 더 남았다
// Rule 3... wait
do {
  print("3. Don't walk off the end... whoops!")

  let count = 3
  let stride = MemoryLayout<Int16>.stride
  let alignment = MemoryLayout<Int16>.alignment
  let byteCount =  count * stride

  let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
  let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount + 1) // OMG +1????

  for byte in bufferPointer {
    print(byte)  // pawing through memory like an animal
  }
}
현재 OBOE(off-by-one error: 원하지 않았던 추가적인 사이즈에 관한 에러) 문제는 특히 심각하게 언세이프한 코드이다. 조심하고, 검토하며, 테스트하라!

언세이프 스위프트 예제1: 압축
여러분의 모든 지식을 가져와서 C.API를 감싸볼 시간이다. 코코아는 일반적으로 데이터를 압축하는 알고리즘을 구현한 C 모듈을 포함한다. 여기서 LZ4는 속도가 중요시될때, LZ4A는 속도보다는 높은 압축률이 중요시될때, ZLIB는 공간과 속도가 균형있게 중요시되고 새로운것(오픈소스)이고, LZFSE도 공간과 속도의 균형 면에서 잘 동작한다.

Compression이라 부르는 플레이그라운드를 생성하자. Data를 사용하는 순수 스위프트 API를 정의함으로서 시작해보자.

그러고나서 아래 코드를 여러분의 플레이그라운드에 넣자.
import Foundation
import Compression

enum CompressionAlgorithm {
  case lz4   // speed is critical
  case lz4a  // space is critical
  case zlib  // reasonable speed and space
  case lzfse // better speed and space
}

enum CompressionOperation {
  case compression, decompression
}

// return compressed or uncompressed data depending on the operation
func perform(_ operation: CompressionOperation,
             on input: Data,
             using algorithm: CompressionAlgorithm,
             workingBufferSize: Int = 2000) -> Data?  {
  return nil
}
이 함수는 압축과 해제를 하는 perform 함수인데 지금은 nil을 리턴하게 해두었다. 여기에 짧은 언세이프 코드를 추가하게 될것이다.

다음 프레이그라운드 마지막에 아래의 코드를 추가하자.
// Compressed keeps the compressed data and the algorithm
// together as one unit, so you never forget how the data was
// compressed.

struct Compressed {
  let data: Data
  let algorithm: CompressionAlgorithm
  init(data: Data, algorithm: CompressionAlgorithm) {
    self.data = data
    self.algorithm = algorithm
  }
  // Compress the input with the specified algorithm. Returns nil if it fails.
  static func compress(input: Data,
                       with algorithm: CompressionAlgorithm) -> Compressed? {
    guard let data = perform(.compression, on: input, using: algorithm) else {
      return nil
    }
    return Compressed(data: data, algorithm: algorithm)
  }
  // Uncompressed data. Returns nil if the data cannot be decompressed.
func decompressed() -> Data? {
    return perform(.decompression, on: data, using: algorithm)
  }

}
entryData 타입의 익스텐션이다. 여러분은 옵셔널 Compressed 구조체를 반환하는 compressed(with:)라는 메소드를 추가했었다. 이 메소드는 간단하게 Compressed에서 compress(input:with:)라는 스테틱 메소드를 호출한다.

마지막에있는 예제사용은 현재 동작하지는 않지만, 지금 고쳐보자!

여러분이 처음 코드로 들어왔던 곳으로 스크롤을 올려보고, perform(_: on: using: workingBufferSize:) 함수를 아래처럼 만들어보자.
func perform(_ operation: CompressionOperation,
             on input: Data,
             using algorithm: CompressionAlgorithm,
             workingBufferSize: Int = 2000) -> Data?  {
  // set the algorithm
  let streamAlgorithm: compression_algorithm
  switch algorithm {
  case .lz4:   streamAlgorithm = COMPRESSION_LZ4
  case .lz4a:  streamAlgorithm = COMPRESSION_LZMA
  case .zlib:  streamAlgorithm = COMPRESSION_ZLIB
  case .lzfse: streamAlgorithm = COMPRESSION_LZFSE
  }
  // set the stream operation and flags
  let streamOperation: compression_stream_operation
  let flags: Int32
  switch operation {
  case .compression:
    streamOperation = COMPRESSION_STREAM_ENCODE
    flags = Int32(COMPRESSION_STREAM_FINALIZE.rawValue)
  case .decompression:
    streamOperation = COMPRESSION_STREAM_DECODE
    flags = 0
  }
  return nil /// To be continued
}
압축 알고리즘과 실행 작업을 위해 여러분의 스위프트 타입에서 압축 라이브러리에 필요한 C 타입으로 변환하게 된다.

다음으로 return nil을 아래로 바꾸자.
// 1: create a stream
var streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
defer {
  streamPointer.deallocate(capacity: 1)
}

// 2: initialize the stream
var stream = streamPointer.pointee
var status = compression_stream_init(&stream, streamOperation, streamAlgorithm)
guard status != COMPRESSION_STATUS_ERROR else {
  return nil
}
defer {
  compression_stream_destroy(&stream)
}

// 3: set up a destination buffer
let dstSize = workingBufferSize
let dstPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: dstSize)
defer {
  dstPointer.deallocate(capacity: dstSize)
}

return nil /// To be continued
위 코드에서 무슨일이 일어나는지 보자.
  1. compression_stream을 할당하고 소멸을위해 defer 블럭으로 준비한다.
  2. 그러고 pointee 프로퍼티를 사용하여 스트림을 가져와서, compression_stream_init 함수에 보내준다. 이 컴파일러는 여기서 좀 특별한 일을 한다. inout & maker를 이용하여 여러분의 compression_stream을 받아서 자동으로 UnsafeMutablePointer<compression_stream>으로 바꾼다.(streamPointer로 보낼 수도 있고 이 특정 변환을 필요로 하지 않을 수도 있다)
  3. 마지막으로 여러분이 작업할 버퍼인 목적지 버퍼를 생성한다.
return nil을 바꾸어서 perform 함수를 완성했다.
// process the input
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
  // 1
  var output = Data()
  // 2
  stream.src_ptr = srcPointer
  stream.src_size = input.count
  stream.dst_ptr = dstPointer
  stream.dst_size = dstSize
  // 3
  while status == COMPRESSION_STATUS_OK {
    // process the stream
    status = compression_stream_process(&stream, flags)
    // collect bytes from the stream and reset
    switch status {
    case COMPRESSION_STATUS_OK:
      // 4
      output.append(dstPointer, count: dstSize)
      stream.dst_ptr = dstPointer
      stream.dst_size = dstSize
    case COMPRESSION_STATUS_ERROR:
      return nil
    case COMPRESSION_STATUS_END:
      // 5
      output.append(dstPointer, count: stream.dst_ptr - dstPointer)
    default:
      fatalError()
    }
  }
  return output
}
이것이 실제로 일어나는 것이다. 그리고 무슨 일이 일어나는지 보자.
  1. 아웃픗(압축된 데이터든 압축해제된 데이터든, 그 작업에따라 다르다)을 담은 Data 오브젝트를 생성한다.
  2. 당신이 할당한 포인터와 그 크기와함께 소스버퍼와 목적지버퍼를 설정한다.
  3. 그리고 COMPRESSION_STATUS_OK를 반환할때까지 계속 compression_stream_process를 호출한다.
  4. 목적지버퍼는 마침내 그 함수로부터 반환된 아웃풋으로 복사된다.
  5. 마지막 패킷이 들어오면, COMPRESSION_STATUS_END가 나오고, 오직 목적지버퍼의 부분만 잠재적으로 복사되는것이 필요하다.
사용 예제에서 10000 요소의 배열이 153바이트로 압축된 것을 볼 수 있다. 나쁘지 않은 결과이다.

언세이프 스위프트 예제2: 난수 생성기
난수는 게임에서부터 기계학습에 이르기까지 많은 어플리케이션에서 중요한 부분으로 다뤄진다. macOS는 훌륭한 (암호학적으로 풀기힘든) 난수를 생성하는 arc4random(A Replacement Call 4 random)을 제공한다. 불행히도 이것은 리눅스에서 사용할 수 없다. 게다가 arc4randomInt32 난수만 제공한다. 그러나 dev/urandom파일은 무제한으로 난수를 제공한다.

이번 섹션에서는 이 파일을 읽은 새로운 정보로 완전히 타입 세이프한 난수를 만들어 볼 것이다.

hexdumphexdump


RandomNumbers라는 새 플레이그라운드를 만듦으로서 시작해보자. 이번시간에는 macOS 플랫폼을 선택했는지 확인하자.

만들고나면 원래 있던 것을 아래의 것으로 바꾸자.
import Foundation

enum RandomSource {
  static let file = fopen("/dev/urandom", "r")!
  static let queue = DispatchQueue(label: "random")
  static func get(count: Int) -> [Int8] {
    let capacity = count + 1 // fgets adds null termination
    var data = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
    defer {
      data.deallocate(capacity: capacity)
    }
    queue.sync {
      fgets(data, Int32(capacity), file)
    }
    return Array(UnsafeMutableBufferPointer(start: data, count: count))
  }
}
file이라는 변수는 static으로 선언해 놓았으므로 시스템에 오직 하나만 존재하게 될것이다. 그 프로세스가 종료될때 당신은 그것에 접근하는 시스템에 의존할 것이다. 여러 스레드에서 난수를 요구할 수도 있기 때문에 일련의 GCD큐로 접근하는 상황을 막아야한다.

get 함수가 작업이 일어나는 곳이다.  먼저, 할당되지 않은 저장소를 생성하는데, fgets은 항상 0에 종료되므로, 당신이 원하는 것보다 하나 더 많게 준비한다. 다음으로 GCD 큐에서 작업하면서 파일로부터 데이터를 가져온다. 마지막으로는, Sequence처럼 동작하는 UnsafeMutableBuffePointer로 감싸서 표준 배열에 데이터를 복사한다.

지금까지 Int8값의 배열만 (안전하게) 줄것이다. 이제 이것을 확장시켜보자.

플레이그라운드의 마지막 부분에 아래를 추가하자.
extension Integer {
  static var randomized: Self {
    let numbers = RandomSource.get(count: MemoryLayout<Self>.size)
    return numbers.withUnsafeBufferPointer { bufferPointer in
      return bufferPointer.baseAddress!.withMemoryRebound(to: Self.self, capacity: 1) {
        return $0.pointee
      }
    }
  }

}

Int8.randomized
UInt8.randomized
Int16.randomized
UInt16.randomized
Int16.randomized
UInt32.randomized
Int64.randomized
UInt64.randomized
Integer 프로토콜의 모든 하위타입에 static randomized 프로퍼티를 추자했다(Portocol Oriented Programming를 읽어보자!) 먼저 난수를 얻어서 반환된 배열의 바이트와함께 요청된 타입으로 Int8 값을 리바인드한 뒤(C++의 reinterpret_cast 처럼) 복사본을 반환한다. 간단하다! :]

이게 다다! 언세이프 스위프트를 사용한 안전한 방법으로의 난수이다.

여기서 어디로 가야할까?
여기 모든 플레이그라운드가 있다. 그리고 여러분이 더 배우려고 찾아볼 수 있는 추가적인 자료들이 있다.

여러분이 이 튜토리얼을 즐겼기를 바란다. 만약 질문이 생기거나 공유하고 싶은 경험을 겪게되면 이 포럼(링크)에서 그것을 기대하고 있겠다!



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: You Probably Don't Want enumerated

스위프트 표준 라이브러리에서 한가지 종종 오용되는 부분은 시퀀스(Sequence)의 enumerated()함수이다. 이 함수는 새로운 시퀀스를 만들어주는 데, 이 시퀀스는 원래 시퀀스의 각 요소와 그 요소에 해당되는 번호를 가진다.

enumerated()는 잘못 사용되고 있다. 이 함수는 각 요소에 번호를 제공하기 때문에 그 번호 문제에대한 쉬운 해결법이 될 수 있다. 그러나 이런 번호 문제는 더 나은 방법으로 해결될 수 있다. 그런 경우가 무엇인지 보자. 우리가 어떤 실수를 하고 있는지 보고, 이것을 추상적인 수준에서 한번 해결해보자.

enumerated()를 사용할때 주된 이슈는 이 함수가 요소와 그에 해당하는 각 인덱스(index)를 반환한다고 생각해버리는 것이다. 이 함수는 모든 시퀀스에서 사용가능하지만, 시퀀스는 인덱스를 가진다는 것을 보장하는 녀석이 아니기 때문에, 우리는 이것이 인덱스가 아니라는 것을 기억해야한다. 코드에서 index라 부르지않고 offset이라 부르고 있는데, 이 네이밍 컨벤션은 글 마지막에 소개해 놓았다. 오프셋(offset)은 항상 0에서 시작하여 각 요소마다 증가하는 정수형태를 말한다. Array의 경우 인덱스와 일치하겠지만, 기본적으로 다른 타입에서는 그렇지 않다. 아래 예제를 한번 보자.
let array = ["a", "b", "c", "d", "e"]
let arraySlice = array[2..<5]
arraySlice[2] // => "c"
arraySlice.enumerated().first // => (0, "c")
arraySlice[0] // fatalError
arraySlice라는 변수는 당연하게도 ArraySlice이다. 그러나 startIndex는 특별하게도 0이아닌 2이다. 이때 enumerated()first를 호출하면 오프셋이 0인 튜플을 반환해주고, 그 첫번째 요소인 "c"를 반환한다.

다른 방법으로 예시를 보자.
zip(array.indices, array)
실제로는 이렇게 한다.
zip((0..<array.count), array)
그리고 Array가 아닌 다른 것과 작업을 하면 언제든지 틀린 동작 결과를 만들 것이다.

enumerated()를 사용하면서 (인덱스가 아닌) 오프셋을 사용것으로 생긴 이슈 말고도 다른 이슈들이 있다. enumerated() 사용에대해 여러번 생각해볼 수 있는데, enumerated()를 사용할때 얻을 수 있는 더 나은 이점이 있다. 조금 더 살펴보자.

내가 본 enumerated()의 가장 일반적인 사용은, 다른 배열로부터 일치하는 요소를 잡기위해 열거된(enumerated) 배열로부터 오프셋으로 사용하는 것이다.
for (offset, model) in models.enumerated() {\
     let viewController = viewControllers[offset]
     viewController.model = model
}
이 코드는, 배열이된 modelsviewControllers에 의존하는데, 이 배열은 0에서 시작하고 정수에의해 색인(index)된다. 그리고 이 배열의 길이가 같다는 것에도 의존하고 있다. 만약 models 배열이 viewControllers 배열보다 짧다면, 별다른 나쁜일이 일어나지 않겠지만, viewControllersmodels보다 짧다면 크레쉬가 일어날 것이다. 또한 큰 역할을 하고 있지도 않은 추가적인 offset 변수까지 가지고 있어야한다. 더 스위프트한 방법으로 다시 짜보면 아래처럼 될 수 있다.
for (model, viewController) in zip(models, viewControllers) {
     viewController.model = model
}
이 코드는 읽는이를 집중시키며, 모든 Sequence 타입에서 동작한다. 또한 배열의 길이가 일치하지 않는 것도 알아서 처리해준다. 더 나은 방법일 것이다.

다른 예제를 보자. 이 코드는 첫번째 imageView와 그 컨테이너 사이에 오토레이아웃 제약(constraint)를 추가하고 쌍의 이미지뷰 사이의 오토레이아웃 제약을 만든다.
for (offset, imageView) in imageViews.enumerated() {
     if offset == 0 {
          imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
     } else {
          let imageToAnchor = imageView[offset - 1]
          imageView.leadingAnchor.constraint(equalTo: imageToAnchor.trailingAnchor).isActive = true
     }
}
이 코드 예제도 비슷한 문제가 있다. 우리는 쌍의 요소가 필요하지만, 고수준에서 작업할때 enumerated()를 사용하는 것은 지긋지긋한 인덱스를 다뤄가며 필요한 번호를 뽑아내야 한다는 의미이다. 이 부분도 마찬가지로 zip이 도와줄 것이다.

먼저 첫번째 요소에서 컨테이너 제약을 다루는 코드를 작성하자.
imageViews.first?.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
다음으로 이 쌍의 요소를 다루자.
for (left, right) in zip(imageViews, imageViews.dropFirst()) {
     left.trailingAnchor.constraint(equalTo: right.leadingAnchor).isActive = true
}
이제 됐다. 인덱스는 보이지 않고, 어떠한 (다중-전달) 시퀀스로 동작하므로 더 집중할 수 있게 되었다.

(한 익스텐션에 쌍으로 만드는(pairing) 코드를 넣을 수도 있고, 필요에 따라서는 .eachPair()을 호출할 수도 있다.)

enumerated()의 몇몇 유효한 사용이 있을 수 있다. 여러분이 얻어내고 있는 것이 인덱스가 아니라 그냥 정수이기 때문에 각 요소에 해당하는 (인덱스가 아닌) 번호로 작업해야 할 때가 바로 옳바른 사용 시점이다. 예를들어 여러 뷰들의 각 수직 좌표 y를 높이와 시퀀스의 오프셋의 곱으로 만들어야 한다면 enumerated()가 적절할 것이다. 아래에 구체적인 예시가 있다.
for (offset, view) in views.enumerated() {
     view.frame.origin.y = offset * view.frame.height
}
여기의 offset은 번호의 속성으로 사용되고 있기때문에 enumerated()가 잘 동작한다.

이제 간단하게 요약해보자면, enumerated()를 인덱스로 사용하고 있다면 그 문제를 해열하는데 더 좋은 방법이 있을 것이며, enumerated()를 번호로 사용한다면 좋아요를 표시한다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Swift: When to use guard vs if


최근에 내 코드베이스에서 내가 느낀 것은 guard를 디폴트로 하냐, if를 디폴트로 하냐이다. 나는 그렇게 많이 생각하지 않고 guard로 바꾼다.

그러나 이것이 문제가 되기도 하는데, guardif에는 차이점이 있고, 어느것을 사용할 것인지 생각해볼 필요가 있는것 같다.

차이점은 미묘하지만 존재한다. guard는 어떤 값이 의도한것처럼 기능하길 원하도록 표현할때 사용된다.

예를들어 try! Swift app 에서 발표 세션 타입을 표시할때, 발표 타이틀이 세션 타이틀이다.



그러나 모든 세션이 발표를 가지는 것은 아니므로 프레젠테이션은 선택적이다. 사실 특정 세션 타입에서는 발표를 표시하고 타이틀을 가질거라 기대한다. 이때가 guard의 최고의 유스케이스이다!
@objc public enum SessionType: Int {
    case workshop
    case meetup
    case breakfast
    case announcement
    case talk
    case lightningTalk
    case sponsoredDemo
    case coffeeBreak
    case lunch
    case officeHours
    case party
}


public class Session: Object {
    // this is optional because not all sessions have presentations
    // e.g. no presentation during breakfast
    open dynamic var presentation: Presentation?
    // other properties here


    /** The main name of this session */
    public var formattedTitle: String {

        switch self.type {
        case .talk, .lightningTalk:
            // for the talk / lighting talk session type
            // we expect the presentation to be there
            // if it's not there, it's a fail, so guard is used
            guard let presentation = presentation else { return defaultTitle }
            return presentation.localizedTitle
        // other cases continued...
        }
    }
이 발표 타이틀은 항상 발표 세션 타입을 위해 표시될 수 있다. 만약 없다면, 실패한다. 이것이 이 경우에 왜 guard를 써야하는지의 이유이다.

그러나 다른 경우도 생각해보자. 쉬는시간세션(coffee break session)은 스폰서를 받을수도 있다. 이 경우, 쉬는시간의 타이틀에 스폰서 이름을 넣을 수 있다. 스폰서가 있으면 스폰서의 이름을 넣고, 없으면 넣지 않는 두가지가 다 맞는 경우이다. 이 경우가 if를 사용할 수 있는 경우다.
public class Session: Object {


    /** A sponsor, if any, responsible for this session. */
    open dynamic var sponsor: Sponsor?


    /** The main name of this session */
    public var formattedTitle: String {

        switch self.type {
        case .coffeeBreak:
            // some sessions are sponsored, some aren't
            // it's not a fail if there is no sponsor
            // so if is used
            if let sponsor = sponsor {
                return "Coffee Break, by \(sponsor.name)".localized()
            }
            return "Coffee Break".localized()
        // other cases continued...
        }
    }
@ecerney puts it so well의 말처럼, guard를 약한 Assert로 생각하면 된다.

if절처럼 guard는 불리언값의 표현에따른 상태를 실행한다. if절과는 다르게, guard절은 조건이 충족되지 않을때만 실행된다. guardAssert처럼 생각할 수 있는데, 크레쉬를 내버리는 Assert가 아니라 우아하게 빠져나올 수 있는 Assert이다.

그러니 guard를 쓰기전에 if를 생각해보자!


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: My Development Toolset 2017 for iOS


모두들 반갑다! 내가 현재 맥북에서 사용하는 iOS 개발 툴, 잡다한 것, 서비스, 웹사이트, 프레임워크 툴에 대해 소개하고자 한다.

  • Git 사용을 위한 GitKraken를 추천한다. GitKraken은 자동으로 GitFlow를 추가할 수 있다.
  • 이슈에 대해서는 이 Gitscout를 따로 쓰고 있다.
  • GitBar는 커밋되지 않은 소스코드를 상기시켜준다.
  • Build Time Analyzer는 여러분 프로젝트의 스위프트 빌드타임을 쪼개에 보야주는 macOS용 앱이다.
  • Tomato One를 사용해서 Pomodoro Technique로 효율을 증가시키자.
  • WatchDog는 Xcode를 끄거나 macOS를 재시작하지 않은채 자동으로 DerivedData를 정리해준다.
  • Cakebrew는 GUI로 Homebrew를 관리한다.
  • Liya는 하나의 인터페이스에서 MySQL, PostgreSQL, SQLite3에 접근할 수 있다.
  • Quiver는 코드 조각 메니저이고 메모나 코드, 파일을 저장해놓는 노트이다. 5년동안 snippets를 사용했었는데, 스위프트가 추가되었다는 이유로 이것을 사용했다. 그러나 이제 바꿀때가 된것 같다.
  • Oh My Zsh와함께 터미널을 쓰면 터미널이 눈과 두뇌를 가지게 될 것이다.
  • 나중에 읽고 싶으면 Pocket에 담아두자. 사파리나 크롬 익스텐션은 설치하지 말자. LINER 이것도 있다.
  • 이미지를 작게 만드려면 Squash를 사용하자. 무료의 솔루션으로는 guetzli도 있다.
  • 여러분이 Sketch는 알거라 생각된다. 그러면 Zeplin라는 것도 있는데, 이것도 확인해보자.
  • 인터렉트 레이아웃 텍스트나 모든 화면 크기를 확인하고자할때는 RevealApp를 쓰자.
  • SizeUp는 단축키로 여러분의 창 크기를 조정하고 위치를 조정해준다.
  • BitBar는 모든 스크립트 출력을 macOS 메뉴바에 보여준다. 더 중요한 것은 리뷰의 평균 점수, 앱 상태, 버전, 이런 당신이 필요로하는 어떤것이든 넣을 수 있다. dev 플러그인을 확인하는 것을 잊지말자.
  • LittleIpsum 단어나 문장, 문단을 생성해준다.
  • 지금은 코드 snippets을 사용하고 있다.코드 snippets이 어디에 쓰였는지, 언제 쓰였는지 알고 싶다면 Paste는 클립보드 관리자이다.
  • Gitsome은 터미널에서 쓸 수 있는 멋진 Git/GitHub 커멘드라인 인터페이스이다.
  • Easy APNs Provider는 최고의 푸시 노티피케이션 테스팅 툴이다.
  • Houston은 애플 푸시 노티피케이션을 위한 간단한 gem이다.
  • Bee는 JIRA 클라이언트, JIRA 에자일 클라이언트, 깃헙 이슈 클라이언트, FogBugz 클라이언트, 마크다운 편집기의 모든 기능이 들어있다.

팟케스트
여러분이 팟케스트를 들을 시간이 있다면 PodcastMenu는 내가 좋아하는 맥용 앱이다.
  • Fedrico Viticci, John Voorhees의 AppStories를 확인해보아라.

프레임워크
  • 내 프로젝트에서 가짜 데이터가 필요하다면 Fakery를 추천한다.
  • LocalizationKit는 스위프트로 다이나믹 다국어 번역 배달 시스템이다.
  • Armchair는 스위프트로 작성된 간단하지만 강력한 앱 리뷰 관리자이다. iOS와 macOS용이 있다.
  • Siren는 설치된 iOS 앱의 버전을 확인하고 새 버전이 출시되었을 때 알림을 준다.
  • SwiftGen는 자동 스위프트 코드 생성을 위한 코드 생성기이다.
  • Bohr는 화면을 설정하기위한 초기 세팅을 할 수 있게 해준다.
  • SwiftyJSON는 JSON 파싱계의 최고봉이다. 만약 문제를 겪으면 kitura(링크) 버전을 확인해보자.
  • SwiftyBeaver는 스위프트를 위한 다채롭고, 유연하며, 가벼운 로그 툴이다. 그리고 탐색, 검색, 필터링을 위한 맥용 앱을 제공한다.
  • 스위프트에서 JSON 파싱 라이브러리의 대안으로는 ✨ Gloss가 있다.
  • Hero는 커스텀된 뷰컨트롤러 트렌지션을 제공한다.



웹사이트
  • AppSight는 iOS 모바일 앱에서 회사가 어떤 SDK와 서비스를 사용했는지 찾아준다.
  • iOSCookies는 스위프트로 작성된 iOS 라이브러리 컬랙션이다.
  • Ole Begemann는 iOS에대한 거장의 블로그이다.
  • littlebitesofcocoa는 iOS와 맥을 위한 팁과 기술이다.
  • 멋진 모바일 엔지니어링 블로그이다. 당신은 Toptal에 있는 모든 글을 읽으면 좋을 것이다.
  • Erica Sadun는 깊은 iOS 블로그이다.

잡다한것

서비스
  • 로컬에 MongoDB, MySQL, Jenkins, Minecraft을 설치하고 싶지 않으면 Docker가 그것에대해 혹은 그 이상으로 도움을 줄것이다. Docker를 사용하면 여러분의 프로젝트나 프로토타입을 위해 백엔드, 데이터베이스, 배포된 앱을 빌드할 수 있다. 스위프트를 위한 Docker를 확인해보자.
  • 여러분이 Docker를 사용하고 있다면, KitematicCaptain를 사용하면 좋다.
  • 클라우드 컴퓨팅 플랫폼이 필요하면 나는 digitalOcean를 즐겨쓴다.
  • 프로젝트를 관리하는데는 Asana를 사용한다.
  • rollout.io는 앱에서 크래쉬를 고치고 파라미터를 재정의한다.
  • GoogleAnalytics는 제품 사용을 분석해준다.
  • Zoommy에서는 한 곳에서 무료 사진을 찾을 수 있다.
  • heNounProject는 무료이며 높은 품질의 아이콘을 가지고 있다.

편집
  • Ulysses는 발행에있어서 최고의 맥용 앱이며, 마크다운 편집기, 글을 쓰는데 좋다.
  • Podfile과 함께 작업할때는 나는 Atom를 더 선호한다.
  • node.js나 express.js로 작업한다면 Brackets 사용을 선호한다. 여러분이 WWDC2016(링크) 테마를 다우받고 싶으면 여기서 다운받을 수 있다.

이게다다. 읽어줘서 고맙다. 이 모든 툴이 여러분의 생산성에 도움이 되길 바란다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Design words with data


우리가 작성하는 것을 어떻게 데이터로 알 수 있을까?

쓰는것은 양식의 예술이다. 단어는 우리를 웃게 만들기도 하고, 울게 만들기도 하며, 멋진일을 하는데 영감을 주기도 한다.

그러나 나는 쓰기에도 과학이 있다는 것에 대해 오늘 이야기해보고 싶다. 우리는 데이터로부터 어떻 용어를 쓸지에 대한 정보를 제공받고 그 용어에대해 객관적으로 생각하게 해준다.

무엇이 옳고 무엇이 그른가?
나는 드롭박스의 UX 작성자로서, 우리의 목표는 모든 사람들이 우리가 사용하는 모든 단어를 이해할 수 있게 만드는 것이다. 잘못된 단어 하나라도 사용자 경험을 파괴할 수 있다. 모호한 버튼의 글자 하나나 익숙하지 않은 용어는 사용자를 쉽게 낙담시킨다.

우리가 옳바른 단어를 골랐음을 확신하려면, 쓰기에대해 정보에 근거한 선택을 하기위해서 조금 특별한 기술들을 사용하고 있다.

1. Google Trends
몇몇의 용어들 중에 적절한 것을 선택해야한다고 가정해보자. 예를들어 다음 중에 어느것을 여러분 제품에 사용할 수 있을까?

* Log in
* Log on
* Sign in
* Sign on

첫번째로 소개할 것은 Google Trends이다. 쉼표를 기준으로 비교하고 싶은 단어를 모두 입력해 넣으면 된다. Google Trends는 사람들이 Google에서 이 단어를 얼마나 자주 검색했는지 비교해준다. 그 검색은 자동으로 "페이스북은 log in" 혹은 "Sign in은 안된다" 같은 말도 포함된다.

그래서 Google Trends는 어떤 결과물을 보여줄까?

Google Trends를 사용한 용어비교Google Trends를 사용한 용어비교


짜잔! 여기선 깔끔하게 Sign in이 이겼다. 이 말은 사용자들이 이 동작을 부를때 "Sign in:을 더 많이 쓰는 것으로 보인다. 당신이 사용자 경험에 단어를 맞추려면, 다른 것보다는 "Sign in"을 고르는게 더 안전할 것이다.


드롭박스에서는 "Version history" 기능을 표현할때 각기 다른 용어를 쓴다는 것을 발견했다.


우리는 이런 부조화를 고치고 싶었으나 어떤 용어를 사용해야할지 확신이 서지 않았다. "version history"라 해야하나, 아니면 "file history"? 우리는 여러개 고려대상 중, Google Trends의 결과로 그 한가지를 뽑아냈다.

Google Trends에서는 "version history" 검색이 더 많아보이게 나왔고, 우리가 제품 전반에서 "version history"라 부르는 가장 큰 이유가 이것이다. 

2. Google Ngram Viewer
Ngram Viewer는 Google Trends와 비슷한데, 구글이 스캔한 출판된 책에서 검색한다는 점만 다르다. 여러분은 이 데이터를 이용하여 여러분의 언어에서 어떤 용어가 더 일반적으로 쓰이는지 볼 수 있다.

드롭박스는 최근에 iOS앱에 새로운 서명 툴을 출시했다. 여러분의 서명을 그리는 화면에서, 이것을 검토하기 전에는 "Sign Your Signature"이라 적어놓았었다.

우리도 "Sign Your Signature"가 이상하게 보인다는 점을 인지하고 있었으나, 이상하게 보인다는 이유로 이것을 바꿀 순 없었다. 어떻게 이 변경을 납득시켰을까?

Ngram Viewer를 보여주며 "Sign Your Signature"과 "Sign Your Name"을 비교했을 때이다. 이것은 "Sign Your Signature"이 좋지 않음을 보여주었다. 이 데이터를 팀에게 보여주니 그들은 빠르게 "Sign Your Name"으로 바꾸어주었다.

Ngram Viewer를 사용한 용어 비교Ngram Viewer를 사용한 용어 비교


3. 가독성 테스트
몇년동안 언어 전문가들은 여러분의 단어가 어떻게 쉽게 이해되는지 측정해주는 여러 가독성 테스트를 개발해왔다.

이 테스트의 많은 것이 여려분이 쓴 것의 등급을 매겨줄 것이다. 예를들어 8등급이라는 의미는 US에서 일반적으로 8등급이 여러분이 쓴 거을 이해할 수 있다는 뜻이다.

나는 이 테스트중 하나를 사용해 내 Medium 이야기(How to design words)를 하나 돌려보았다. 아래에 그 결과이다.

Readability-Score.com의 결과Readability-Score.com의 결과


여기서 아주 흥미로운 데이터를 얻을 수 있다. 예를들어
  • 나는 6등급으로 이야기를 썼다.
  • 내 어조가 중립적이지만 약간 긍정적이다.
  • 나는 문장당 평균 10.7 단어를 쓴다(드롭박스에서는 문장을 15단어 이하로 쓰려고 노력하고 있다).

이런 테스트 중 하나를 받아보고 싶다면 아래에 몇 시도해볼 수 있는 링크를 준비해놓았다. 이 중 몇개의 테스트는 더 좋은 가독성으로 작성하기 위한 수정도 제안해줄 것이다.

4. 리서치 조사
새 기능에 어떤 이름을 붙일지 알아보고 있는가? 아니면 어떤 가치관에 집중하는가(Or what value prop to focus on)? 이런 상황에서는 리서치 조사를 세팅하는게 도움이 될 수 있다.

많은 조사 툴이 여러분의 타겟 대중을 선택할 수 있게 해준다. 따라서 잠재적 사용자로부터 쉽게 피드백을 받을 수 있다.

아래에는 여러분이 연구 조사를 설정할 수 있는 몇가지이다.
드롭박스는 우리 제품을 사용하는데 가장 큰 이점이 무엇인지 물어보는 설문조사를 시행했다. 대부분의 사람들이 "접근"이라고 대답했다(어떠한 기기에서도 내 파일을 가져올 수 있는 능력). 결론적으로 우리는 대문 페이지를 '접근'에 맞춰 다시 디자인하게 되었다.


5. 사용자 연구
사용자 연구는 여러분이 작성한 것에 대한 피드백을 받을 수 있게 해준다. 일반적으로 사용자 연구에서는 여러 사람들 초대해 글을 읽게 하거나 제품을 사용하게 한 다음 그것에 대해 질문을 한다. 이런 것은 여러분이 작성한 것이 이해되었는지 아닌지 알아보기에 매우 좋다.

우리의 조사자 중 한명은 최근 새로운 흐름을 테스트했던 연구를 진행했다. 거기서 말한 한 단계이다.
Select "Remove local copy" to save space
사용자가 이 기능을 사용할때 질문해보았다. 대부분 이 기능을 이해나는데 힘들었고, 유용하다고 생각되지 않았다. 그래서 사용자에게 유리한 말을 앞쪽으로 놓음으로서 단어의 순서를 바꾸었다.
Save space by selecting "Remove local copy"
이번에는 참여자들이 이 기능을 사용할때 훨씬 쉬웠다고 말해주었다. 그리고 우리는 모든 단어의 순서를 바꾸었다.

자성자의 직감이 어떻게 경험으로 바뀔수 있는지 보여주고, 여러분은 다른 디자인 선택처럼 테스트할 수 있다.

마음으로 쓰고 머리로 고치자
특정 용어를 선택하려 할때는 데이터가 유용할 수 있다. 그러나 이 말뜻이 기계처럼 작성하라는 의미는 아니다.

내가 본 바로는, 먼저 마음으로 초안을 그린다. 당신의 결단력을 믿자. 그러고나서 당신의 생각을 써내고 이제 단어를 다듬기위해 조사나 데이터를 찾아보면 된다.

글쓰기는 예술이자 과학이다. 마음으로 쓰고 머리로 고침으로써 근거있고 유악한 것을 만들어낼 수 있다.

데이터는 작성자에게 편리함을 제공한다. 데이터는 여러분이 쓴 것이 "옳도록" 만든다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: UI Animation: Please Use Responsibly



지난 몇년간 프로토타입 툴이 터져나왔는데, 이것은 디자이너들에게 세부적인 인터렉션을 만들 수 있는 기회를 주게 되었다. 디자이너는 이 틀울 배우는데 몇시간이나 몇일을 쓰고 있다. 그러나 많은 디자이너들이 프로토타이핑 툴의 선택의 기술적 세부사항에서 놓히고있는게 있다. 디자이너들은 몇걸음 뒤로 물러서서 애니메이션의 목적과 목표가 무엇인지 질문을 던저여한다. 어떤 의도로 애니메이션을하면 모션은 의사소통을위한 도구로서 사용된다. 디자이너들은 사용자에게 방해가되는 경박한 애니메이션 사용을 피해야한다.

디자이너들은 디자인 문제를 해결하기위한 이점으로서 애니메이션을 사용해야한다. 예를들어 계속성을 보여준다던지 들어가고 나오는 애니메이션을 통한 오브젝트 사이에 관계를 보여준다던지 하는 것들이 있다. 선택에대한 개선을 위해 인식 로드(cognitive load)를 줄임으로서 의미있는 애니메이션은 사용자를 기쁘게하고 정보를 제공한다.

인식 로드를 줄이기
인식 로드는 작업을 완료하기위해 필요한 정신적 노력의 양을 말한다. 위키피티아에서 "큰 인식 로드는 작업 완수에 부정적인 영향을 줄 수 있다"고 말하고 있다. 사용자가 사이트에 들어와있을때, 그들은 정보를 넘치도록 받는다. 그 결과 '터널 시야'라고 알고있는 선택적 태도를 가지는데, 화면에서 그들이 작업하고 있는 부분에 직접적으로 집중한다는 의미이다. 따라서 사용자는 화면에 있는 모든것을 보지는 않을 것이다.

디자이너로서, 사용하기 쉬운 인터페이스를 만드는 것은 중요한 일이다. 이것은 사용자가 작업을 완수하는데 드는 정신적인 노력을 가볍게 해준다. 디자이너들은 이런점을 어떻게 애니메이션으로 달성할 수 있을까? 우리가 모션을 잘 사용할때, 이것은 사용자 피드백과 효과적으로 소통하여 사용자 혼란을 줄여준다. 추가적으로 사용자 생각을 없애므로서, 페이지의 "액션을 부른다"던지 그런 더 중요한 것들에 주의를 기울일 수 있게 된다.


사용자의 주의를 끌기
NNgroup은 "애니메이션을 써서 효과적인 방법은 사용자의 시선을 끄는 것이다"고 말했다. 사용자의 시선을 끌게되면 스크린과 화면안의 요소들 사이의 계층과 관계를 보여주는데 집중시켜 가이드를 할 수 있게 해준다는 이점이 있다.

애니메이션은 사용자 화면에 바로 놓이면 안된다. NNgroup 글에서는 주변의 뷰 중에 움직임은 주변의 잠재적 위험을 인식하는 생물학적 특성덕분에 사용자 시선을 빠르게 끌 수 있다. 그러나 사용자가 의된 사이즈의 사이드바나 헤더에 있는 전형적인 베너나 팝업에 주목하지 않으려고 해왔는데, 디자이너들은 UI와 애니메이션이 이 패턴을 밟지 않도록 반드시 확신해야한다. 디자이너들은 한번에 최소한의 모션만하고, 관련된 정보가 있을것이라 사용자가 생각하는 위치에 요소를 애니메이션하여 무분별한 베너를 피할 수 있다.

시각적 계층
화면에서 페이지 요소가 로드되는 순서를 애니메이션하는 것은 페이지의 시각적 계층을 보여줄 수 있다. 미묘한 애니메이션을 이용하여 페이지의 레이아웃을 사용자게게 보여줄것이고 인식 로드를 줄이는 것에 초점을 맞출 수 있다.

추가적으로 다른 요소에대해 같은 애니메이션을 사용하여, 그 요소들이 그룹화되있고 비슷한 동작을 취한다는 이해를 돕는데 도움이될것이다. 사람의 마음은 항상 새로운 패턴을 찾고있다.

더 나은 선택을 만들기
미시 인터렉션(microinteraction, 한 오브젝트의 이동)과 거시 인터렉션(macrointeraction, 오브젝트 사이의 이동)에서의 모션은  사용자 문맥을 제공하고 시스템을통한 지속성을 보여준다. 또한 모션은 새로운 인터렉션이나 제스처를 사용자에게 가르쳐줄 수 있다. 잘 사용된 애니메이션은 정보들이 어떻게 맞아떨어지는지 빠르게 이해할 수 있게 해주며 결과적으로 더 나은 결정을 할 수 있게 해준다.

애니메이션은 발견할 수 있게 해주는 능력을 가지고있다. 상태간의 트랜지션은 한 오브젝트의 기능을 소통할 수있게 한다. 예를들어, 메뉴 아이콘이 닫기 아이콘으로 바뀌어서 사용자에게 같은 버튼이 두 동작을 완료한다는 것을 보여준다.

애니메이션은 사용자가 공간적 정보의 마음의 지도를 만드는데 도움을 준다. 작은 화면에서는 이것이 중요하다. 한 사용자가 화면미로안에서 쉽게 길을 잃어버릴 수 있다. 예를들어 사용자가 오른쪽으로 계속 스와이핑하여 페이지 깊숙히 들어간다. 몇몇 사용자는 왼쪽으로 스와이핑하여 메인페이지로 돌아갈 수 있다는 것을 발견할 것이다. 발견가능성을 보장하기위해, 애니메이션과함께 시각적인 행동유도성(affordances)이 사용되어야 한다.

적응시키기(Onboarding)
애니메이션은 인터렉티브한 적응을 도와준다. 적당한 시점에 적절한 것을 사용자에게 보여줌으로서, 디자이너들은 진행하는 것을 보여준다(Designers are creating progressive disclosure). 진행 모습은 사용자가 봐왔던 잡다한 것을 줄여서 시스템을 이해하기 쉽게 만들어준다. 앱이 어떻게 동작하는지 새로운 컨텐트를 배우는동안 정밀한 애니메이션은 편하게해준다. 결과적으로 이것은 사용자에게 무엇이 중요한지 기억하는데 도움이 된다.

기쁨을 만들기
애니메이션은 사용자가 기쁘고 사용자 경험을 증진시키는데 멋진 방법이다. 그러나 애니메이션으로 사용자를 기쁘게하기전에 디자인너들은 사용자의 기본 기대를 충족시켜줘야하고 깨진것을 번저 제거해야한다. 그렇지않으면 즐거움이 없는 애니메이션을 느낄것이다. 애니메이션의 빈도(frequency), 지속(duration), 속도(speed)는 사용자의 시스템 이해에 영향을 준다. 이것이 왜 디자이너들이 애니메이션을 만들때 심사숙고해야하는지 그 이유다.


기쁨을 위한 애니메이션 추가를위한 적절한 장소는 그들이 원하는 것을 보여주면서 놀라게 해줄때이다. 예를들어 무료 배송같은 것들이다. 애니메이션은 사용자에게 방해가 되지 않아야한다.

빈도
디자이너는 사용자에게 이 애니메이션이 얼마나 자주 나타날지 생각해야한다. 첫번째로 보여지는 애니메이션은 신선하고 사용자를 기쁘게할 수도 있지만, 신선함과 기쁨은 그 후에 성가신것이 될수 있다. NNGroup은 사용자 테스트 세션동안 다음을 찾아냈다. 참가자들이 말한 내용이다: "이 [애니메이션]은 처음 보기엔 멋지지만 지금은 성가신것이 되었다"

지속
디자이너는 사용자들이 애니메이션을 기다리다가 그 작업을 포기하기 전에 얼마나 기다릴 수 있는지 알아야한다. NNGroup 연구는 100ms까지는 사용자에게 나타나야함을 보여준다. 애니메이션이 좀 길게 걸린다는 것을 파악하고, 그 시간 범위는 150ms에서 350이다. Val Head에 따르면 일반적인 애니메이션 가이드라인은 200ms에서 500ms 사이에 실행되는 것이다. 그 목적은 애니메이션이 자연스럽게 보이게 만들기 위함이다. 사용자들은 보기에 친숙한 것으로 식별하기때문에 결국 디자이너의 결정에 달려있다. 애니메이션이 얼만큼의 빠르기로 실행되어야하는지 적당한 선이 있는데, 너무 빨리 빠르면 사용자가 너무 느리게 놓칠 수 있으며, 너무 느리면 사용자는 시스템이 느리다고 인식 할 수 있습니다.

속도
애니메이션의 전반적인 속도는 성능의 인지에 영향을 줄 수도 있다. 느린 애니메이션은 전반적인 시스템이 느리다고 생각하게 만들 것이다. 그러나 지연을 숨기고 사용자가 인지하는 성능 증가를 유지하기위해 애니메이션을 사용할 수도 있다.

로딩 애니메이션은 시각적 피드백과 함께 나타나 사용자를 잡아둔다. 결과적으로 사용자는 기다리는 시간을 더 짧게 느낀다. 애니메이션의 사이클 수는 지각하는 속도를 높힐 수 있다. 페이스북은 스켈래톤 컨텐트 로딩 애니메이션을 가지고 있다. 이것은 전통적인 로딩 스피너로 만든 우아한 솔루션이다. Viget의 연구에서는 사람들이 일반적인 애니메이션보다 브랜드로된 로딩 이미지에대해 더 길게 기다릴 것이라는 점을 발견했다.


접근성
디자이너들은 애니메이션 접근성을 고려해야한다. 무질서하게 개별적인 모션은 현기증나고 지겹고 혼란스럽게 만들 수 있다. iOS7에서 화면 이동(transition)이나 백그라운드로 가는 애니메이션을 선택적으로 끌 수 있게 해놓은 이유이다. 백그라운드에있는 방식은 포그라운드보다 더 천천히 움직이는 시간차 스크롤은 올바르게 사용되지 않는다면 혼란을 만들 수도 있는 모션의 예시이다. 이것을 해결하기 위해서는, 디자이너들이 '큰' 동작의 애니메이션을 자동으로 실행시키지 않도록 해야한다. 사용자들이 애니메이션을 시작한다면 좀 더 준비되있을 것이고 방심한 틈을타 당하지 않을 것이다.


웹 접근성는 애니메이션이 적용된 컨텐트를 확인하는것이 텍스트 형태로 표현되는 것을 추천한다; 시각적으로 감소된 컨텐트와 무질서하게 사용자에게 접근하는 것을 접근하게 해준다. 디자이너들은 영향이 없는 사용자들이 그 작업흐름을 빨리하기위해 애니메이션을 끌 수 있어야한다는 상황도 알고 있어야한다. 제일 좋은 방법은 W3C 가이드라인에 따른 애니메이션 접근성을 만들기 위해서는 Web Accessibility를 확인하라.



디자이너들은 마음속에 아래 습관들을 기억하고 있어야한다.

체크리스트:
목표 : 애니메이션이 사용자에게 전달하고 싶은 것이 무엇인가?
초점 : 사용자의 현재 초점이 어디인가? 사용자가 찾고 있는 것이 무엇인가?

모션의 메카닉:
빈도 : 애니메이션을 얼마나 자주 실행하나?
지속 : 애니메이션을 얼마나 길게 실행하나?
속도 : 애니메이션을 어떤 속도로 실행하나?
발생 : 어떻게해서 애니메이션이 발생하나? 사용자 동작으로부터? 아니면 자동적으로?
접근성 : 사용자가 애니메이션을 껐을때 어떻게 사용자 경험에 영향을 줄 것인가?

디자이너들은 체크리스트의 항목으로 모션 스타일 가이드를 만들고 싶어할 것이다. 컨텐트와 시각적인것은 스타일 가이드가 있으며, 리스트에 애니메이션을 추가함으로서, 디자이너들은 전반적으로 일반적인 경험을 만들고 싶어한다. Google의 Material design and Slaesforce의 Lightning Design 좋은 예시 자료이다.

참조:



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: How to use colors in UI Design


실질적인 팁과 도구들


색은 모든것과같고(Color is like everything else), 알맞게 사용하는 것이 최고이다. 여러분의 칼라 스킴에서 색을 최대 세가지로 제한하면 더 나은 결과물을 낼 수 있을 것이다. 디자인 프로젝트에 칼라를 적용시키는것은 균형을 맞추는 수많은 작업을 가지며, 더 많은 칼라를 사용할수록 그 균형을 맞추기 더 복잡해진다.


칼라는 디자인에 좋은 품질을 더하지 않는다-더 강력하게 만든다.
Pierre Bonnard

여러분 팔랫트에 정의해놓은 것에서 색을 추가해야 한다면, 쉐이드와 틴트를 사용하자. 이것은 다른 톤으로 작업할 수 있게 해준다.

60-30-10 규칙
이 인테리어 디자인 규칙은 시대에 상관없이 칼라 스킴을 쉽게 서로 놓게해주는 데코레이팅 기술이다. 60% + 30% + 10% 균형은 칼라에 균형을 맞춰준다. 이 공식은 균형에대한 이해를 만들어주고 집중하는 지점에서 다음까지 눈을 편안하게 이동할 수 있게 해주기때문에 잘 먹힌다. 또한 사용하기에도 매우 간단하다.

60%는 주요 색상(dominant hue)이고, 30%는 두번째 칼라, 그리고 10%는 강조하고싶은 칼라이다.



칼라의 의미
과학자들은 몇세기동안 특정 칼라에대한 심리적 영향을 연구해왔다. 미학 외에도 칼라는 감정(emotions)과 연상(associations)의 창조자다. 칼라의 의미는 문화와 환경에따라 다양하다. 이것이 왜 흑&백 패션 매장을 보는지의 이유이다. 그들은 우아하고 숭고해보이고 싶다.


  • 빨강색: 열정의, 사랑의, 위험한
  • 파랑색: 고요한, 책임감있는, 안전한
  • 검정색: 미스터리한, 우아한, 악의
  • 하얀색: 순수한, 고요한, 깨끗한
  • 초록색: 새로운, 신선한, 자연의

더 많은 리스트를 보고싶으면 color culture를 확인해보자.

먼저 그레이스케일부터
디자인에서 처음부터 칼라와 톤으로 작업하고싶어하지만, 이런 행동은 당신이 주요 칼라를 조정한다고 3시간을 쓰고있다는 것을 알아차렸을때 당신은 배신해버릴것이다... 매우 매혹적이긴하나 이런 태도를 피하는 법부터 배워야한다.

대신에 공간을 정하고 요소들의 레이아웃을 잡도록 해야한다. 이렇게하면 여러분의 많은 시간을 단축시킬 것이다. 몇 제약은 매우 생산적이다. 필립(flips)은 지루하지 않게 보여야한다. 좋게 보이려면 다른 톤을 시도해보자.


순수한 그레이스케일과 점정으로부터 떠나기
내가 배웠던 가장 중요한 칼라 기법중 하나는 채도없는 그레이 칼라 사용을 피하는 것이다. 실제 세상에서 순수한 그레이 칼라는 거의 존재하지 않는다. 검정도 마찬가지이다.

여러분의 색에 조금이라도 채도를 항상 넣자. 잠재적으로 사용자에게 더 자연스럽고 친근하게 보일 것이다.




자연을 믿자
최고의 칼라 조합은 자연에서보터 나온다. 그들은 항상 자연스러워보인다. 디자인 솔루션을 위한 환경을 찾는 가장 좋은 방법은 항상 팔레트가 바뀐다는 점이다.
영감을 얻기 위해서는 단지 주변을 둘러보면 된다
대비를 기억하자
몇 칼라들은 서로 잘 맞지만, 다른 것들은 그렇지 않을 수 있다. 칼라 휠에서 가장 잘 관찰되는 인터렉트가 무엇인지에대한 명확한 규칙들이 있다. 이 방법을 알고 있어야하지만 손수 할 필요는 없다.


여러분이 칼라 이론에대해 더 배우고 싶다면 이 글을 확인해보자-Color Theory For Designers: Creating Your Own Color Palettes


영감을 얻자
UI 참조에대해 이야기할때는 dribbble이 최고이다. 칼라로 검색하는 도구도 가지고 있어서, 다른 디자이너들이 사용한 특정 칼라에대한 시각적 조사를 하고싶으면 여기로 가보자 dribbble.com/colors


비디오, 출력 디자인, 인테리어 디자인, 패션... 그 많은것이 한곳에 모여있다. 이 팔랫트에서만 있지말고 관심있는 모든것을 저장하자.

  

종종 나는 KPOP 비디오클립에서 칼라를 따내기도한다. 이것들은 매우 훌륭하다.

도구들
쉽게 작업하기위해, 2017년에 사용할 수 있는 칼라 파랫트의 최고의 툴들을 묶어보았다. 이것들은 여러분의 시간을 많이 단축시켜 줄것이다.

Coolors.co
칼라를 선택하는데 명백하게 내가 제일 좋아하는 툴이다. 선택된 칼라에 락을 걸어놓고 공간을 누르면 팔랫트를 만들어준다. 또한 Coolors는 이미지를 업로드하여 그 이미지로부터 칼라 팔랫트를 만드는 기능도 제공한다. 이렇게 멋진 기능은 한 결과물에대해서만 제한이 없지만 대신에 참조 포인트를 수정할 수 있게 해주는 picker를 가진다.



Kuler
이 툴은 어도비에서 나온 툴이며, 긴 시간동안 우리 곁에있었다. 브라우저에서 데스크탑 버전으로 사용할 수 있다. 데스크탑 버전을 사용하고 있다면 포토샵에 칼라 스킴을 내보낼 수 있다.


Paletton
Kuler와 비슷하지만 다섯개의 톤으로 제한되지 않는다는 점이 다르다. 톤을 추가해가면서 주요 칼라가 필요할때 좋은 툴이다.


Designspiration.net
칼라 팔랫트에대한 아이디어는 있지만 그것을 섞은 예시자료가 필요하다고 생각해봐라. Designispiration는 이런 당신을 위한 좋은 도구이다. 5개의 칼라를 골라서 여러분의 쿼리에 맞는 이미지를 검색할 수 있다. 특정 팔랫트로 이미지를 찾을 수 있을 뿐 아니라 디자인에서 실제로 구현된 것이다.

Shutterstock Lab Spectrum
내가 고른 칼라로 사진을 찾고 싶으면 어떨까? 흠, Shutterstock는 특정 톤으로 사진을 찾는 Spectrum이라는 도구를 가지고있다. 검색에 뭔가 쳐넣을 필요가 없는데, 물방울표시로 작은 미리보기 이미지가 팔랫트를 생성해줄 것이기 때문이다.


Tineye Multicolr
그러나 사진에 있는 여러 칼라를 찾고 싶고, 각각의 양을 고르고 싶으면 Tineye가 도와줄 것이다. 이 웹사이트는 Flickr로부터 1000만개의 창의적인 일반 이미지를 데이터베이스에 쓰고 있다.
<동영상, viemo>

마지막의 생각들
칼라는 마스터하기에 힘든 개념이고, 특히 디지털 영역에서는 더 그렇다. 위에서 말한 팁들은 올바른 칼라를 선택하는 일에 도움이 될 것이다. 멋진 색채 스킴를 만드는 법을 배우는 가장 좋은 방법은 여러분 자신을 위해 열심히 연습하고 색을 가지고 노는 것이다.


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Naming Things in Swift

최근에 나는 여러 프로그래밍 언어와 환경을 사용해보고 있고, 내 기술을 다양하게 만드려 노력하고 있다. 나는 보통 리액트(React), 스위프트, Objective-C, 스칼라로 작업해왔다. 이것들은 각자 그들의 어풍과 규약을 가지고 있다. 나는 실제 경험을 통해 배우면서 언어를 비교하고 언어의 차이를 발견하여 더 나은 스위프트 개발자가 되는데 적용시켜보기로했다.

내가 스칼라를 배울 수 있게 도와준 내 상사는 네이밍에관한 블로그 포스팅(링크)을 보내주었고 스칼라에서 다른 수준의 장황함을 사용할 때 그것의 포괄적인 설명으로 나에게 깊은 인상을 주었다. 오늘 내 목표는 스위프트에서 언제 간결해야하고 언제 설명을 덧붙여야하는지에대한 위 포스팅과 비슷한 멋진 글을 쓰려한다. 이 스칼라 포스팅에서 몇 예시를 빌려 스위프트와 iOS 앱을 연관시킬 것이다.

여러분이 프로그래밍할때 간결함을 좋아하든 하지않든, 스위프트는 당신이 선호하는 수준의 말수로 코드를 작성할 수 있게 하는 기능을 가지고 있다. 네이밍을 넘어, 트레일링 클로저 문법, 이름없는 파라미터, 익명의 클로저 인자는 어떨때는 간결하게 해주고, 어떨때는 풀어서 설명해준다.

간결해야하는지 아닌지 그 질문이 아니라, 바로 어디에 간결하게(혹은 풀어서 길게) 하면 되는지이다.

스위프트는 꽤 오랫동안 겪어왔는데, 이것은 코드가 제네럴하게 접목될 수 있는 관용구를 개발하면서 시작되었었다. 스위프트 창시자들은 휼륭한 문서인 offical API design guidelines(링크)를 배포하는데 충분히 친절해왔다. 이것들이 좋긴하지만 나는 좀 더 원하는게 따로 있다. 우리가 어떻게 스위프트의 관용적인 직감을 만들 수 있는지 이야기해보고 싶다. 우리는 세부적으로 직관적으로 네이밍하는 것에 대해 다룬 뒤 언어의 특성에대해 토론해볼것이다.

철학
스위프트 API 설계의 원리에서 특히 네이밍이 언급하는 것은 다음과 같다.
  • 사용되는 시점에서 명료함은 가장 중요한 목표이다.
  • 명료함이 간단함보다 더 중요하다.
멋진 가이드라인인데, 좀 더 깊게 들어가보자. Haoyi의 스칼라 블로그 포스팅에는 우리가 뭔가 네이밍을 붙일때 우리의 목표가 무엇인지 말해준다.
프로그래머가 아직 모르지만 알고싶어하는 것을 보여주어라
매우 흥미로운 가이드라인이며, 이것은 우리에게 코드의 맥락을. 생각하게 만들고, 미래에 어디에서 동작할지 생각하게 만든다.코드는 한번만 쓰여지지만, 계속해서 읽혀진다. 따라서 프로그래머들은 읽기 편하게 최적화시키고 작성하는 것에 힘을 들여야함을 잊지말자. 그리고 읽기 최적화될때 고려해야할 가장 중요한 점은 바로 문맥이다. 스칼라 블로그 포스팅에서 이것들을 잘 정리해 놓았다(링크). 그리고 문맥이란 프로그래머가 이미 알고 있는 것과 프로그래머가 알고 싶어 하는 것 둘 다를 모두 포함한다.

프로그래머가 이미 아는 것
  • 당신의 코드베이스에서 예전에 이미 본 것
  • 다른 코드베이스에서 예전에 이미 본 것
  • 이전 작업에서 그들이 골랐던 사실들
프로그래머가 알고 싶어 하는 것
  • 그들이 하는 것에 영향을 주는 것
  • 그들이 이해할 필요가 있는 것
  • 그들이 익숙하지 않은 것
  • 정확함, 보안, 성능등의 이유로 특별히 위험한 것
이것은 포괄적인게 아니다.

누구나 그리고 언제나, 당신의 코드를 읽고 있다고 생각해라. 코드를 매일 사용하는 사람이 직장동료일까? 아니면 지금으로부터 6개월뒤의 자기자신일까? 당신의 오픈소스 프로젝트에 가볍게 컨트리뷰트를 하려고 하고있는것일까? 이러한 여러 상황들은 어떤 함수의 이름을 어떻게 정할지 영향을 받을 것이다. 설명해보자.

당신의 코드를 매일 사용하는 한 동료의경우 당신의 코드베이스와 그것의 규약에 완전히 친숙하다면 간결한 코드가 최고일 것이다. 만약 6개월동안 그 코드베이스에서 작업할 계획이 없다면 그 규약이 결국 생소하게 되어갈 것이므로 설명하는 말처럼 만드는게 더 도움이 될 것이다. 오픈소스 프로젝트의 가벼운 컨트리뷰터들은 아마 큰 코드베이스가 어떻게 서로 맞춰지는지 이해할 수 없을 것이다. 따라서 지나친 설명은 당신 프로젝트의 컨트리뷰터가 이해하는데 도움을 줄 수 있을 것이다.

어떤 사람이 당신의 코드를 읽고싶어할지, 그리고 그들의 목적이 무엇인지 생각해보아라.

가이드라인
이것은 원칙이 아닌 가이드라인이다. 여러분의 직감이 규칙을 지키기 싫어한다면 그렇게하지 말아라. 이제 중요한 순서로 네이밍에 관한 가이드라인에대해 이야기해보고자한다. 그리고 항상 마음속에 문맥을 기억하자!

(네기 지금 이 글에서 나온(링크) 가이드라인을 스위프트에 적용시키는 점을 기억해달라-우리는 이 포스팅과 그 저자인 Li Haoyi(링크)에게 감사해야한다)

넓은 범위의 이름은 길어야한다
이 예제에서 i라는 이름이 왜 괜찮을까?
for var i in 0..<10 {
  print(i)
}
그러나 여기서는 왜 안될까?
struct MyStruct {
  let i: Int
}
i라는 놈이 코드베이스 어디에서 쓰이는지 생각해보아라. 처음 예제에서 i는 for문 안에서만 접근되었다. 그러나 두번째 예제는 구조체의 맴버이며 저 구조체를 사용하는 어디에서나 i를 접근할 수 있는데, 잠재적으로 모든 코드베이스에서 사용가능하다. 기볍게 본 것으로 i는 매우 널리 쓰일 수도 있다는 이유때문에 i의 문맥을 찾을 수 없다.

우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 위 구조체를 고쳐보자.
struct MyStruct {
  let numberOfInteractions: Int
}

여기서 말하고자하는 바는, 루프에서 쓰이는 모든 변수가 짧아야함을 의미하는게 아니라, 넓게 쓰일 것의 이름은 길어야한다는 의미이다. 이에 반해 아래 예제를 보자 이 예제는 루프 안에서도 짧은 변수가 나쁜 방법일 수 있음을 보여준다.
for var i in 0..<10 {

  ...

  ...

  let data = Data(repeating: 0, count: i)

  ...

  ...

  writeToDb(transformedData, i) // Tricky C API...

  ...

  ...

  ...

  let temp = i + 1

  ...
}
우리 모드 i가 길어야 좋을 거라는 점에 동의할 것이라 생각된다. 왜냐? 그 사용의 범위가 넓은데다가 더 사용되기 때문이다. 이것이 다음 가이드라인으로 연결시켜준다.

더 사용된 이름들은 짧아야한다.
스위프트에서 처음 배웠던 함수인 print를 한번 보자. 함수의 이름처럼 "print"는 아주 완벽하게 동작한다.
print("Hi there!")
그렇다면 왜 "cache"가 별로 좋지 않은 것일까?
class Downloader {
  func cache() { ... }
}

...

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...
    downloader.cache() // Only called at app startup.
  }
}
print는 아주 많이 사용되고 모든 스위프트 개발자들이 그것에 익숙하다고 생각할 것이다. cache는 한번만 쓰이며 거의 모습을 보지 못하는 커스텀 객체에 정의해 놓는다. 이런것은 긴 이름이 의미가 있다.
class Downloader {
  func initializeCache() { ... }
}
훨씬 낫다.

위험한 이름은 길어야한다. 
몇 함수들은 그들이 하는 때문에 이름이 길다. 위험한 함수들은 이름이 길어야하는 반면 빈번하게 쓰이는 일상의 함수들은 짧아야한다. 여기 아주 긴 함수이다.
extension Downloader {
  func loadDataFieldsFromOfflineCache() { ... }
}
대신 loadFromCache으로 될 수 있습니다.
extension Downloader {
  func loadFromCache() { ... }
}
그러나 이 함수를 길게하는 것이 의미가 있는지 생각해보자.
extension Downloader {
  func deleteAPICredentialsFromCache() { ... }
}
이 함수를 호출하는 것이 위험하여 긴 이름을 가지게 되었다. 우리는 사고로 사용자의 데이터를 지워버리는 일은 항상 피하고 싶을 것이다. 그렇기 때문에 이렇게 너무 간결하게 호출하고 싶지 않을 것이다.
extension Downloader {
  func delToken() { /* deletes user data omg! */ }
}
우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 이것이 사용자 데이터를 제거할 때 함수를 호출하는 누군가가 당연히 그 사실을 알고 싶어할 것이라고 상상한다.

소스 문맥의 이름들은 짧아야한다.
내부 타입이 존재하는 타입 이름은 짧아야한다. 그리고 외부 타입이 존재하면 길어야한다. 아래를 한번 생각해보자.
protocol Delegate {
  ...
}
Delegate 프로토콜이 무엇을 위한 것인지 알 수 없으므로 이것은 너무 짧다. 좀 더 긴 이름을 붙여서 더 낫게 해보자.
protocol DownloaderDelegate {
  ...
}
멋지다! 이제 저 프로토콜이 무엇을 위한 것인지 알도록 도와준다.

스위프트 컴파일러가 타임으로 프로토콜을 도와준다면 대안의 방안이 될 수 있다.
class Downloader {
  protocol Delegate {
    ...
  }
}
이것은 충분히 자격이 있는 Downloader.Delegate로 확장할 수 있다. 그러나 슬프게도 스위프트는 아직 이런식으로 감쌓여진 프로토콜의 종류를 지원하지 않는다.

그냥 이름으로 타입 정보 중복을 피해야한다.
class Downloader {
  protocol DownloaderDelegate {
    ...
  }
}
개발자들은 이미 Downloader 클래스 안에 타입들이 이 클래스와 동작해야한다는 것을 알기 때문에 정보의 중복은 무의미하다. 이제 마지막 가이드라인으로 넘어가자.

강타입(Strongly Typed) 이름들은 짧아야한다.
스위프트는 강력하게 표현력있는 타입 시스템을 가지며, 우리는 이를 이용하여 이름을 짧게 만들 수 있다. 예를들어 아래 프로퍼티를 생각해보자.
class Downloader {
  var downloaderDelegate: Delegate
}
우리는 이미 저 델리게이트 프로퍼티가 DownLoader 클래스에 속한다는 것을 알기 때문에 프로퍼티 이름으로서 downloaderDelegate를 부르는 것이 불필요하다.

아래에 또다른 카운터 예제가 있다.
func zipTwoSequences<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
대신에 표준 라이브러리는 이것만 포함한다.
func zip<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
타입 시그니처에서 인자가 시퀀스라는게 확실하기 때문이다.

여기까지가 네이밍 가이드라인에대한 이야기이고, 이제는 간결하게 만들어주는 스위프트 특징들을 이야기해보자!

전반적으로 이름들을 생략하기
설명이 긴 것부터 간결한 것까지 그 범주중에 굉장히 "간결함"의 끝에는 아예 이름이 없는 것이다. 트레일링 크로저 문법, 이름없는 파라미터, 익명의 클로저 인자들로 이름없이 할 수 있다. 그것들을 사용할 때는 아래 가이드라인을 넘는 문제이다.

클로저 문법 추적은 매우 편하고 함수 호출을 더 간결하게 하도록 도와준다. Ray Wenderlich의 스위프트스위프트 스타일 가이드에서 쿨로저 섹션(링크)의 말을 빌리자면, 클로저의 목적이 모호하다면 트레일링 클로저 문법은 사용하지 마라고 한다. 예를들자면 아래같은 경우가 나쁜 경우이다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}) { finished in
  ...
}
이렇게하는 것이 훨씬 더 명료하다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}, completion: { finished in
  ...
})
이름없는 파라미터(unnamed parameters)의경우는 인자 레이블로 공식 스위프트 API 가이드라인을 참고할것이다.
  • 인자들이 유용하게 구별되지 않을때 모든 레이블들을 생략하라.(ex. union(set1, set2))
  • 함수 이름의 문법이 첫번째 인자가 무엇인지 명확할때 레이블들을 생략하라.(ex. addSubview(y))
  • 타입 규약을 위해서는 레이블들을 생략하라.(ex. Int64(someUInt32))
  • 그렇지 않으면 (일반적으로는) 인자 레이블을 명시하라.

마지막으로 익명의 클로저 인자가 남았다. 대부분 클로저의 길이에따라 이것을 사용하는데, "넓은 범위의 이름은 길어야한다"는 규칙과 일맥상통한다.

만약 여러분의 클로저가 몇가지 안되는 일을 한다면 익명의 클로저 인자를 사용하라.
(0..<10).map({ String($0) })
아래는 과하게 설명이 긴 카운터 예제이다.
(0..<10).map({ number in String(number) })
그리고 아래의 것은 네이밍에관해 처음 두가지 가이드라인을 접목하지 않을 때 어떤식으로 생겼을 수 있는지 보여준다.
(0..<10).map({
  ...

  ...

  let data = Data(repeating: 0, count: $0)

  ...

  ...

  return Model(fromData: data, index: $0)
})
다시한번 Ray Wenderlich 가이드로가서 클로저에대한 정보를 살펴보길 바란다.

오늘 다루었던 가이드라인이라는 것은 절대적인 어떤 것이 아님을 기억하자. 경험하고 다른 사람에게 물어보고 그리고 배우자. 그 과정을 즐길 수 있길 바란다!


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Pretty much every way to assign optionals

Non-옵셔널을 저장하거나 옵셔널을 옵셔널에 저장하기

기본적인 것이다. 로켓과학처럼 복잡한 것이 아니다.
optItem = 5 // optItem is now .some(5)
optItem = optValue // optItem is whatever optValue is
요약
사용빈도: 모든 경우
터무니없는 접근법인가: 전혀 그렇지 않다.

옵셔널을 non-옵셔널에 저장하기
많은 함수와 메소드들이 옵셔널 값을 반환한다. throw와 함께 try?를 사용할때도 옵셔널 값을 반환한다. 여러분은 종종 그 결과를 non-옵셔널 변수 혹은 프로퍼티로 담아둬야 할 때가 있을 것이다.

이렇게하기위해 nil을 테스트하고 어떤 non-nil 결과의 언랩핑된 결과를 저장한다. 아래에 몇가지 방법이 있다.

조건부의 바인딩
if let을 사용하여 조건부로 옵셔널을 바인딩하고 대입을 실행할 수 있다.
if let optItem = optItem { 
    item = optItem // if optItem is non-nil
}
if let과 완전히 동일하지만 그래도 여러분이 원한다면 if case도 사용할 수 있다.
// Sugared optional
if case let optItem? = optItem { 
    item = optItem // if optItem is non-nil
}

// External let
if case let .some(optItem) = optItem {
    item = optItem // if optItem is non-nil
}

// Internal let
if case .some(let optItem) = optItem { 
    item = optItem // if optItem is non-nil 
}

Nil coalescing
만일의 대비의 값으로 nil coalescing을 사용할 수 있다.
item = optItem ?? fallbackValue
만일의 대비의 값이 필요없다면 원래 값을 사용해도 된다.
item = optItem ?? item
약간의 주의. 컴파일러가 "자신에 자신을 할당하는" 경우를 최적화하는지 잘 모르겠다. 그것을 확인하고 할당한다면 if let 방법보다 덜 효율적일 것이다.

또한 이 Itemnon-nil 일때만 갱신한다는 의도가 추가적인 if let을 쓰는게 깔끔하다고 생각하므로 효율면이나 가독성면에서 이것을 추천하진 않는다.

요약
사용빈도: 종종 사용한다
터무니없는 접근법인가: 전혀 아니다

optItemnon-nil일때만 옵셔널을 갱신하기
이번에는 현재 옵셔널이 nil일때 갱신을 스킵하는 시나리오에대해 설명하겠다. 이러한 경우 nil은 "이 옵셔널을 건드리지 마시오"라는 의미이다. 나는 이런 시나리오가 일어나지 않게 생각했다.

명확한 해결 방법이다.
if optItem != nil { optItem = newValue }
? 표시를 사용한 완전히 이상한 벙법이다.
optItem? = nonOptionalValue
?의 사용에서 rhs는 반드시 non-옵셔널이어야하고 컴파일 시점에 보장된다. 스위프트 언어의 특징중에 다소 모호한 것이다(이것을 짚어준 Joe Groff에게 감사하다).

혹은 "non-nil 수신자를 위한 테스트" 대입을 위해 이렇게 할 수 있다(바보같지만).
if let optValue = optValue {
    optItem? = optValue
}
이 예제는, rhs? 대입은 non-옵셔널이여야한다. 조건부로 옵셔널을 바인딩하는 것은 ?을 쓸 수 있게 해준다. Madalin Sava가 아래의 간단한 대안을 알려주었다. (이 섹션의 모든 것이 그렇듯) 절약 면에서는 높은 점수지만 명료하지 않은 결과에대해서는 낮은 점수를 받는다.
optItem? = optValue ?? optItem!
요약
사용빈도: 절대 사용하지 말라
터무니없는 접근법인가: 확실히 그렇다

optItemnil일때만 옵셔널을 갱신하기
이번에는 "대부분 사용하는, 한번만 세팅하기"로 설명할 수 있겠다. 옵셔널이 non-nil 값으로 한번 대입하게 되면, 그것은 다시 덮어쓰이지 않게 만든다. 대입을 하기 전에 nil을 확인하여 가장 간단하게 할 수 있다.
if optItem == nil { optItem = newValue }

혹은 오퍼레이터로 불태워도 된다. 아마 이렇게 하고 싶진 않을 것이다.

infix operator =?? : AssignmentPrecedence

// "fill the nil" operator
public func =??<T>(target: inout T?, newValue: T?) {
    if target == nil { target = newValue }
}
optItem =?? newValue

이것도 한번 보자: SE-0024

요약
사용빈도: 나는 이렇게 사용하진 않으나, 다시 대입되는 것을 막고 싶을때 유용할 것이다. 이것이 묵시적으로 언랩핑된 옵셔널의 미친 버전의 종류이나, 그것을 다시는 바꾸지 않는다고 보장함을 테스트하는 곳과, (IVO의) 모든 일련의 변화가 non-nil 값이여야하는 곳이 아닌..
터무니없는 접근법인가: 터무니없지는 않으나 일반적이지도 않다.

새로운 값이 non-nil일때만 옵셔널을 갱신하기
이 시나리오는 기본적으로 묵시적인 언랩핑된 옵셔널을 따라한 것이지만 세이프티가 추가되고 IVO 크레쉬가 없다. 항상 non-nil에대한 테스트를 하기때문에 그 값을 검증하여 한번 세팅하면 옵셔널이 다시는 nil을 반환하지 않을 것이다.

한계는 non-nil의 새 값을 갱신하고 nil 대입을 버린다.
if let newValue = newValue { optItem = newValue }

혹은 이렇게(nil-값을 위해 다시 대입하는 행위를 한다는게 낭비처럼 느껴지지 않는가?)
optItem = newValue ?? optItem
혹은 연산자로 불태워도 된다. 마찬가지로 이렇게 하고 싶지는 않을 것이다.
infix operator =? : AssignmentPrecedence

// "assign non-nil values" operator
public func =?<T>(target: inout T, newValue: T?) {
    if let newValue = newValue {
        target = unwrapped
    }
}
이것도 한번 보자 : Swift Evolution

요약
사용빈도: 내가 사용하진 않는다만 non-IVO 옵셔널을 사용하고 싶은 사람들이 원할 수도 있을 것 같다.
터무니없는 접근법인가: 그렇진 않으나 일반적이지도 않다.


'Swift와 iOS > Swift Basic' 카테고리의 다른 글

[번역]No-contiguous raw value enumeration  (372) 2017.05.13

WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

최근에 Brennam Stehling는 내가 완전히 모르고 있었던 환상적인 Swift의 기능 하나를 알려주었다. 여러분은 각 case마다 자동으로 값이 증가하는 raw값의 열거형을 만들 수 있을 것이다.
enum MyEnumeration: Int {
   case one = 1, two, three, four
}

MyEnumeration.three.rawValue // 3
그리고 여러분은 손수 값을 지정한 raw 값 열거형을 만들 수도 있다.
enum MyEnumeration: Int {
    case one = 1, three = 3, five = 5
}
그런데 나는 이 두가지를 합쳐서 사용할 수 있다는 것을 몰랐었다!(아마 아래처럼 표준-기반 값들에는 그렇게 하지 않을것 같지만..)
enum HTTPStatusCode: Int {
    // 100 Informational
    case continue = 100
    case switchingProtocols
    case processing
    // 200 Success
    case OK = 200
    case created
    case accepted
    case nonAuthoritativeInformation
}

HTTPStatusCode.accepted.rawValue // 202
그래도 멋지지 않는가?

나는 아마 그 위치(예를들면 "1에서 시작")와함께 값으로 접근할 수 있게 지정하고, 아래에 있는 값들은 확정되지 않은 의미를 가진다. Kristina Thai는 다음과같이 언급했다. 의미있는 값을 생략하는 것은 가독성면이나 열람시에 도움이 되지 않을것이라고.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.

'Swift와 iOS > Swift Basic' 카테고리의 다른 글

[번역]옵셔널을 대입하는 여러가지 방법들  (0) 2017.05.13

WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Refactoring singleton usage in Swift

더 명료하고, 더 모듈화시키며, 더 테스트용이한 코드베이스를 위한 팁

소프트웨어 개발에서 싱글톤널리 권장되지 않고 눈쌀을 찌푸리게 만든다. 이것을 테스트하는 것은 어렵거나 불가능하고, 묵시적으로 다른 클래스에서 사용하면 여러분의 코드베이스는 헝클어져버린다. 또한 이것은 코드의 재사용도 어렵게 만든다. 오랫동안 싱글톤은 전역 변수나 가변 상태의 변형에 지나지 않다고 생각해왔다. 적어도 많은 사람들은 이 방법이 나쁜 방법이라는 것 정도는 인지하고 있다. 그러나 때때로 싱글톤은 피할수 없는, 필요한 독이기도 하다. 이것을 어떻게 깔끔하고 모듈화되고 테스트용이하게 우리 코드에 집어넣을 수 있을까?

싱글톤은 어디에나 있다
애플 플랫폼에서 싱글톤은 Cocoa와 Cocoa Touch 프레임워크 어디에나 있다. UIApplication.shared, FileManager.default, NotificationCenter.default, UserDefaults.standard, URLSession.shared 등이 있다. 또한 이 디자인 패턴은 Cocoa Core Competencies 가이드의 한 섹션으로 나와있다.

여러분이 묵시적으로 저 싱글톤(혹은 여러분의 싱글톤)을 참조할때 여러분의 코드를 변경하는데 드는 노력이 증가할 것이다. 싱글톤을 사용하는 클래스에서 싱글톤을 변경하거나 목(mock) 할 수 있는 방법이 없기 때문에 여려분의 코드를 테스트하기 어려워지거나 불가능해진다. 아래는 iOS 앱에서 일반적으로 볼 수 있는 것이다.
class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let currentUser = CurrentUserManager.shared.user
        if currentUser != nil {
            // do something with current user
        }

        let mySetting = UserDefaults.standard.bool(forKey: "mySetting")
        if mySetting {
            // do something with setting
        }

        URLSession.shared.dataTask(with: URL(string: "http://someResource")!) { (data, response, error) in
            // handle response
        }
    }
}
이것이 묵시적인 참조이다(클래스 안에서 직접 싱글톤을 사용한다). 더 나아지게 할 수 있는데, 스위프트로 가볍고 쉬우며 의존성을 줄이는 방법이 있다. 또한 스위프트로 우아하게 만들 수도 있다.

의존성 주입
짧게 말해, 답은 의존성 주입이다. 이 원리는 여러분의 함수와 클래스를 모든 입력이 명시적으로 되게 설계하는 방법이다. 위의 코드를 의존성 주입을 사용하여 리팩토링 한다면 아래처럼 생겼을 것이다.
class MyViewController: UIViewController {

    let userManager: CurrentUserManager
    let defaults: UserDefaults
    let urlSession: URLSession

    init(userManager: CurrentUserManager, defaults: UserDefaults, urlSession: URLSession) {
        self.userManager = userManager
        self.defaults = defaults
        self.urlSession = urlSession
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let currentUser = userManager.user
        if currentUser != nil {
            // do something with current user
        }

        let mySetting = defaults.bool(forKey: "mySetting")
        if mySetting {
            // do something with setting
        }

        urlSession.dataTask(with: URL(string: "http://someResource")!) { (data, response, error) in
            // handle response
        }
    }
}
이 클래스는 이제 더이상 모든 싱글톤에 묵시적으로 의존하지 않는다. 이것은 명시적으로 CurrentUserManager, UserDefault, URLSession에 의존한다. 그러나 이런 의존성들이 이것들이 싱글톤이라는 것을 이 클래스는 전혀 모른다. 이런 세부사항들은 기능이 바뀌지 않은채 문제가 없다. 뷰컨트롤러는 단이 이 오브젝트의 인스턴스가 존재한다는 사실만 알고있고, 호출 시점에서 싱글톤을 담아 보낼 수 있다. 다시 말하지만, 이런 세부사항은 이번 수업의 관점과는 관련이 없다.
let controller = MyViewController(userManager: .shared, defaults: .standard, urlSession: .shared)

present(controller, animated: true, completion: nil)
프로 팁: 여기서 스위프트 타입 추론이 일어난다. URLSession.shared라고 쓰는 것 대신에 .shared라고 쓸 수 있다.

만약 다른 userDefaults를 줘야한다면(예를들어 App Groups과 데이터를 공유해야한다면) 바꾸기 쉽다. 사실 이 클래스에서는 아무것도 바뀌지 않아야한다. UserDefualts.standard를 보내는 것 대신에 UserDefaults(suitName: "com.myApp")을 보내야한다.

게다가 이제 유닛테스트에서 이 클래스의 가짜나 목(mock)을 보낼 수 있다. 실제 스위프트에서 목 하는 것은 불가능하지는 않으나 더 편한 workarounds가 있다. 이것은 여러분의 코드를 구성하고 싶은대로 할 수 있다. CurrentUserManager라는 프로토콜을 쓸 수 있는데, 이것은 테스트에서 "목"할 수 있다. 또한 테스트를 위한 가짜 UserDefault에 제공할 수 있고 URLSession을 옵셔널로 만들어 테스트에선 nil을 넣으면 된다.

리팩토링 지옥
이 방법에 열중하여 이제 기술적 빚을 안고있는 여러분의 코드베이스를 해방시키고 싶을 것이다. 의존성 주입은 이상적이고 더 순수한 객체 모델을 제공하지만, 종종 달성하기 어려울때가 있다. 게다가 처음 코드를 짤 때 이것을 수용하도록 설계하지 않을 것이다.

위에 우리가 리팩토링한 것은 이제 더 모듈화되고 테스트용이하다. 그러나 여기에는 현실적인 문제가 있다. MyViewController의 생성자는 빈(init())것에 익숙한데 이제 3개의 파라미터를 받아야한다. 모든 호출시점이 바뀌게 되는 것이다. 이것을 구성하기위해 깔끔하고 적절한 방법은, 계층 위아래로 혹은 이전 뷰컨트롤러에서 지금 뷰컨트롤러까지 인스턴스를 보내도록 만드는 방법이다. 이것은 객체 그래프의 루트에서부터 모든 자식까지 데이터를 보내야 한다는 뜻이다. iOS에서는 특히 뷰컨트롤러에서 뷰컨트롤러로 데이터를 보내는 것이 머리아픈일이다. 특히 다른 사람에게 넘겨받은 코드베이스는 갑자기 많이 바뀌는 구현에서 애를 먹을 것이다. 그러면 대부분 클래스들(특히 뷰컨트롤러)의 생성자는 바뀌어야한다. 이런 바뀜은 앱 전체를 일괄적으로 리팩토링해야하는데 당신이 인지하고 있기 어려운 범주가 되버린다. 모든 코드를 고칠 수도 있겠지만 아니면 다른 클래스들은 여전히 묵시적으로 싱글톤을 참조하게 두고 몇개만 의존성 주입으로 바꾸는 것이다. 그래도 이런 부조화는 훗날에 문제를 야기할 수도 있다.

따라서 이런 리팩토링은 복잡하고, 크고, 넘겨받은 코드베이스에는 알맞지 않다. 이런 이유로 리팩토링을 하지 말고 이런 기술적 부채와 함께 살아갈것인지도 의논해보아야한다. 그러다가 몇달, 몇년뒤 멀티 사용자 기능을 지원해야할때, 계정을 바꿀때 CurrentUserManager가 동작하지 않을 수 있다. 이것은 어떻게 해결할 것인가?

여러분이 어떤 프로젝트를 시작할때부터는 클래스 설계의 첫 단계부터 이런 종류의 변경을 수용할 수 있게 만드는 방법이 있을 것이다.

디폴트 파라미터 값들
스위프트에서 내가 좋아하는 기능 중 하나는 디폴트 파라미터 값들이다. 이것은 놀랍도록 유용하고 여러분 코드에 엄청난 유연성을 가져다준다. 디폴트 파라미터로, 의존성 주입이라는 토끼구멍으로 들어가지 않고, 또 여러분의 코드베이스에서 엄청난 복잡성도 만들지 않고서, 위에서 말한 문제를 해결할 수 있다. 아마 여러분의 앱은 한명의 유저만을 가질 것이니, 위와같으 의존성 주입의 구현이 불필요하게 과하다.

싱글톤을 디폴트 파라미터로하여 사용할 수 있다.
class MyViewController: UIViewController {

    init(userManager: CurrentUserManager = .shared, defaults: UserDefaults = .standard, urlSession: URLSession = .shared) {
        self.userManager = userManager
        self.defaults = defaults
        self.urlSession = urlSession
        super.init(nibName: nil, bundle: nil)
    }
}
이제 호출 시점을 고칠 필요가 없다. 그러나 클래스 그 자체 안에서는 수많은 다양한 것들이 있다. 이제 의존성 주입을 사용하고 더이상 싱글톤을 참조하지 않는다.
let controller = MyViewController()

present(controller, animated: true, completion: nil)
이러한 변경으로 어떤 이득을 취할 수 있을까? 모든 호출 지점을 바꾸지 않고 이 패턴을 사용하기 위해 모든 클래스를 리팩토링할 수 있다. 의미로나 기능적으로나 바뀐것은 없다. 그러나 여러분의 클래스들은 이제 의존성 주입을 사용한다. 이것들은 단지 내부적으로 인스턴스를 사용하고 있다. 이것을 위에서 설명한 것처럼 테스트할 수 있고 유연하게 모듈화된 API를 유지보수 할 수 있다. (그래도 모든 퍼블릭 인터페이스는 바뀌지 않는다는 점) 본질적으로는 아무것도 바뀌지 않은채 계속 코드베이스에서 작업할 수 있을 것이다.

커스텀을 전달받는 떄가 오면, non-싱글톤 파라미터로 어떤 클래스도 변경하지 않고서 해결할 수 있다. 오직 호출 지점만 바꾸면 된다. 게다가 완전한 의존성 주입을 구현하여 계층의 위에서 애래로 모든 의존성마다 전달해가려면 그냥 드폴트 파라미터를 지우고 그 위에서 의존성으로 전달하면 된다.

필요에따라 어떤 디폴트값의 opt-in 혹은 opt-out도 할 수 있다. 아래 예제에서는 커스텀 UserDefaults를 제공하지만, CurrentUserManagerURLSession을 위해 디폴트 파라미터를 가지고 있는다.
let appGroupDefaults = UserDefaults(suiteName: "com.myApp")!

let controller = MyViewController(defaults: appGroupDefaults)

present(controller, animated: true, completion: nil)

결론
스위프트는 적은 노력으로 "partial" 종류의 의존성 주입을 만든다. 여러분의 클래스에 드폴트값으로 새 프로퍼티와 생성자 파라미터를 추가하여, 코드를 모듈화시키고 테스트하기 좋게 만들 수 있다(리팩토링에 빠지지 않고 완전한 의존성 주입을 만들 필요 없이 가능하다). 만약 프로젝트 시작 시점에 클래스를 이렇게 설계하면 코딩하면서 궁지에 몰리는 일이 더 적어질 것이다(그리고 여러분이 궁지에 몰리더라도 쉽게 빠져나올 수 있을 것이다).

여러분은 이 예제를 넘어 클래스, 구조체, 열거형, 함수 등 코드 전반에 이 개념과 설계를 적용시켜 볼 수 있다. 스위프트의 모든 함수는 디폴트 파라미터 값을 받을 수 있다. 나중에 어떤게 바뀔 수 있을지 생각해봄으로서, 적은 노력으로 변경할 수 있는 타입이나 함수를 만들어낼 수 있을 것이다.

좋은 소프트웨어를 만드는 것과 설계하는 것은 원하는 것을 쉽게 바꿀 수 있지만, 모든것을 바꾸진 않아도 되는 코드를 짠다는 의미이다. 이것이 의존성 주입 뒤에 있는 그 이유이며 스위프트의 디폴트 파라미터가 이것을 빠르고 쉽고 우아하게 해결할 수 있게 도와줄 것이다.


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

모든 문제는 또다른 프로토콜을 추가하여 해결할 수 있다.

옵셔널은 멋지다. 이제까지 나는 Objective-C의 "messages to nil return nil" 버그를 너무 많이 봐왔었고 다시 그때로 돌아가고 싶지도 않다.

그러나 당신은 옵셔널이나 특정 타입의 옵셔널이 필요할 때가 종종 있다. 아래에는 내가 즐겨쓰는 그 경우들이다.

isNilOrEmpty
가끔씩 nilisEmpty==true의 차이를 신경쓰지 않아도 될 때가 있다. 먼저 _CollectionOrStringish 프로토콜을 만든다. 이 프로토콜은 비어있고, 이 타입이 isEmpty 프로퍼티를 가진다는 것을 표시하여 사용한다.
protocol _CollectionOrStringish {
    var isEmpty: Bool { get }
}

extension String: _CollectionOrStringish { }
extension Array: _CollectionOrStringish { }
extension Dictionary: _CollectionOrStringish { }
extension Set: _CollectionOrStringish { }
다음으로 Optional where Wrapped: _CollectionOrStringish를 확장(extension)하자.

extension Optional where Wrapped: _CollectionOrStringish {
    var isNilOrEmpty: Bool {
        switch self {
        case let .some(value): return value.isEmpty
        default: return true
        }
    }
}

let x: String? = ...
let y: [Int]? = ...

if x.isNilOrEmpty || y.isNilOrEmpty {
    //do stuff
}

value(or:)
이것은 아주 간단하다. 이것은 함수로 표현된 ?? nil-coalescing 연산자이다.
extension Optional {
    func value(or defaultValue: Wrapped) -> Wrapped {
        return self ?? defaultValue
    }
}
이것은 아주 코드에서 연산자의숲(operator-soup)에 들어갈때 사용하는데, 어디서 사용하든 함수형태의 것이 명확하다. 혹은 함수 파라미터로 nil-coalescing을 써야할 때 사용한다.
// operator form
if x ?? 0 > 5 {
    ...
}

// function form
if x.value(or: 0) > 5 {
    ...
}

apply(_:)
이것은 리턴 값이 없는(혹은 ()을 리턴할 수도 있다) 버전의 map이다.
extension Optional {
    /// Applies a function to `Wrapped` if not `nil`
    func apply(_ f: (Wrapped) -> Void) {
        _ = self.map(f)
    }
}

flatten()
Update: VictorPavlychoko가 댓글로 짚어주었듯, ExpressibleByNilLiteral으로 flatten을 더 간단하게 만들 수 있다!
protocol OptionalType: ExpressibleByNilLiteral { }

// Optional already has an ExpressibleByNilLiteral conformance
// so we just adopt the protocol
extension Optional: OptionalType { }

extension Optional where Wrapped: OptionalType {
    func flatten() -> Wrapped {
        switch self {
        case let .some(value):
            return value
        case .none:
            return nil
        }
    }
}
ExpressibleByNilLiteral이 적용되지 않았을 때 사용할 수 있다는 것을 설명하기 위해, 교육의 목적으로 원래의 구현을 남겨두고 있다.

원래의 flatten
이중 옵셔널로 작업해본적이 있다면 이 익스텐션의 진가를 인정할 수 있을 것이다. 여기서 몇 프로토콜과 익스텐션을 필요로 하는데, 어떤 임의의 Wrappednone 케이스를 구성하는 방법을 찾기위한 꼼수이다. 이 이야기가 와닫지 않는다면 축하한다. 당신에게 평범하고 생산적인 삶을 살 수 있는 희맘ㅇ이 아직 있다. 아래에다가 설명을 갈게 쪼게어 해놓았으니 보자.
  1. 보통 컴파일러 마법은 모든 Optional<Wrapped>들에(감쌓인것 까지도) nil을 대입하게 해주고, 그냥 모든것이 잘 동작한다.
  2. flatten()으로부터 리턴을 표현하기 위해 추상 타입 맴버(연관타입)을 제공할 수 있다.
    * 익스텐션에서 self를 참조하고 아래처럼 제네릭 파라미터를 생략할 수 있다면
    extension Optional where Wrapped: Optional
    flatten() -> Wrapped.Wrapped 이렇게도 할 수 있을 것이나, 불행히도 지금 이렇게 할 수 없다.
  3. 일반적인 옵셔널 마법은 동작하지 않아야한다. 왜냐하면 프로토콜에 익스텐션이 연관타입 WrappedType을 반환할 것이라 약속했기 때문이다. 컴파일러 마법은 nil을 .none으로 만들 수 없다.
    * 만약 WrappedType: Optional<?>으로 만든다면: 동작은 할것이나 그렇게 할 수 없을 것이다.
    * 만약 WrappedType: Self로 만든다면: 스스로 동작은 할 것이나 그렇게 할 수 없을 것이다.
    (If we could constrain WrappedType: Optional<?> it would work but we can't.
    If we could constrain WrappedType: Self it would work but we can't.)
  4. 우리 프로토콜에서 init()를 요구조건으로 추가한다. 이것으로 WrappedType의 인스턴스를 구성하여 반환하는데 사용할 수 있다.
  5. OptionalType 익스텐션에서 self=nil을 사용할 수 있다. 그 이유는, 컴파일러가 self는 옵셔널이라는 것을 알고 있기 때문에 마법이 일어난다.
protocol OptionalType {
    associatedtype WrappedType
    init()
}

extension Optional: OptionalType {
    public typealias WrappedType = Wrapped
    public init() {
        self = nil
    }
}

extension Optional where Wrapped: OptionalType {
    func flatten() -> WrappedType {
        switch self {
        case .some(let value):
            return value
        case .none:
            return WrappedType()
        }
    }
}
언급된 몇 제약들은 결국 타입 시스템에대한 여러 증진으로 드러날 수 있다.

valueOrEmpty()
한 타입이 빈 것으로 표현될때의 작은 규약이며 이것으로 nil-coalesce하여 성가시지 않게 만들 수 있다.

/// A type that has an empty value representation, as opposed to `nil`.
public protocol EmptyValueRepresentable {
    /// Provide the empty value representation of the conforming type.
    static var emptyValue: Self { get }

    /// - returns: `true` if `self` is the empty value.
    var isEmpty: Bool { get }

    /// `nil` if `self` is the empty value, `self` otherwise.
    /// An appropriate default implementation is provided automatically.
    func nilIfEmpty() -> Self?
}

extension EmptyValueRepresentable {
    public func nilIfEmpty() -> Self? {
        return self.isEmpty ? nil : self
    }
}

extension Array: EmptyValueRepresentable {
    public static var emptyValue: [Element] { return [] }
}

extension Set: EmptyValueRepresentable {
    public static var emptyValue: Set { return Set() }
}

extension Dictionary: EmptyValueRepresentable {
    public static var emptyValue: Dictionary { return [:] }
}

extension String: EmptyValueRepresentable {
    public static var emptyValue: String { return "" }
}

public extension Optional where Wrapped: EmptyValueRepresentable {
    /// If `self == nil` returns the empty value, otherwise returns the value.
    public func valueOrEmpty() -> Wrapped {
        switch self {
        case .some(let value):
            return value
        case .none:
            return Wrapped.emptyValue
        }
    }

    /// If `self == nil` returns the empty value, otherwise returns the result of
    /// mapping `transform` over the value.
    public func mapOrEmpty(_ transform: (Wrapped) -> Wrapped) -> Wrapped {
        switch self {
        case .some(let value):
            return transform(value)
        case .none:
            return Wrapped.emptyValue
        }
    }
}

descriptionOrEmpty
Swift3에서 보간법(interpolated) 문자열 옵셔널을 포함한 새로운 경고는 유용하다; 대부분 여러분은 문자열이 "(nil)"으로 표사되길 원하진 않을 것이다. 그러나 그런 동작을 원하든 아니면 그냥 빈 문자열을 원할때든 간편한 프로퍼티들이 있다.
eextension Optional { 
     var descriptionOrEmpty: String { 
         return self.flatMap(String.init(describing:)) ?? ""
     } 

     var descriptionOrNil: String { 
         return self.flatMap(String.init(describing:)) ?? "(nil)" 
     } 
} 

결론
이게 유용하고 재미있었다면 이런 형식으로 임의의 익스텐션으로 몇몇 포스팅을 해왔다.

또한 이런 동작들에대한 아주 커다란 포스팅을 준비하고 있는데, 시간이 많이 걸리는 중이다. 글을 써내려가는 중이니 기다려주길 바란다.


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: System Level Breakpoints in Swift


모든 훌륭한 소프트웨어 개발자들은 결국 훌륭한 소프트웨어 디버거가 되야한다. 디버깅은 크게는 브레이크포인트의 설정으로 이루어지고, 실행시간동안 앱의 임의의 상태 지점을 관찰하기위해 브레이크 포인트를 걸어 놓은 곳에 가본다. 큼직하게는 두가지 종류의 브레이크 포인트가 있다. 하나는 당신의 코드에 설정한 것이고, 다른 하나는 다른사람의 코드에 설정한 것이다.

여러분의 코드에 브레이크 포인트를 설정하는것은 간단하다. 그냥 Xcode 프로젝트의 소스코드 라인을 찾아서, 관련된 라인 옆에 홈 안의 공간을 탭하면 된다.



그러나 시스템 API에대해 브레이크 포인트를 설정하고 싶거나 소스코드를 가지고 있지 않은 라이브러리 안에 구현된 메소드에 브레이크 포인트를 설정하고 싶은 경우는 어떨까? 예를들어, 레이아웃 버그를 잡아야한다고 할때, UIView에서 애플의 고유의 내부 layoutSubviews 메소드 호출을 관찰하는데 도움이 될 수 있다. 역사적으로 Objective-C 개발자들에게는 이것이 큰 문제가 아니다. 어떤 메소드를 심볼릭하게 표현하기위해 그리고 그것을 브레이크하기위해, Xcode의 lldb 콘솔(View -> Debug Area -> Activate Console)로 들어가서 이 이름을 지정해가면서 브레이크 포인트를 지정하면 된다는 것을 안다. lldb에서 "b"라는 단축 명령어는 우리가 입력한 것으로부터 전체 이름과 매칭되는 것을 찾는 정규식이다.

(lldb) b -[UIView layoutSubviews]
Breakpoint 3: where = UIKit`-[UIView(Hierarchy) layoutSubviews], address = 0x000000010c02f642(lldb)

lldb 콘솔에서 겁을 주거나, 현재 디버그 세션보다 더 길게 붙이는 브레이크 포인트를 원하면, Xcode의 내장된 심볼릭 브레이크 포인트 인터페이스(Debug -> Breakpoints -> Create Symbolic Breakpoint)를 사용하여 같은 결과를 달성할 수 있다.


사실, 여러분의 iOS 앱에 브레이크 포인트를 걸고 앱을 돌리면 애플의 layoutSubviews 메소드에서 브레이크 포인트 안으로 실행시켜볼것이라고 생각한다 lldb 콘솔로 돌아와서 메시지를 받은 오브젝트를 확인한다.

(lldb) po $arg1
<UIClassicWindow: 0x7f8e7dd06660; frame = (0 0; 414 736); userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x60000004b7c0>; layer = <UIWindowLayer: 0x600000024260>>

이제 계속해서 심블에 브레이크를 걸어보자. 그리고 또다시 그렇게 하자. lldb 콘솔에 "po $arg1"이라고 입력하여 매번 타겟을 확인하라. 유별난 버그를 추적하는동안 이런 종류의 분석을 시행하는게 얼마나 유용한지 상상할 수 있을 것이다.

그러나 우리 플랫폼을 이제 시작한 가여운 스위프트 프로그래머들이나 스위프트 문법에만 열관하는 사람들은 어떻게할까? 애플의 문서를 읽어보거나, "-[UIView layoutSubviews]"를 해석하는게 불가능한 사람들은 "UIView.layoutSubviews"이 완전히 눈에 거슬릴 뿐만 아니라, 스위프트에게는 옳은 것일까?

불행히도 "UIView.layoutSubviews"라고 브레이크포인트를 설정하면 동작하지 않는다.

(lldb) b UIView.layoutSubviews
Breakpoint 3: no locations (pending).WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb)

이것이 실패하는 이유는, UIView에 스위프트로 구현된 layoutSubviews라는 메소드가 없기 때문이다. 이것은 모두 Objective-C로 구현되있다. 사실 아주 많은 스위프트에서 쓸 수 있는 Objective-C 메소드들이 Objective-C 메시지 전송으로 바로 전달하도록 컴파일된다. 스위프트 파일에 "UIView().layoutIfNeeded()"같은 것을 입력하면, 컴파일은 될것이며, layoutIfNeeded라는 스위프트 메소드가 없기때문에 호출해도 아무일도 일어나지 않을 것이다.

이것이 스위프트로 맵핑된 모든 Cocoa 타입들에게 해당되지는 않는다. 예를들어, 모든 "Data.write(to:options:)" 호출에 브레이크를 걸고 싶다고 생각하자. 아마 "Data.write"에 브레이크 포인트를 걸어서 동작하길 원할것이다.

(lldb) b Data.write
Breakpoint 11: where = libswiftFoundation.dylib`Foundation.Data.write (to : Foundation.URL, options : __ObjC.NSData.WritingOptions) throws -> (), address = 0x00000001044edf10

그리고 된다! 이건 어떨까? 실제로 이것만 아니다. 이것은 -[NSData writeToURL:options:error:]의 길에서 libswiftFoundation을 통해 전달하는 모든 호출에 브레이크가 걸릴 것인데, Objective-C 구현을 호출하는것에 직접 잡히지는 않을 것이다. 메소드에서 모든 호출을 잡기 위해서는, 저수준(Objective-C 메소드)에서 브레이크포인트를 설정할 필요가 있다.

따라서 규칙으로, iOS나 Mac 플랫폼에서 더 좋은 디버거를 원하는 스위프트 프로그래머들은 Objective-C의것과 동일하게 스위프트 메소드를 매핑할 수 있는 능력이 필요하다. UIView.layoutSubviews같은 메소드의경우, 바로 "-[UIView layoutSubviews]"로 맵핑하지만, 많은 메소드의경우 지금처럼 간단하게 될것이다.

스위프트로 맵핑된 메소드 이름을 Objective-C로 다시 매핑하기위해서는, 많은 Foundation 클래스들이 NS 접두를 뺐다는 것을 인식하고, 스위프트 API 가이드라인을 적용하기위해 메소드 시그니처를 다시 작성한 영향이다. 예를들어 순수한 스위프트 프로그래머들은 "Data.write(to:options)"의 저수준 구현으로 브레이크 포인트를 설정하기 때문에 쉽게 유추하지 못할것이다. "NS" 접두를 붙이고, URL 파라미터를 명시적으로 넣고, 이상한 에러 파라미터를 추가한다. 이것은 보기에 옛날의 나쁜 시기에 까탈스러운 백발의 노인이 실패를 전달하는데 사용하는 것 같아 보인다.

(lldb) b -[NSData writeToURL:options:error:]
Breakpoint 13: where = Foundation`-[NSData(NSData) writeToURL:options:error:], address = 0x00000001018328c3

성공했다!

이런 Obejctive-C 메시지 시그니처와 API 규약들에대한 추가적인 지식을 개발하게 만드는 생각은 여러분을 힘겹게 만든다는 점에서, 나는 여러분의 다음 과제를 통해 얻을 약간의 수정된것을 제공한다(I offer a little hack that will likely get you through your next challenge). API가 이런 원리중 하나를 사용햐여 작성되었으면, 함수의 스위프트 이름은 거의 Objective-C 메소드 이름의 부분집합이다. 아마도 lldb의 정규식 일치 기능을 활용하여 브레이크포인트를 설정하고싶은 메소드를 0으로 만들 수 있다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이제 "break list"를 입력하고 lldb가 표시한 매칭 수를 보자. 그것들 중에는 스위프트로 된 (libswiftFoundation으로 이루어진) 여러 메소드가 있으나, 질문에서 타겟 메소드를 찾아야할 것이다. 사실, 여러분이 브레이크를 걸고싶은 다른 저수준의 Objective-C 메소드도 찾아봐야할 것이다.

리스트를 더 잘 관리하기위해, 주어진 Objective-C 프레임워크안에 타겟 메소드가 있다는 지식을 주고, 이름으로 특정 공유된 라이브러리에 제한된 매칭을위한 "-s" 플래그를 넣는다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이 브레이크포인트 사이에서 NSPageData에서 몇 거짓이 있지만, 그 목록은 모두 더 관리할 수 있다. 하나의 브레이크포인트 "17"은 하위-숫자로 식별된 매치의 모든것을 가지고 있는다. 여러분의 방법으로 브레이크포인트의 목록을 걸러내도 좋다.

(lldb) break disable 17.6 17.7 17.8
3 breakpoints disabled.
(lldb) c

Objective-C를 스위프트에 맵핑하는 애플의 방식은 스위프트 개발자를위해 더 즐거운 프로그래밍 경험을 함께 만들게 해주지만, 그 세부적인 구현을 이해하지 못하고 있거나 어떻게 동작하는지에대한 이해가 부족하게되면 엄청난 혼란으로 빠질 수 있다. 나는 이 글이 여러분의 스위프트 앱을 디버깅하는데 필요한 도구가 되었으면 좋겠고, 불가피하게 사용하는 Objective-C 코드를 더 효율적이게 썼으면 좋겠다.

Update: 나는 두가지 관련된 버그를 넣었다: Radar #31115822 스위프트 메소드 포맷에서 자동으로 매핑한 것을 Obejctive-C 메소드로 돌아오도록 해야한다, 그리고 Radar #31115942 간결한 스위프트 메소드 시그니처를 만드는것에대해 lldb를 더 직관적으로 만들도록 해야한다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Safety In Swift


스위프트는 일반적으로 "세이프한" 언어로 불린다. 실제로 swift.orgAbout 페이지에서 이렇게 말한다.
스위프트는 세이프티, 퍼포먼스, 소프트웨어 설계 패턴의 현대적인 방법 사용을 내장한 일반 목적 프로그래밍 언어이다.
그리고
  • 세이프. 코드를 작성하는 가장 명백한 방법은 안전한 방법으로 동작할 수 있다. 정의되지않은 동작은 세이프티의 적이고, 개발자 실수들은 스프트웨어가 제품으로 되기 전에 잡힌다. 가끔 세이프티를 선택하는것이 스의프트가 엄격하게 느껴질 것이나, 장기적으로 봤을땐 명쾌하게 시간을 절약해줄 것이라 믿는다.
  • 빠름. 스위프트는 C 기반 언어(C, C++, Objective-C)를 대체하려는 목적이 있다. 이것처럼! 스위프트는 많은 작업의 퍼포먼스에서 이런 언어들과 반드시 비교된다. 또한 퍼포먼스는 단지 나중에 깨끗하게 만들어야하는 짧은 폭발적인 빠름이 아니라, 예상가능하고 일관되어야한다. 진귀한 기능을 가진 많은 언어가 있다. 빠른것은 희귀하다.
  • 표현력. 스위프트는 개발자들이 기대하는 현재의 기능과 함께, 즐겁게 사용할 수 있는 문법을 제공하기위해 십여년에서 나온 컴퓨터 사이언스의 증진의 이점이 있다. 그러나 스위프트는 아직 끝난게 아니다. 우리는 언어 증진을 탐색하고 계속해서 어떤 일이 스위프트가 더 나아지게 만드는지 포용할 것이다.

예를들어 우리가 Optional 타입과같은 것과 작업할때, 스위프트가 세이프티를 끌어올리는 것은 명확하다. 이전에는 어떤 변수들이 null이 될 수 있는지 없는지 몰랐다. 이런 새로운 널러빌리티(nullability) 정보로, 우리는 명시적으로 null 경우를 다루게 되었다. 이런 "널러빌리티" 타입으로 작업할때, 우리는 크레쉬를 선택할 수 있고, 보통 느낌표(!)를 포함한 연산자를 사용한다. 여기서 세이프티에의한 의미는 명확하다. 여러분의 위험에대해, 여러분이 잠글지 말지 정할 수 있는 안전띠 역할을 하는 것이다.

그러나 다른 경우에, 세이프티가 부족해 보인다. 한 예제를 보자. 한 딕셔너리를 가지고 있다면, 주어진 키(key)로 값을 쥐는것은 옵셔널을 반환한다.
let person: [String: String] = //...
type(of: person["name"]) // => Optional<String>
그러나 비슷하게 배열에서 하면, 옵셔널을 받지 않는다.
let users: [User] = //...
type(of: users[0]) // => User

왜 그러지 않을까? 배열은 비어있을수도 있다. 만약 users 배열이 비어있다면 프로그램은 다른 실제 선택없이 크레쉬될 수 있다. 이것은 세이프하다고 보기 힘들다. 다시 환불받고싶다!

흠, 좋다. 스위프트는 오픈 개발 프로세스니, 아마도 스위프트 에볼루션 메일링리스트에 이 변경사항을 제안할 수 있다.

안된다. 어느쪽도 하지 못할것이다. 깃헙 저장소의 스위프트-레볼루션 페이지에있는 "일반적으로 거절된" 프로포절들는이런 변경을 받아드리지 않을것이라고 말했다.
  • Array<T> 서브스크립 접근을 T?T! 대신에 T를 반환하는 것으로 만든다. 범위를 넘는 배열 접은은 로직 에러라는 사실을 정확하게 반영하기 때문에, 현재 배열의 동작은 의도적이다. 현재 동작을 바꾸는 것은 수용될 수 없는 정도로 배열 접근을 느리게할 수 있다. 이 주제는 이전에도 여러번 나왔지만 매우 받아드려지지 않을것으로 보인다.

무엇을 주는가? 진술된 이유는 이 특정 상황은 속도가 매우 중요하기 때문이라 했다. 그러나 위의 About 패이지 링크로 돌아가보면, "세이프"는 "빠름" 이전에 언어의 표현으로 목록에 나와있다. 세이프티가 속도보다 중요하기라도 한걸까?

여기엔 근본적인 논쟁이 있고, 그 해결책은 "세이프"라는 단어의 정의를 잡아야한다. 일반적인 "세이프"의 이해는 정도의 차이가 있어도 "크레쉬가 나지 않음"인 반면, 스위프트 코어 맴버들은 종종 "의도치않게 틀린 메모리에 절때 접근하지 않는것"이라는 의미로 쓴다.

이 경우, 스위프트의 배열 서브스크립션은 "세이프"이다. 배열이 절때로 할당된 범위 넘어서 메모리에있는 데이터에 접근하지 않을것이다. 메모리에 무엇이있든, 포함하지 않는 메모리에 접근하려고 하기 전에 크레쉬를 낼것이다. 같은 방법에서, 옵셔널 타입은 현존하는것으로부터 모든 클래스와 버그들(null에 접근하려는것)을 막으며, 이런 동작은 현존하는것으로부터 다른 클래스와 버그들(버퍼 오버플로우)을 막는다.

Chris Lattner가 ATP와했던 그 인터뷰의 24:39에서이런 구별을 만든것을 들어볼 수 있다.
 커뮤니티에 혼란에서 비용이라는 관점에서 보면 이해할 수 있는 유일한 방법은 우리가 세이프한 프로그래밍 언어를 만들면이다. "버그가 없다"는 것의 "세이프"가 아니라, 높은 퍼포먼스를 제공하고 프로그래밍 모델 앞으로 가는동안 메모리 세이프티 관점에서의 "세이프"이다.

아마 "메모리-세이프"는 그냥 "세이프"라는 용어보다 더 낫다. 방법은 이렇다. 어던 어플리케이션 프로그래머가 옵셔널로 돌아가는걸 좋아하는것 대신에 범위밖의 배열 접근에 트랩을 거는것을 좋아하는 반면, 모두가 유요하지 않은 데이터를 담은 변수로 계속 하는것보다 그 프로그래을 크래쉬 내는 것을 더 좋아할 수 있다는 것에 동의할 수 있다. 한 변수는 잠재적으로 버퍼 오버플로우 공격에 이용될 수 있다.

이 두번째 등가교환(버퍼 오버플로우를 허용하는것 대신 크래쉬 나는것)은 당연해 보이지만, 몇 언어들은 이 보장을 하지 않는다. C에서는 배열의 범위밖을 접근하는것이 여러분에게 정의되지 않은 동작을 할 수 있게 해주고, 어떤일이든 일어날 수 있다는 뜻이며, 우리가 사용했던 컴파일러 구현에 의존한다. 특히 프로그래머가 실수를 만들었다고 빠르게 말할 수 있을때(배열의 범위 밖 접근 같은), 그들이 옵셔널을 반환하는것 대신에 결정적으로 정크 메모리를 반환하는것 대신에 수용되는 곳에 크래쉬를 내는것을 좋게 느낀다는 것을 스위프트팀은 봐왔다.

"세이프" 정의를 사용하는 것도 "언세이프"한 API가 무엇을위해 설계되었는지 분명하게 한다. 왜냐면 그들은 직접적으로 메모리를 더럽히고, 프로그래머가 절때 유효하지않은 메모리에 접근하지 않을거라는 보장의 특별한 신경을 쓰게 만든다. 이것은 극도로 힘들고, 전문가들도 틀릴 수 있다. 이 주제에대한 글에 흥미가 있다면, 세이프한 방법으로 C를 스위프트에 연결시키는 Matt Gallagher’의 글 확인해보자.

스위프트와 그 코어팀의 "세이프"의 정의는 여러분의 생각에 100% 맞춰지지 않을 것이나, 그들은 클래스의 버그를 막아주어서 여러분같은 프로그래머들이 매일매일 그것에대해 생각하지 않아도 된다. 그 의미를 이해할때, "세이프"를 "메모리 세이프"로 대체하여 사용하면 종종 도움이 뒬 수 있다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Creating Usability with Motion: The UX in Motion Manifesto



역자: 이번 번역글은 늘 번역하던 개발관련 주제가 아닌데다 어려운 용어가 많아서 고민을 많이 했습니다. 제 번역글에서 부자연스러운 부분이나 잘못된 번역이 있다면 편하게 알려주시면 감사하겠습니다.




아래 선언서에는 "UX, UI 디자이너로서, 사용성을 위해 언제 어디서 모션을 구현해야하는지 어떻게 아는가? 질문에 대한 답변이다.

지난 5년동안, 나는 40개국 이상을 다니면서 몇백개의 상위 브렌드와 디자인 컨설던트, UX & UI 디자이너를 코치하고 멘토링하기위해 특별히 기여해왔고 UI 애니메이션에대한 튜토리얼을 했다.

그후 15년동안 사용자 인터페이스에대한 모션을 연구하여, 나는 모션을 사용한 UX 프로젝트에서 사용성에대해 12개의 특정 기회가 있다는 결론에 도달했다.

나는 이 기회를 "모션에서 UX에대한 12가지 원칙"이라 부르며, 이것들은 수많은 창조적인 방법으로 서로 쌓이고 섞일 수 있다.

나는 선언서을 5단계로 나누었다:
  1. UI 애니메이션 주제를 다루기-여러분이 생각하는 그런것은 아니다
  2. 실시간 vs 비실시간 인터렉션
  3. 모션이 사용성을 지원하는 여러방면
  4. 원칙, 기술, 프로퍼티, 값
  5. 모션에서 UX에대한 12가지 원칙

빠른 플러그로, 모션과 사용성에관한 주제에관해 학회나 워크샵에서 야러분의 팀위한 나의 강연을 원한다면, 여기로 가봐라. 여러분의 도시에서 수업에 참석하고 싶으면, classes">여기로 가보아라. 마지막으로 여러분의 프로젝트에대해 컨설팅을 받고 싶으면, 여기로 가면 된다. 내 목록에 추가되려면 여기로 가면 된다.



UI 애니메이션에 관한 것이 아니다
사용자 인터페이스에서 모션에관한 주제는 대부분 디자이너가 'UI 애니메이션'이라고 이해하고 있기 때문에(그렇지 않다), 12가지 원칙에 들어가기 전에 약간의 문맥을 만드는 것을 좋아한다.

디자이너들은 'UI 애니메이션'을 보통 더 즐거운 사용자 경험을 만드는 것이라 생각하지만, 전반적으로 어떤 가치를 더하지 않는다. 그러므로 UI 애니메이션은 종종 UX의 의붓자식처럼 다뤄진다(모든 의붓자식들에게 미안하다). If at all, it usually comes at the end, as a final lipstick pass.

추가로 사용자 인터페이스의 문맥에서의 모션은 디즈니의 12 애니메이션 원칙 문제 아래에 이해할 수 있게 잡혀있는데, 내가 쓴 'UI Animation Principles — Disney is Dead'에서 이야기해놓았다.

나의 선언서에는, 구성을 아키텍트화함으로서, UI 애니메이션은 '모션 원칙에서의 12가지 UX'가 된다는 경우로 만들것이다.

이렇게하여, 내가 말하고자하는 바는, 한 구조는 (요구하는 구성이) 존재하기위해 물리적으로 만들어져야하지만, 무엇이 만들어질지를 결정하는 안내는 원칙의 도메인으로부터 나온다.

애니메이션은 모두 도구에관한 것이다. 원칙들은 도구의 사용이나 그런것을 가이드하는 아이디어들의 실제 응용이다. 원칙들은 디자이너에게 높은 사용 기회를 제공한다.

많은 디자인들이 'UI 애니메이션'를 사실 디자인의 더 높은 형식성의 실행이라 생각한다: 실시간과 비실시간 이벤트동안 인터페이스 오브젝트의 시간적인 동작.



실시간 vs 비실시간 인터렉션
이 접점에서, '상태(state)'와 '동작(act)' 사이의 구별이 중요하다. UX에서 무언가의 상태는 근본적으로 정적이다. 마치 디자인 시안처럼 말이다. UX에서 무언가의 동작은 근본적으로 시간적이고 모션 기반이다. 한 오브젝트가 마스크된 상태에 있을 수 있고 혹은 마스크되고있는 동작에 있을 수 있다. 만약 후자이면, 모션이 포함되있고 때에 따라 사용성을 제공할 수도 있다.

추가로 인터렉션의 모든 시간적 양상은 실시간이나 비실시간으로 일어난다고 생각될 수 있다. 실시간은 사용자 인터페이스에서 사용자가 오브젝트와함께 직접 상호소통한다는 의미이다. 비실시간은 오브젝트 동작이 상호소통 이후에 일어난다는 의미이다: 사용자 동작 후에 일어나고 이동된다.



이 구별은 중요하다.

실시간 인터렉션은 '직접 조작'으로 생각할 수 있는데, 사용자는 인터페이스 오브젝트와 직접적으로 그리고 즉시 소통하고있다. 인터페이스의 이 동작은 사용자가 사용하고 있음으로서 일어난다.

비실시간 인터렉션은 사용자가 입력은 넣은 후에만 발생하고, 그 트랜지션이 완료될때까지 가볍게 사용자 경험으로부터 사용자를 락킹시키는 효과를 가진다.

이런 구별법은 모션에서 UX의 12가지 원칙 세계로 떠나는 여행내내 이용하게 될것이다.


모션은 4가지 방법으로 사용성을 제공한다.
이 네가지 기둥은 사용자 경험의 시간적 동작이 사용성을 지원하는 4가지 방법을 표현한다.

예상(Expectation)
예상은 2가지 영역으로 나뉜다. 사용자가 어떤 오브젝트인지 감지하는것, 그리고 어떻게 동작하는지 감지하는것이다. 디자이너로서 말하자면, 우리는 사용자가 기대하는 것과 그들의 경험 사이의 갭을 최소화시키고 싶다.

연속성(Continuity)
연속성은 사용자 흐름에대해서나 사용자 경험의 '일관성' 둘다에 대한 이야기이다. 연속성은 '내부-연속성'(장면내의 연속성 그리고 '내부 연속성')이라는 용어로 생각될 수 있는데, 일련의 장면에 연속성은 전체 사용자 경험을 만든다.

설명적(Narrative)
설명적은 사용자 경험에서 이벤트의 선형적 과정이다. 이것은 시간적/공간적 프레임워크의 결과가된다. 이것은 처음부터 끝까지 사용자 경험을 연결하는 일련의 인지된 순간들이나 이벤트들로 생각할 수 있다.

관계(Relationship)
관계는 사용자의 이해와 선택을 돕는 인터페이스 오브젝트들 사이에서 공간적인, 시간적인, 계층적인 표현이다.


원칙, 기술, 프로퍼티, 값
Tyler Waye says it as good as any when he writes, "원칙...들은 기술의 어떤 수치를 증가시키는 기능의 약속, 규칙을 근간으로한다. 이런 요소들은 조화롭게 있으며, 무슨일이 일어나도 상관없다." 원칙들은 디자인의 어그노스틱(agnostic)이라는 것을 되풀이한다.

거기서부터, 제일 높은 원칙과 그 아래 기술, 그 아래 프로퍼티, 제일 아래에 있는 값으로서 한 계층을 생각해볼 수 있다.

기술은 원칙의 다양한, 제한이없는 실행들 혹은/그리고 조합들이라 생각할 수 있다. 내 생각에 기술은 '스타일'과 유사한것 같다.

프로퍼티는 기술을 만들기위해 애니메이션하게될 특정 오브젝트 파라미터이다. 이것들은 위치(position), 투명도(opacity), 스케일(scale), 로테이션(rotation), 앵커포인트(anchor point), 색깔(color), 선두께(stroke-width), 모양(shape)등을 포함한다(제한이 없다).

은 우리가 '애니메이션'이라 부르는 그것을 만들기위해 매번 다른 실제 수치의 프로퍼티 값이다.

그래서 여기에 들어가기위해(조금 뛰어들어가기위해), 하나의 가상의 UI 애니메이션 참조는 '블러된 글라스' 기술로 엄폐(Obscuration) 원칙을 사용하고있는데, 이 '블러된 글라스' 기술은 블러(Blur)와 투명도(Opacity) 프로퍼티를 25px, 70% 값으로한다고 말할 수 있다.

이제 해보기위한 몇 도구를 가졌다. 그리고 더 중요한것은, 이런 용어의 도구들은 모든 특정 프로토타이핑 툴의 아그노스틱(agnostic)이다.


모션에서 UX의 12가지 원칙들
이징(Easing)과 오프셋(Offset)&딜레이(Delay)는 타이밍(timing)과 연관된다. 페어렌팅(Parenting)은 오브젝트 관계(object relationship)에 연관된다. 값 변경(Value Change), 마스킹(Masking), 오버레이(Overlay), 클로닝(Cloning)은 모두 오브젝트 연속성(object continuity)에 연관된다. 페럴랙스ㄹ(Parallax)는 시간적 계층에 연관된다. 엄폐(Obscuration), 차원(Dimensionality), 돌리(Dolly)&줌(Zoom)은 모두 공간적 연속성에 연관된다.



원칙1: 이징(Easing)
오브젝트 동작은 시간적 이벤트가 발생할때 사용자 예상을따라 일어난다.



이즈(ease)는 시간적 동작이 나오는 모든 인터페이스 오브젝트이다(실시간이든 비실시간인든). 이징은 부드러운 사용자 경험에서 타고난 '자연주의(naturalism)'을 만들고 그렇게되도록 한다. 그리고 사용자가 기대하는대로 오브젝트가 동작할때 연속성의 이해가 만들어진다. 덧붙이자면 디즈니는 이것을‘Slow_In_and_Slow_Out">Slow In and Slow Out’'라 부른다.

 



왼쪽의 예시는 선형적인 모션이고 '나빠' 보인다. 제일 위에 첫번째 예시는 이징된 모션인데 '좋아' 보인다. 위의 세 예시는 정확한 프레임 수와 정확히 동일한 횟수로 시행된다. 오직 이징에서만 다르다.

디자이너는 사용성을 고민하기 때문에, 미학은 제쳐두고 어떤 예시가 더 사용성을 제공하는지 우리 스스로 엄격하게 요구하고 조사해야한다.

내가 여기서 보여주는 경우는 스큐어모피즘의 특정 수치는 이징에 내제되있다. 사용자 기대를 벗어나는 것이 적은 사용성의 인터렉션이 되는점에서 '이징 그라디언트(easing gradient)'를 생각해볼 수 있다. 사용자 기대치를 벗어나는 동작으로인해 사용가능한 상호작용이 적게되는 '이징 그라디언트(easing gradient)'를 생각해볼 수 있다. 적절히 이징된 모션의 경우에, 사용자는 모션 그자체가 고르고 눈에 걸리지않는 경험을 한다. 주의를 분산시키지 않기 때문에 좋은 일이다. 선형 모션은 명확히 보이고 어찌되었든 완료되지 않은것같고, 거슬리고 눈에 걸린다.

이제 나는 여기서 내 스스로를 완전히 반박하고 옳은 예시에대해 이야기 할 것이다. 모션은 고르지 않다. 사실 그렇게 느끼도록 디자인되었다. 우리는 오브젝트가 어떻게 올라가는지 인지한다.다르게 느껴지지만 여전히 선형의 모션 예시보다 더 '알맞게' 느껴진다.

이징을 적용하지만 여전히 사용성이 없는가(Can you employ easing and still have it not support (or worse case undermine) usability?)? 그 대답은 그렇다. 그리고 여러 방법들이 있다. 한 방법은 타이밍이다. 만약 타이밍이 너무 느리거나(Pasquele에서 빌렸다) 너무 빠르면, 예상을 벗어나고 사용자의 주의를 분산시킬 수 있다. 비슷하게, 이징이 브랜드나 전반적인 경험에 잘못된 선상에 놓이면, 예상과 고름에 부정적인 영향을 줄수도 있을 것이다.

여러분에게 열어주고 싶은 것이 무엇이냐하면, 이징된 모션이 될때의 기회이다. 당신이 디자이너로서 프로젝트에서 만들고 구현할 수 있는 사실상 무한한 숫자의 이징이 있다. 이 모든 이징들은 사용자에서 발생하는 그들의 고유 예상 반응을 가진다.

요약하기: 언제 이징을 사용할까? 항상이다.

원칙2: 오프셋&딜레이(Offset&Delay)
새로운 엘리먼트와 화면이 나올때, 오브젝트 관계와 계층을 정의한다.


오프셋&딜레이는 디즈니의 애니메이션 원칙에 영향을 받은 모션 원칙의 두 UX 중 두번째것이다. 이것은 'Follow_Through_and_Overlapping_Action">Follow Through and Overlapping Action'로부터 온 케이스이다.

실행에서는 비슷하지만 목적과 결과에서는 다른, 그 구현에대해 인지하는것이 중요하다.디즈니의 원칙은 '더 시선을끄는 애니메이션'의 결과가 되는 반면, UI 애니메이션 원칙은 더 사용가능한 경험의 결과가 된다.

이 원칙의 효용은, 인터페이스에서 사용자에게 오브젝트의 자연스러움에대해 뭔가 말함으로서, 성공을위한 사용자를 미리 인지시키는 세팅 작업이다. 위의 참조에서 설명적인것은 위 두 오브젝트가 결합되있고 아래 오브젝트는 분리되있다. 아마도 위 두 오브젝트는 상호소통하지않는 이미지와 텍스트일수 있는 반면 하단의 오브젝트는 버튼일 것이다.

사용자가 이 오브젝트가 무엇인지 인지하기 전이라도, 디자이너는 이미 그에게 오브젝트들이 다소 '분리'되있다고 말해왔다. 이것은 매우 강력하다.


위 예시에서, 플롯팅 액션 버튼(FAB)는 세개의 버튼을 포함한 헤더 네비게이션 엘리먼트로 변형된다. 버튼들이 각기 다른 시간에 '오프셋'되기때문에, 그들의 '분리'를 통해 사용성을 제공하게된다. 다르게 말하면, 디자이너는 시간을 사용하여 (사용자가 오브젝트를 인지하기 전일지라도) 오브젝트들이 분리되있음을 말한다. 이것은 사용자에게 말하는 효과를 가지고, 시각적 디자인으로부터 완전히 독립적이며, 인터페이스에서 오브젝트의 자연스러운 부분이다.

어떻게 하는지 보여주기위해 오프셋&딜레이 원칙을 나눈 예시를 보여주겠다.


위의 예시에서는 정적인 시각적 디자인이 우리에게 백그라운드 넘어에 아이콘들이 있다고 말한다. 아이콘들이 각자 서로 분리되고 서로 다른 일을 한다고 어림짐작할 것이다. 그러나 모션은 이것을 부정한다.

시간적으로는, 아이콘이 줄 단위로 묶여서 마치 한 오브젝트인것처럼 동작한다. 그 타이틀들도 마찬가지로 줄 단위로 묶여있어서 한 오브젝트처럼 동작한다. 이 모션은 사용자이 눈으로 본것과 다른 무언가를 말하고 있다. 이 경우에, 우리는 이 오브젝트의 인터페이스상 시간적 동작은 사용성을 제공하지 ㅇ낳고 있다고 말할 수 있다.

원칙3: 페어렌팅(Parenting)
다수의 오브젝트와 상호소통할때 공간적, 시간적 계층의 관계를 만든다.


페어렌팅은 사용자 인터페이스에서 오브젝트에 '관계'를 부여하는 강력한 원칙이다. 위 예시에서는, 상위 혹은 '자식' 오브젝트의 '스케일'과 '포지션' 프로퍼티는 하위 혹은 '부모' 오브젝트의 '포지션' 프로퍼티에 부모역할을 한다.

페어렌팅은 오브젝트 프로퍼티를 다른 오브젝트 프로퍼티에 연결한다. 이것은 사용성을 제공하는 방법으로 오브젝트 관계와 계층을 만든다.

또한 페어렌팅은 자연스러운 오브젝트 관계를 사용자에게 전달하면서 디자이너가 사용자 인터페이스상의 더 나은 동위의 시간적 이벤트를 만들 수 있게 해준다.

스케일, 투명도, 위치, 로테이션, 모양, 색깔, 값등을 포함한 오브젝트 프로퍼티를 재호출한다. 이런 모든 프로퍼티는 사용자 경험에서 시너지스틱한 순간을 만들기위해 다른 프로프터에 연결될 수 있다.

 



위에서 왼쪽 예시에서, 얼굴 엘리먼트의 'y축' 프로퍼티는 둥근 인디케이터 부모의 'x축' 프로퍼티에 자식역할을 한다. 둥근 인디케이터 엘리먼트는 수평 축을 따라 움직일때, 그 '자식' 엘리먼트는 수평과 수직을 따라 움직인다(마스크(또다른 원칙이다)되고 있지만).

이 결과는 동시에 발생하는 계층적인 시간의 설명적 프레임워크이다. 각각의 숫자에서 '얼굴'이 부분적으로 완전히 보이고 보이지 않는다는 점에서 '얼굴'은 일련의 '잠금 장치(lockup)'로 작동한다는 점을 인지하자. 사용자는 이런 고름을 경험하지만 우리는 이 예시에서 미묘한 '사용성 치트'가 있다고 말할 수 있다.

페어렌팅은 '실시간' 인터렉션으로는 최고의 기능을 한다. 상ㅇ자가 직접 인터페이스 오브젝트를 조작하기 때문에, 디자이너는 모션을통해 사용자에게 소통할 수 있다(어떻게 오브젝트들이 연결되있는지, 그들사이에 어떤 관계가 있는지)

페어렌팅은 3가지 형식으로 나타난다: '직접 페어렌팅(direct parenting)' (위에서 본 두 예시), '지연된 페어렌팅(delayed parenting)'과 '역 페어렌팅(inverse parenting)' (아래에서 보이는 것)

 



원칙 4 : 변형(Transformation)
오브젝트 기능이 바뀔때, 설명적 흐름의 지속적인 상태를 만든다.
 


모션 원칙 '변형'에서 UX에대해 이미 많이 알아보았다. 어떤 면에서는, 가장 명확하고 간파하기 쉬운 애니메이션 원칙이다.

변형은 보면 가장 알기 쉽고, 가장 눈에띄게 보인다. 'submit' 버튼의 모양이 원형의 프로그래스 바로 바뀌고 결국 확인 체크 표시의 모양으로 돌아오는 것은 우리가 인지하는 것이다. 이것은 시선을 잡아주고, 이야기를 말하며, 완료시킨다.


트렌스포메이션이 하는 일은, 다른 UX 상태나 '이다(is)'(이것은 버튼이다, 이것은 원형 프로그래스 바이다, 이것은 체크 표시이다)를 통해 사용자를 균일하게 트렌지션하여 결국 원하는 결과를 만들어낸다(원문: What Transformation does is seamlessly transition the user through the different UX states or ‘is’s’ (as in this is a button, this is a radial progress bar, this is a check mark) which eventually result in a desired outcome). 사용자는 이 기능적인 공간을 통해 최종 목적지로 이끌린다.

변형은 사용자 경험에서 고르게, 연속적인 이벤트로 핵심 순간들을 나누는 효과를 가진다. 이 고름은 더 나은 사용자 인지, 기억, 완수의 결과가 된다.

원칙 5: 값 변경
값 주체가 바뀔때, 동적이고 연속적인 설명적 관계를 만든다.


텍스트 기반 인터페이스 오브젝트(숫자나 글자를 표현하는)는 그 값을 바꿀 수 있다. 이것은 '정의하기 어려운 명확한(elusive obvious)' 개념중 하나이다.

텍스트와 숫자의 변경은 매우 일반적이어서 유용성을 지원할 때 자신의 역할을 평가할 수있는 구별과 엄격함을 제공하지 않으면서도 이를 통과시킨다.

그래서 값이 바뀔대 사용자 경험이 무엇일까? 사용저 경험에서는 모션 원칙에서 12가지 UX가 사용성을 제공하는 기회들이다. 여기 세가지 기회는 데이터 뒤의 현실성에이젠시 개념, 그리고 값 그자체의 동적인 자연스러움을 위해 사용자를 열결시키는 것이다.

아래 사용자 데시보드 예시를 보자.

 


값 기반 인터페이스 오브젝트가 '값 변경' 없이 불러오면 사용자에게 숫자가 정적인 오브젝트라는 것을 전달하게 된다. 이것은 시속 55마일 속도 제한을 표시하는 표지판과 비슷하다.

숫자와 값은 현실에서 일어나는 것들의 표현이다. 현실은 시간, 수입, 게임 스코어, 사업 메트릭스, 건강 추적 등일 수 있다. 모션을 통해 구별해야하는것은 '겂 주체'는 동적이고 값은 동적인 값 설정으로부터 무언가를 반영한다.

이 관계는 시각적인 값으로 구성된 정적 오브젝트로 인해 손실될뿐만 아니라 더 깊은 기회도 잃는다.

모션 기반 값에서 동적 시스템의 표현을 채택했으면, '뉴로피드백'같은 것을 활성화시킨다. 그 데이터의 동적인 자연스러움을 잡고있는 사용자들은 이제 값을 변경할 수 있게 되고 에이전트가 될 수 있게 한다. 값이 정적이면 값 뒤에 현실에 더 적게 연결되고 사용자는 에이전시를 잃는다.

  


값변경 원칙은 실시간 이벤트 비실시간 이벤트 둘 다로 나타날 수 있다. 실시간 이벤트에서는 사용자가 값 변경을 위해 오브젝트와 상호소통한다. 불러올때나 트렌지션같은 비실시간 이벤트에서는 동적인 설명을 반영하기위해 사용자 입력 없이 값을 변경한다.

원칙 6: 마스킹(Masking)
오브젝트나 그룹의 어떤 부분에의해 결정되는 기능이 나타나거나 숨겨질때, 한 인터페이스 오브젝트나 오브젝트 그룹에 연속성을 만든다.


이 마스킹  요청은 오브젝트의 모양간의 관계와 그 기능으로 생각할 수 있다.

디자이너들이 정적 디자인의 문맥에서 '마스킹'에 친숙하기때문에, 시간내 상태로서가 아닌 동작으로서 발생하는 모션 원칙 '마스킹'에서의 UX를 구별하는 것이 필요하다.

오브젝트의 범위를 드러내고 숨기는 시간적 용도를 통해, 기능은 연속적으로 고른 방법으로 트랜지션된다. 또한 설명적 흐름을 보존하는 효과도 가진다.


위 예시는, 헤더 이미지의 바운딩 모양과 포지션을 변경하지만 컨텐트는 변경하지 않은체 앨범이 된다. 마스크로 컨텐트롤 보존하면서, 이것은 그 오브젝트가 무엇인지 변경하는 효과를 가진다(꽤 멋진 기술로). 이것은 트랜지션이라 비실시간으로 일어나고, 사용자 액션을 받고 난 뒤에 활성화된다.

UI 애니메이션 원칙들은 시간적으로 일어나고, 연속성, 설명적, 관계, 예상을통해 사용성을 제공한다는 점을 기억하자. 위 참고자료에서 오브젝트 자체가 바뀌지 않은채로 남아있는 동안, 바운더리와 위치를 가지고 있고, 이 두가지 요인은 이게 어떤 오브젝트인지 결정한다.

원칙 7: 오버레이
층의 오브젝트가 종속된 위치일때, 설명적인 것과 시각적 2차원에서 오브젝트의 공간적 관계를 만든다.


오버레이는 비공간적 계층의 부재를 극복하기위해 사용자에게 2차원의 순서있는 프로퍼티들을 이용하게 함으로서, 사용성을 제공한다.

오버레이는3차원 공간이 아닌 곳에서, 다른 오브젝트보다 뒤나 앞에 있는 위치가 종속된 오브젝트들을 소통하기위해 디자이너들은 모션을 사용할 수 있다.

 


왼쪽의 예시는. 뒷편에 위치한 추가적인 오브젝트를 드러내기위해 앞편의 오브젝트를 오른쪽으로 민다. 오른편의 예시는, 추가적인 컨텐트와 옵션을 드러내기위해 전체 화면을 아래로 민다(각 사진 오브젝트를 소통하기위해 오프셋&딜레이 원칙을 사용하면서).

약간은 디자이너로서 레이어' 방법은 자명할 정도로 매우 명확하다. 우리는 레이어로 디자인하고 레이어 개념은 깊게 내재되있다. 그러나 '만드는것'과 '사용하는것'의 과정은 조심해서 구별해야한다.

계속해서 '만드는' 과정에 끌려가버린 디자이너로서, 우리가 만든 모든 오브젝트 부분들에 충분히 친숙하다. 그러나 사용자로서 눈에 보이지 않는 부분은 시각적으로나 인지적으로 숨겨진 정의와 동작에 의한 것입니다.

오버레이 원칙은 디자이너들에게 'z축' 위치의 레이어 사이에 관계를 통해, 사용자에게 공간적 방향을 개선한다.

원칙 8: 클로닝(Cloning)
새 오브젝트가 시작하고 출발할때, 연속성, 관계, 설명을 만든다.


새 오브젝트가 현재 화면에서 (혹은 현재 오브젝트로부터) 만들어질때, 그 출현에대해 설명적으로 알려주는 것은 중요하다. 이 선언서에서 나는 오브젝트 시작과 출발을위한 설명적 프레임워크를 만드는것이 강력하게 중요하다고 주장한다. 간단하게 투명도 조절은 이런 결과를 달성할 수 없다. 강력한 설명을 만들기위해 마스킹, 클로닝, 차원은 사용성 기반 세가지 접근법이다.

  


위 세가지 예시에서는, 사용자가 이 오브젝트에 집중하고 있는 시간동안 히어로 오브젝트로부터 새 오브젝트를 만든다. 접고 펼치는 위 두가지 예시의 접근법(주의를 잡고, 클론된 새 오브젝트의 생성을 통해 눈을 끈다)은 이벤트를 명확하고 모호하지않게 연결하여 소통하는 강한 효과를 가진다: 액션 'x'는 새 자식 오브젝트의 생성 'y'의 결과를 가진다.


원칙 9: 엄폐(Obscuration)
사용자에게 주요 시각 계층이 아닌 오브젝트나 화면에 관계에서 공간적으로 그 자체를 익숙하게 해준다.


모션 원칙의 마스킹과 비슷한데, 엄폐는 정적 현상과 시간적 현상 둘다에 존재한다.

시간적인 생각을 경험하지 못한 디자이너에게는 혼란스러울 수 있다(이것은 순간 사이의 순간이다). 보통 디자이너들은 화면대 화면을 디자인하거나 작업대 작업을 디자인한다. 엄폐라는것을 엄폐된 상태가 아니라 엄폐되는 동작이라고 생각하자. 정적인 디자인은 엄폐된 상태를 표현한다. 시간을 도입하여 엄폐되고있는 오브젝트의 동작을 만들어준다.

 


위의 두 예시에서, 엄폐를 볼 수 있는데, 투명한 오브젝트나 오버레이처럼 보인다. 또한 시간내 여러 프로퍼티를 포함하는 시간적 인터렉션이다.

여기서의 여러 일반적인 기술에는 블러 효과와 전체 오브젝트 약한 투명성(lessoning of overall object transparency)을 가진다. 사용자는 그가 동작하고있는 곳에 추가적으로 주요하지 않는 문맥을 알게 되었다(그 주요 오브젝트 계층 '뒷편에' 또다른 세계가 있다).

엄폐는 사용자 경험에서 뷰나 '목표 뷰'를 하나로 만들기위해 디자이너들이 보완할 수 있게 해준다.

원칙 10: 페럴랙스(Parallax)
사용자가 스크롤 할때, 시각적 2차원에서 공간적 계층을 만든다.


'페럴랙스'는 모션 원칙에서 한 UX로서 다른 비율로 움직이는 다른 인터페이스 오브젝트를 표현한다.

'페럴랙스'는 디자인 완결을 유지하면서 동시에 사용자를 주요 액션과 컨텐트에 집중하게 해준다. 페럴랙스 이벤트동안 사용자는 뒷편의 엘리먼트들은 뒤로 들어가있는 지각과 인식을 준다. 디자이너는 배경이나 도움을주는 컨텐트로부터 직접적인 컨텐트를 분리해낼 수 있게 페렐랙스를 사용할 수 있다.

 


사용자에게 있는 이 효과는 인터렉션의 지속시간, 다양한 오브젝트 관계를 명확하게 정의하는 것이다, 앞쪽의 오브젝트, 혹은 사용자에게 '가까히' 나타나서 '빠르게' 움직이는 오브젝트이다. 비슷하게, 뒷쪽의 오브젝트, 혹은 '멀리 있다'고 인지되어 '천천히' 움직이는 오브젝트이다.

디자이너는 이런 관계를 만들 수 있는데, 시간 자체를 사용하여 인터페이스에서 어떤 오브젝트가 높은 우선순위를 갖는지 사용자에게 알려준다. 따라서 뒷편에 밀어넣거나 상호소통하지 않는 오브젝트를 더 '뒤로'하여 이해할 수 있다.

시각적 디자인에서 어떤것이 결정되었는지를 넘어, 사용자들이 이제 인터페이스 오브젝트가 계층을 가진다고 인지할 뿐만 아니라, 이 계층은 이제 그 디자인/컨텐트를 의식하기 전에 자연스러운 사용자 경험을 사용자가 가지는데에 이용할 수 있다.

원칙 11: 차원(Diemnsionality)
새 오브젝트가 시작되거나 출발할때, 공간적 설명의 프레임워크를 제공한다.


사용자 경험에 중요한 점은 위치의 이해만큼 연속성의 현상이다.

차원은 2차원의 비논리적 사용자 경험을 극복하는 강력한 방법을 제공한다.

인간은 실제 세상과 디지털 경험 둘다 설명을 위해 공간적 프레임워크 사용에 두드러지게 적응된다. 공간적 시작이나 출발의 레퍼런스를 제공하면 사용자가있는 UX의 정신적 모델을 증진하는데 도움이 된다.

또한 Dimensionality Principle은 깊이가없는 객체가 같은 평면에 존재하지만 다른 객체의 '앞에 또는'뒤에 존재하는 시각적 인 평지의 계층적 역설을 뛰어넘는다.

차원은 세가지 방법으로 표현된다. 오리가미 차원(Origami Dimenstionality), 플롯팅 차원(Floating Dimenstionality), 오브젝트 차원(Object Dimenstionality).

오리가미 차원은 '접는' 혹은 '매딜린' 셈차원 인터페이스 오브젝트의 용어라 생각할 수 있다.

 


여러 오브젝트들이 '오르가미' 구조로 결합되기 때문에, 숨겨진 오브젝트들은 공간적으로 보이지 않더라도 아직 '존재한다'고 말할 수 있다. 이것은 효과적으로 사용자 경험을 랜더링하는데, 인터렉션 모델 자체에서나 인터페이스 오브젝트 그 자체의 시간적 동작에서 둘 다 사용자가 작업의 문맥을 이야기하고 만든다.

플롯팅 차원은 인터페이스 오브젝트에게 공간적 시작/출발을 제공하여, 직관적이고 매우 설명적인 인터렉션 모델을 만든다.


위 예시에서, 차원은 3D '카드' 사용을 통해 달성되었다. 이것은 시각적 디자인을 제공하는 강력한 설명적 프레임워크를 제공한다. 이 설명은 추가적인 컨텐트와 상호활동에 접근하기위해 카드를 '가볍게 튀기면서(flipping)' 연장된다. 차원은 갑작스러움을 최소화하는 방법으로 새 엘리먼트를 소개하는 강력한 방법일 수 있따.

오브젝트 차원은 실제 깊이와 양식으로 차원적인 오브젝트가 된다.

 


여기에는, 실제 공간적인 오브젝트를 형성하기위해 3D 공간에 여러 2D 레이어가 정렬되있다. 그들의 차원은 실시간과 비실시간 트랜지션의 순간에 드러난다. 오브젝트 차원의 이 기능은 사용자가 보이지 않는 공간 위치에 기반한 오브젝트 기능의 예리한 인지를 전개하는 것이다.

원칙 12: 돌리(Dolly)&줌(Zoom)
인터페이스 오브젝트와 공간이 이동할때, 연속성과 공간적 설명을 보존한다.


돌리와 줌은 영화에서의 개념인데, 카메라에 관련된 오브젝트의 이동이나 먼 샷에서 가까운 샷으로(혹은 그 반대도) 부드럽게 변하는 프레임에서 이미지 그자체의 크기와 관련된 오브젝트 이동을 의미한다.

특정 문맥에서, 3D 공간에서 카메라를 향해 이동하거나 3D공간에서 카메라가 오브젝트를 향해 카메라가 이동한다면, 오브젝트가 줌되는지 말하기는 불가능하다(아래 참조를 보자). 아래 세가지 예시는 가능한 시나리오를 설명한다.


'돌리'와 '줌'은 둘 다 연속적인 엘리먼트와 씬상의 변형을 포함함하고 모션 원칙에서 모션을 통해 사용성을 제공한다는 UX의 요구사항을 맞춘다는 점에는 비슷하지만, '돌리'와 '줌'의 인스턴스를 분리해서 다루는것이 적절하다.

  


돌리는 영화 용어인데, 물체를 향해 가거나/멀어지는 카메라 이동에 적용된다(또한 수평적 '추적의' 이동에 적용하는데, 사용성 문맥에는 적게 관련된다).


UX에서 공간적으로, 이 모션은 사용자의 시점에서의 변화이거나 오브젝트가 위치를 변경하면서 남아있는 고정되어있는 시점을 의미한다. 돌리 원칙은 연속성과 설명을 통해 사용성을 제공하고, 인터페이스 오브젝트들과 목적지간의 고른 트랜지션을 한다. 돌리는 차원 원칙과 통합하여 더 공간적인 경험과 더 깊이감의 결과를 만들고, 사용자에게 현재 뷰의 '앞'/'뒤'에 추가적인 영역이나 컨텐트를 알려준다.

줌은 시점이나 오브젝트가 공간적으로 이동하는것이 아닌 곳의 이벤트를 의미하지만, 오브젝트 자체가 스케일되는것이다(혹은 이것의 우리 뷰가 작아져서 확대된 이미지가 된다). 이것은 보는이에게 다른 오브젝트나 화면 안에 추가적인 인터페이스 오브젝트를 알려준다.


 고른 트랜지션(실시간, 비실시간 둘 다)으로 사용성을 제공한다. 돌리&줌 원칙에서 나온 고름은 공간의 정신적 모델을 생성하게될때 꽤 강력하다.



이 긴 것을 완주했다면, 축하한다! 야생의 선언서였다. 모든 gif들이 로딩되서 여러분의 브라우저를 죽이지 않았길 바란다. 또한 여러분 스스로를 위한 어떤 가치와 새로운 도구를 얻었길 바라며 여러분의 인터렉티브 프로젝트에 사용되길 바란다.

사용성을 제공하기위한 디자인 툴로서 어떻게 모션 사용을 시작할 수 있는지에대해 좀 더 배우길 바란다.

다시, 마지막 플러그로, 모션과 사용성에관한 주제에관해 학회나 워크샵에서 야러분의 팀위한 나의 강연을 원한다면, 여기로 가봐라. 여러분의 도시에서 수업에 참석하고 싶으면, 여기로 가보아라. 마지막으로 여러분의 프로젝트에대해 컨설팅을 받고 싶으면, 여기로 가면 된다.내 리스트에 추가되려면 여기로 가면 된다.



이 선언서는 아마존의 Kateryna Sitner의 끊임없는 피드백과 관대하고 인내심있는 기여가 없었다면 가능하지 않았을 것이다. 감사합니다! 브레인스토밍과 I land the plane하는 주장에 Alex Chang, 매의 눈을 가진 마이크로소프트의 Bryan Mamaril, 편집 노트의 Jeremey Hansom, 놀라운 UI 모션 지도자가 된 Eric Braff, 몇년간 나를 믿고 따라온 Artefact의 Rob Girling, 애프터 이펙트 학회에서 UI 모션에대한 영감을 주는 Matt Silverman, 멋진 룸메이트이자 UI에 영감을 주는 Bradley Munkowitz, 모션에서 놀라운 글을 쓰는 Pasquale D’Silva, UI 안무(Choreography)에대한 멋진 글의 Rebecca Ussai Henderson, UI와 모션에관한 주제에 멋진 기여를 하는 Adrian Zumbrunnen, 나의 지능적 형재로 항상 나의 수준을 올려주는 Wayne GreenfieldChristian Brodin, 그리고 영감의 gif들을 만들어내고있는 수천의 UI 애니메이터들에게 특별 감사를 전한다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: What’s New in Swift 3.1?


멋진 소식: Xcode8.3과 스위프트3.1이 베타로 나왔다! 이 릴리즈는 오래 기다린 스위프트 패키지 매니저 기능들과 언어 자체의 증진을 담고있다.

Swift Evolution Process에 가깝게 팔로우하고 있지 않았다면, 이 글을 계속 읽어보자(이 글은 그런 여러분을 위한 글이다!).

이 글에서는, 스위프트3.1에서 여러분 코드에 주요하게 영향을 줄 수 있는 가장 중요한 변화들만 집어주겠다. 안으로 들어가보자! :]

시작하기
스위프트3.1은 스위프트 3.0과 소스-호환이 되므로, 이미 Xcode에서 여러분의 프로젝트를 Edit\Convert\To Current Swift 를 사용해서 스위프트 3.0으로 마이그래이션 했다면, 새 기능이 여러분의 코드를 망가뜨리지 않을 것이다. 그러나 애플은 Xcode 8.3에서 스위프트 2.3 지원을 멈췄다. 따라서 아직 스위프트 2.3에서 마이그레이션을 하지 않았다면 지금이 그 때이다!

아래 섹션에서는, [SE-0001] 같은 링크의 태그를 볼 수 있을 것이다. 이것들은 Swift Evolution 프로포절 번호이다. 각 프로퍼절에 링크를 넣었놨으니 각 특정 변경에대한 세부적인 사항들을 찾을 수 있을 것이다. 나는 우리가 이야기나는 기능들을 플레이그라운드에서 시도해보길 추천하며, 그렇게하여 여러분은 그 모든 변화를 더 잘 이해할 수 있다.

따라서 Xcode를 켜서, File\New\Playground...를 선택하고 플랫폼은 iOS를 선택한다. 여러분이 원하는 아무거나 호출하고, 원하는 아무거나 저장하자. 이 글을 읽는동안, 각 기능을 플레이그라운드에서 시도해보아라.

Note: 만약 스위프트 3.0의 짧고 간단한 하이라이트 리뷰가 필요하다면, What’s New in Swift 3를 확인해보자.

언어 개선
먼저, 숫자 타입의 실패할수있는 생성자, 새로운 시퀀스 함수등을 포함하여, 이번 릴리즈에서 언어 개선에대해 살펴보자.

실패할수있는 숫자 변환 생성자(Failable Numeric Conversion Initializers)
스위프트3.1은 모든 숫자 타입(Int, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)을위해 실패할수있는 생성자를 만들었다. 이것은 정보 손실없이 성공적으로 완료하거나 아니면 간단하게 nil을 반환한다 [SE-0080]

이 기능은 유용한데, 예를들어 세이프와 복구가능한 방법에서 외부 소스로부터 느슨한 타입 데이터 변환을 다룰때가 있다. 예를들어서, 한 Student JSON 배열을 어떻게 처리하는지보자.

class Student {
  let name: String
  let grade: Int
  
  init?(json: [String: Any]) {
    guard let name = json["name"] as? String,
          let gradeString = json["grade"] as? String,
          let gradeDouble = Double(gradeString),
          let grade = Int(exactly: gradeDouble)  // <-- 3.1 feature here
    else {
        return nil
    }
    self.name = name
    self.grade = grade
  }
}

func makeStudents(with data: Data) -> [Student] {
  guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
        let jsonArray = json as? [[String: Any]] else {
    return []
  }
  return jsonArray.flatMap(Student.init)
}

let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"},
                    {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, 
                    {\"name\":\"Steven\", \"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)]

아래처럼 실패할수있는 생성자를 명시하여 Stuent 클래스 안에 Double에서 Intgrade 프로퍼티를 변환하는데 실패할수있는 생성자를 사용한다.

let grade = Int(exactly: gradeDouble)

gradeDouble6.33같은 분수 값이고, 이것은 실패할 것이다. 만약 6.0같은 정확한 Int로 표현할 수 있다면 이것은 성공할 것이다.

Note: 실패할수있는것 대신에 대안의 설계는 throwing 생성자를 사용하는 것이었다. 커뮤니티는 실패할수있는것이 더 낫고 더 인간환경공학적 설계라고 선택했다.

새로운 시퀀스 함수들
스위프트 3.1은 데이터 필터링을위한 표준 라이브러리의 Sequence 프로토콜의 두가지 함수를 추가했다. prefix(while:)drop(while:)이다. [SE-0045]

피보나치 무한 시퀀스를 생각해보자.

let fibonacci = sequence(state: (0, 1)) {
  (state: inout (Int, Int)) -> Int? in
  defer {state = (state.1, state.0 + state.1)}
  return state.0
}

스위프트 3.0에서 우리는 fibonacci sequence를 돌기위해 반복 카운터를 명세했었다.

// Swift 3.0
for number in fibonacci.prefix(10) {
  print(number)  // 0 1 1 2 3 5 8 13 21 34
}

스위프트 3.1은 주어진 두 값 사이에 시퀀스의 모든 엘리먼트를 뽑아내기위해 조건과함께 prefix(while:)drop(while:)을 사용할 수 있게 해준다.

// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
  print(element) // 144 233 377 610 987
}

prefix(while:)은 특정 조건을 만족시키는 가장 긴 서브시퀀스를 반환한다. 시퀀스의 첫부분부터 시작해서 주어진 클로저가 false를 반환하는 첫번째 엘리먼트에서 멈춘다.

drop(while:)은 그 반대이다. 주어진 클로저에서 false를 반환하는 첫번째 엘리먼트에서 시작해서 스퀀스의 끝에서 멈춘다.

Note: 이 경우엔 트레일링 클로저 문법(trailing closure syntax)을 사용할 수 있을 것이다.
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

구체적인 제한 익스텐션(Concrete Constrained Extensions)
스위프트 3.1은 구체적인 타입 제한으로 제네릭 타입을 익스텐션할 수 있게 해준다. 이전에는, 프로토콜로 제한되어있어서 이런 타입을 익스텐션할 수 없었다. 예제를 살펴보자.

예를들어, 루비온레일즈는 사용자 입력을 확인하기위한 유용한 isBlank 메소드를 제공한다. 여기에 스위프트 3.0에서 String 데이터 타입 익스텐션에서 어떻게 계산된 프로프터(computed property)로 구현할 수 있는지 보자.

// Swift 3.0
extension String {
  var isBlank: Bool {
    return trimmingCharacters(in: .whitespaces).isEmpty
  }
}

let abc = " "
let def = "x"

abc.isBlank // true
def.isBlank // false
isBlank 계산된 프로퍼티가 옵셔널 문자열로 동작하기 원한다면, 스위프트 3.0에서 아래를 할 수있을것이다.
// Swift 3.0
protocol StringProvider {
  var string: String {get}
}

extension String: StringProvider {
  var string: String {
    return self
  }
}

extension Optional where Wrapped: StringProvider {
  var isBlank: Bool {
    return self?.string.isBlank ?? true
  }
}

let foo: String? = nil
let bar: String? = "  "
let baz: String? = "x"

foo.isBlank // true
bar.isBlank // true
baz.isBlank // false

이것은 String에 적용하기위해 StringProvider 프로토콜을 만들었다. isBlank 메소드를 추가하기위해 Wrapped 타입이 StringProvider일때 Optional을 확장하는데 사용한다.

스위프트 3.1은 프로토콜 대신에 아래처럼 구체적인 타입을 익스탠션하게 해준다.

// Swift 3.1
extension Optional where Wrapped == String {
  var isBlank: Bool {
    return self?.isBlank ?? true
  }
}

이것은 이전과 같은 기능을 하나, 아주 많이 코드를 줄일 수 있다!

감싸진 제네릭(Nested Generics)
스위프트 3.1은 제네릭으로 감싸진 타임을 섞을 수 있게 해준다. 예제로, 이것을 보자. raywenderlich.com에서 이끌고있는 어떤 팀이 블로그에 포스팅을 발행하고 싶을 때마다, 웹사이트의 높은 퀄리티 기준을 맞추기위해 이 발행에 참여한 개발자들의 팀을 넣는다.

class Team {
  enum TeamType {
    case swift
    case iOS
    case macOS
  }
  
  class BlogPost {
    enum BlogPostType {
      case tutorial
      case article
    }
    
    let title: T
    let type: BlogPostType
    let category: TeamType
    let publishDate: Date
    
    init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) {
      self.title = title
      self.type = type
      self.category = category
      self.publishDate = publishDate
    }
  }
  
  let type: TeamType
  let author: T
  let teamLead: T
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    self.type = type
    self.author = author
    self.teamLead = teamLead
    self.blogPost = blogPost
  }
}

Team이라는 외부 클래스안에 BlogPost라는 내부 클래스를 감싸고(nest) 두 클래스를 모두 제네릭 클래스로 만든다. 이것은 이 팀이 지금까지 웹사이트에 내가 발행한 튜토리얼과 글을 찾는 방법이다.
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, 
     category: .swift, publishDate: Date()))

Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, 
     category: .swift, publishDate: Date()))
그러나 실제로는, 이 경우 좀 더 심플하게 만들 수 있다. 감싸진 내부 타입은 제네릭 외부 타입을 사용하면 디폴트로 부모 타입을 상속한다. 따라서 이것을 정의할 필요가 없다.
class Team {
  // original code 
  
  class BlogPost {
    // original code
  }  
  
  // original code 
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    // original code   
  }
}
Note: 스위프트에서 제네릭에대해 더 배우고 싶다면, 우리가 최근에 업데이트한 getting started with Swift generics를 읽어보라.

스위프트 버전 사용 가능
여러분은 if swift(>= N) static construct를 사용해서 특정 스위프트 버전을 체크할 수 있다.
// Swift 3.0
#if swift(>=3.1)
  func intVersion(number: Double) -> Int? {
    return Int(exactly: number)
  }
#elseif swift(>=3.0)
  func intVersion(number: Double) -> Int {
    return Int(number)
  }
#endif
그러나 이런 방법은 스위프트 표준 라이브러리같은 곳에서 사용될때 중요한 결점을 가지고 있다. 이것은 각각 예전 언어 버전을 지원하기위해 컴파일하는 표준 라이브러리를 요구한다. 여러분이 스위프트 컴파일러를 예전의 호환성 모드로 실행시킬때(예를들어 스위프트 3.0 동작을 원한다고 말할때), 특정 호환 버전으로 컴파일된 표준 라이브러리 버전을 사용해야할것이다. 만약 3.1버전으로 컴파일한 것을 사용한다면, 간단하게 코드를 작성할 수 없을 것이다.

따라서 스위프트 3.1은 현재 플랫폼 버전 외에도 특정 스위프트 버전 숫자를 지원하는 @available 속성을 확장했다[SE-0141].
// Swift 3.1

@available(swift 3.1)
func intVersion(number: Double) -> Int? {
  return Int(exactly: number)
}

@available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
  return Int(number)
}
이 새로은 기능은 intVersion 메소드는 어떤 스위프트 버전하에 가능하다는 말과 같은 뜻이다. 그러나 표준 라이브러리같은 라이브러리들은 오직 한번만 컴파일되게 할 수 있다. 그러면 컴파일러는 간단하게 주어진 호환 버전을 선택하여 기능을 고른다.

Note : 스위프트에서 어베일러빌리티 속성(availibility attributes)에대해 너 배우고 싶으면, availability attributes in Swift라는 우리 튜토리얼을 확인해보자.

비-이스케이핑 클로저(Non-Escaping Closure)를 이스케이핑 클로저(Escaping Closures)로 변환하기
함수에 클로저 인자는 스위프트 3.0 디폴트에의해 non-escaping으로 만들어졌다[SE-0103]. 그러나 이 프로퍼절의 일부가 그때 구현되지 못했다. 스위프트 3.1에서는 withoutActuallyEscaping() 핼퍼 함수를 사용하여 임시로 비-이스케이핑 클로저를 이스케이핑 클로저로 변환할 수 있다.

왜 이런게 필요할까? 아마 자주 필요하진 않을것이지만, 제인에서 나온 아래 예제를 생각해보자.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void,
             on queue: DispatchQueue) {
  withoutActuallyEscaping(f) { escapableF in     // 1
    withoutActuallyEscaping(g) { escapableG in
      queue.async(execute: escapableF)           // 2
      queue.async(execute: escapableG)     

      queue.sync(flags: .barrier) {}             // 3
    }                                            // 4
  }
}
이 함수는 동시에 두 클로저를 실행하고 두 클로저가 완료되면 반환한다.
  1. fg는 비-이스케이핑으로 들어와서 escapableFescapableG로 변환된다.
  2. async(excute:)은 요구 이스케이핑 클로저를 호출한다. 다행히도 이전 단계 덕에 이것을 가지고 있다.
  3. sync(flags: .barrier)를 실행하여, async(execute:)메소드가 완전히 완료되고 클로저가 다음에는 다시는 호출되지 않을거라 보장한다.
  4. 스코프는 escapableFecapableG 사용을 제한한다.
만약 임시의 이스케이핑 클로저를 어딘가에 넣어주려 한다면(즉, 실제로 이스케이프된 것들) 이것은 버그가 될 수 있다. 표준 라이브러리의 나중의 버전에서는 여러분이 이것을 호출하려할때, 이것을 감지하고 잡아낼 수 있게 될것이다.

스위프트 패키지 매니저 업데이트
아, 긴 시간동안 기다린 스위프트 패키지 매니저 업데이트가 도착했다!

수정가능한 패키지들(Editable Packages)
스위프트 3.1은 스위프트 패키지 매니저에 수정가능한 패키지라는 개념을 넣었다[editable-packages">SE-0082].

swift package edit 명령은 존재하는 패키지를 받아서 수정가능한 패키지로 변환한다. 수정가능한 패키지는 의존성 그래프에서 모든 일반적인 패키지의 발생을 대체한다. 패키지 매니저를 일반적인 해결된 패키지(canonical resolved package)로 돌리려면 --end-edit 명령을 사용하자.

버전 피닝(Version Pinning)
스위프트 3.1은 스위프트 패키지 매니저에 특정 버전으로 버전 피닝 패키지 의존성 개념을 추가했다[package-pinning">SE-0145]. pin이라는 명령은 하나 혹은 모든 의존성을 핀한다.
$ swift package pin --all      // pins all the dependencies
$ swift package pin Foo        // pins Foo at current resolved version
$ swift package pin Foo --version 1.2.3  // pins Foo at 1.2.3
unpin 명령으로 이전 패키지 버전으로 되돌릴 수 있다.
$ swift package unpin —all
$ swift package unpin Foo
이 패키지 매니저는 Package.pins에 각 패키지의 활성된 버전 핀 정보를 저장한다. 이 파일이 없다면, 패키지 매니저는 오토메틱 피닝(automatic pinning) 프로세스의 부분으로서 자동으로 패키지 메니패스트에서 특정 요구에따라 생성한다.

다른 것들
swift package reset 명령은 체크아웃된 의존성이 없거나 빌드 인공의 프레젠트가 없는 깨끗한 상태로 패키지를 되돌린다.

게다가, swift test --parallel 명령은 병렬로 테스트를 실행시킨다.

잡다한 것들
스위프트 3.1에는 어느 카테고리에도 맞지않는 짧은 소식들도 있다.

다중-리턴 함수(Multiple-Return Functions)
vforksetjmp같은 두번 리턴하는 C 함수는 이제 동작하지 않는다. 이것들은 관련된 방법으로 프로그램의 컨트롤 플로우를 바꿔버린다. 그래서 스위프트 커뮤니티는 이것들 사용을 금지시키고 이제 컴파일타임 에러를 내뱉도록 하기로 결정했다.

자동-링킹 끄기(Disable Auto-Linking)
스위프트 패키지 매니저는 C 언어 타겟을 위한 모듈 맵(module maps)의 자동-링킹 기능을 끌 수 있다.
// Swift 3.0
module MyCLib {
    header “foo.h"
    link “MyCLib"
    export *
}

// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}

여기서 어디로 가야할까?
스위프트 3.1은 스위프트 3.0 기능을 갈고 닦는 역할을 하는데, 올해(2017년 가을쯤) 스위프트 4.0 번들을 더 심사숙고하게 변경하기위한 준비이다. 이런것에는 제네릭, 정규식, 더 인간환경적인 문자열 설계 등에대한 거대한 개선을 포함한다.

여러분이 뭔가 새로운 것을 느끼고 있다면 Swift standard library diffs을 한번 보거나, 스위프트 변화에대한 모든 정보를 얻을 수 있는 공식 Swift CHANGELOG를 보자. 혹은 스위프트 4.0이 나올때까지 지켜보고 있어도 된다.

그리고 스위프트 4.0과 그 넘어에 무엇이 바뀔지 궁금하다면, 바로 지금 프로포절되고있는 것을 볼 수 있는 Swift Evolution proposals를 보면된다. 만일 여러분이 매우 열망하고있다면 현재 리뷰중인 프로포절에 피드백을 날리거나 여러분 스스로 프로포절을 할 수도 있다 ;].

지금까지 스위프트 3.1에서 여러분이 좋아하는점과  싫어하는점이 무엇인가? 이 포럼 아래 토론에서 알려달라!

이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: What are MVP and MVC and what is the difference?- StackOverflow


Q. RAD(드레그-드롭과 구성)을 넘어 볼때, 사용자 인터페이스를 만드는 방법에서 많은 툴이 지향하는 방법은 Model-View-ControllerModel-View-Presenter, Model-View-ViewModel 이 세가지 디자인 패턴으로 이해되었다. 내 질문은 세가지이다.
  1. 이 패턴이 해결할 이슈들은 무엇인가?
  2. 이것들은 어떻게 비슷한가?
  3. 이것들은 어떻게 다른가?


A.
Model-View-Presenter
MVP에서는 프레젠터가 뷰를 위한 UI 비즈니스 로직을 담고 있다. 뷰에서 나온 모든 호출은 프레젠터로 직접 델리게이트한다. 프레젠터는 뷰와 바로 분리되있고 인터페이스를통해 이야기한다. 이것은 유닛테스트에서 뷰를 목(mock) 할 수 있게 해준다. MVP의 한가지 공통된 특징은 양방향 디스패치가 되야한다는 것이다. 예를들어 누군가 "저장" 버튼을 누를때 이벤트 핸들러는 프레젠터의 "OnSave" 메소드에 델리게이트한다. 저장이 완료되면 프레젠터는 인터페이스를 통해 뷰에게 콜백하여, 뷰는 저장이 완료되었다고 표시할 수 있다.

MVP는 웹 폼(Web Form)에서 분리된 표현을 달성하기에 매우 자연스러운 패턴이되는 경향이 있다. 그 이유는 뷰가 항상 ASP.NET 런타임에의해 가장 먼저 만들어지기 때문이다. 여기서 더 다양한 종류에대해 확인할 수 있다.

두가지 주요 종류
수동적인 뷰(Passive View): 이 뷰는 가능한 멍청하고 거의 로직을 가지고 있지 않는 뷰이다. 프레젠터는 뷰와 모델에게 말을 하는 중간자 역할을 한다. 뷰와 모델은 서로 완전히 막혀있다. 모델이 이벤트를 만들어내지만, 프레젠터는 뷰를 생신하기위해 그것을 구독(subscribe)한다. 수동적인 뷰에서는 직접적인 데이터 바인딩은 없고, 프레젠터가 데이터를 셋(set) 하는데 사용되는 뷰의 세터(setter) 프로퍼티로 노출시킨다. 모든 상태는 뷰가 아닌 프레젠터에서 관리된다.
  • 장점: 최대의 테스트성; 뷰와 모델의 분리가 명확하다.
  • 단점: 모든 데이터 바인딩을 여러분 스스로 해야하는, 더 많은 일거리(예를들어 모든 세터 프로퍼티들).

감독 컨트롤러(Supervising Controller): 프레젠터가 사용자 제스처를 다룬다. 뷰는 데이터 바인딩으로 모델을 직접 바인딩한다. 이 경우, 모델을 뷰에 보내주는게 프레젠터의 일이라서 바인딩할 수 있다. 이 프레젠터는 버튼 누르기, 화면 이동 등 제스쳐를 위한 로직을 담고 있을 것이다.
  • 장점: 데이터 바인딩을 이용하여 코드의 양을 줄인다.
  • 단점: 더 낮은 테스트성(데이터 바인딩 때문에), 모델에 직접 말하기 때문에 뷰는 더 낮은 캡슐화가 된다.

Model-View-Controller
MVC에서 컨트롤러는 앱 로딩같은 어떤 액션에 반응하여, 어떤 뷰가 표시될지 결정하는 책임을 가진다. 이것은 액션이 뷰를 통해 프레젠터로 라우트한다는 부분이 MVP와 다른 점이다. MVC에서는, 뷰에서의 모든 액션이 컨트롤러에 호출하여 상호 관련이 있다. 웹에서는 각 액션이 응답할 컨트롤러가있는 다른편에서 URL 호출을 포함한다. 컨트롤러가 그 처리를 완성하면, 올바른 뷰를 돌려줄 것이다. 이런 순서는 어플리케이션의 라이프 내내 그 방법으로 계속된다.


Action in the View
     -> Call to Controller
     -> Controller Logic
      -> Controller returns the View



MVC에대해 한가지 크게 다른점은 뷰가 모델을 직접 바인딩하지 않는다는 것이다. 뷰는 간단하게 랜더링만하고 완전한 상태없는(stateless)것이된다. MVC의 구현에서 뷰는 보통 코드 뒤에서 로직이 하나도 없다. 이것은 절대적으로 필요한 MVP와 상반되는데, 그 뷰가 프레젠터에게 델리게이트하지 않으면 절때 호출되지 않을 것이기 때문이다.

Presentation Model
우리가 볼 또다른 패턴은 프레젠테이션 모델 패턴이다. 이 패턴에는 프레젠터가 없다. 대신에 뷰가 직접 프레젠테이션 모델을 바인딩한다. 그 프레젠테이션 모델은 뷰를 위해 면밀하게 만들어진 모델이다. 이 의미는 이것이 seperation-of-concern의 위배일 수 있으므로, 모델은 절때 도메인 모델일 수 없는 프로퍼티들을 호출시킨다. 이 경우, 프레젠테이션 모델은 도메인 모델을 바인딩하고, 모델에서 나오는 이벤트를 구독할 것이다. 그럼 뷰는 프레젠테이션 모델에서 나오는 이벤트를 구독하고 적절히 스스로 갱신한다. 프레젠테이션 모델은 뷰가 액션을 호출하는데 사용하는 명령을 노출시켜 놓을 수 있다. 이런 방법의 이점은, 프레젠테이션 모델이 완전히 뷰를 위한 모든 동작을 캡슐화하기 때문에, 본질적으로 코드 뒤에서 함께있는 것을 제거할 수 있다. 이 패턴은 WPF 어플리케이션에서 사용하기에 강한 후보이고, 또한 Model-View-ViewModel이라 부르기도 한다.



A. 얼마전에 이것에대해 글을 썼는데, 이 두가지 차이점을 훌륭하게 포스팅한 Todd Snyder 글 을 인용한다.

여기에는 패턴간의 핵심적인 차이가 있다.
MVP 패턴
    • 뷰가 모델에 더 느슨하게 연결되있다. 프레젠터는 모델을 뷰에 바인딩할 책임을 가진다.
    • 뷰와의 인터렉션이 인터페이스를 통하기 때문에 유닛테스트하기 더 쉽다.
    • 보통 뷰:프레젠터는 1:1로 맵핑된다. 복잡한 뷰는 여러 프레젠터를 가질 것이다.
MVC 패턴
    • 컨트롤러는 행동 기반이고, 뷰를 통해 공유될 수 있다.
    • 표시를 위해 어떤 뷰를 선택할지 결정하는 책임일 수 있다.

내가 찾은 것중에 웹에서는 최고의 설명이다.


A. 이건 디자인 패턴의 여러 종류를 과하게 간단하게 만든 그림이긴 하나, 두가지 차이를 생각하기에는 좋아보인다.

MVC
MVC

MVP
enter image description here



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Service-oriented AppDelegate


보통 앱델리게이트(AppDelegate)는 거대한 클래스이다. 이것은 여러분의 앱에 대해 너무 많이 알고 있고 점점 더 커지게 된다. 이 글에서는 깔끔한 플러그인 기반 아키텍처를 만들어서 그 세부 기능으로부터 어떻게 분리해낼 수 있는지 보여줄 것이다.

우리는 올바르지 않은 방법으로 문제를 직면하고 있다. 당신의 앱델리게이트를 어떻게 관리하고 있는지 생각해보자. 내 생각엔 굉장히 거대하거나, 아니면 적어도 거기서 코딩한 것들에 대해 자신감이 있지는 않을 것 같아 보인다.

앱델리게이트의 문제는 바로 앱델리게이트가 여러분의 앱에대해 너무 많이 알고 있다는 점이다. 이것은 여러분의 의존성들을 알고있는데, 어떻게 초기화하는지, 어떻게 푸시 노티피케이션을 파싱하는지 등 많은 것을 알고 있다.

요약을 해보자면
ApplicationSercvices를 만들어낼 수 있다. 이것들은 AppDelegate 라이프사이클을 공유하고 해당 단계에 작업을 실행하는 오브젝트이다. 여러분의 앱델리게이트는 피관찰자(Observable)이고 여러분의 서비스들은 관찰자(Observer)이다. 이런 접근법은 그 서비스들로부터 앱델리게이트를 분리시켜주고 단일 책임 오브젝트를 생성해준다.

물론 많은 양의 코드를 작성하게 되므로 이 문제를 다루기위해 CocoaPod을 만들었다. PluggableAppDelegate라 부르며 여기에서 확인할 수 있다: http://github.com/fm091/PluggableApplicationDelegate

서비스를 생성해서 AppDelegate에 등록만 하면 된다. 그 결과는 여러분이 봐왔던 것 중에 가장 작고 깔끔한 AppDelegate가 될것이다.

일반적인 앱델리게이트
일반적으로, 앱델리게이트는 많은 역할을 가지고 있다.
  1. 여러분 앱의 모든 의존성을 초기화한다.
  2. 전역의 UIAppearance 값들을 초기설정한다.
  3. 푸시 노티피케이션을 다룬다.
  4. 푸시 노티피케이션을 등록한다.

그밖에 더 있을 것이다.

여기서 문제는 여러분의 앱델리게이트가 정확하게 모든 컴포넌트가 어떻게 동작하는지, 그리고 어떻게 인스턴스화하는지 알고 있기 때문에 생긴 것이다. 또한 이것은 여러분의 앱이 어떻게 생겼는지도 알 수도 있다. 그리고 푸시 노티피케이션 포멧도 알고있다. 결과적으로 생각했던것보다 너무 커져버린다.

다른 접근법
ApplicationService라 부르는 그 고유의 컴포넌트에 여러분의 의존성들을 캡슐화하여 들고 있다고 생각해보자.

모든 ApplicationService는 하위-앱델리게이트(sub-AppDelegate)이다. 이것은 앱델리게이트의 라이프사이클을 공유하고, 요청된 이벤트에서 행동을 취한다.

여러분 AppDelegate는 이벤트의 발생를 관찰하는 관찰자(observable)이 되어, 어떤 일이 일어나면 서비스에게 알려주기만 하면 된다.



어플리케이션 서비스들
어플리케이션 서비스들은 AppDelegate 라이프사이클을 공유하는 오브젝트들이다. ApplicationService는 그냥 태그한 프로토콜이다. 만약 PluggableApplicationDelegate를 사용한다면 이렇게 생겼을 것이다.
public protocol ApplicationService: UIApplicationDelegate {}
위에서 본 것처럼, UIApplicationDelegate만이지만, 더 전달력있는 이름이다.

이것을 구현할때, AppDelegate를 구현하듯이 하면 된다. 그러나 여기서 다른점은 단일 책임 요소를 코딩한다는 점이다.

아래에 구현에대한 예시가 있다.
import Foundation
import PluggableApplicationDelegate

final class LoggerApplicationService: NSObject, ApplicationService {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        print("It has started!") return true
    }
    func applicationDidEnterBackground(_ application: UIApplication) {
        print("It has entered background”)
    }
}

새로운 앱델리게이트
PluggableApplicationDelegate를 상속받고 이것을 서비스에 등록함으로서, 이제 여러분의 앱델리게이트는 더 작아졌다.

아래를 확인해보자
import UIKit 
import PluggableApplicationDelegate 

@UIApplicationMain 
class AppDelegate: PluggableApplicationDelegate { 

    override var services: [ApplicationService] { 
        return [ LoggerApplicationService() ] 
    } 
}

PluggableApplicationDelegate는 너무 크다. 뭔가 일어났을때만 그 서비스에 알려준다. 그것에대한 코드를 여기서 확인할 수 있을 것이다.

의존성과 기능을 점점 더 앱에 추가하더래도 앱델리게이트는 아래처럼 깨끗하게 유지될 것이다.
import UIKit 
import PluggableApplicationDelegate 

@UIApplicationMain 
class AppDelegate: PluggableApplicationDelegate { 

    override var services: [ApplicationService] { 
        return [ 
            PushNotificationsService(), 
            AnalyticsService(), 
            CustomAppearanceService(),
            FirebaseService(), 
            FacebookSDKService() 
        ] 
    } 
}

결론
이게 다다. 여러분의 앱델리게이트를 분리시키는 간단한 방법이며, 그것이 커다란 덩어리로 되지 않게 막아준다.

내 깃헙 저장소를 꼭 확인해보기 바란다:

내가 도움이 됐던 만큼 여러분에게도 도움이 되길 바라며, 이것에대한 피드백을 꼭 받고싶다. 아래 댓글로 남겨달라

고맙다!


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목 : Building iOS App with VIPER Architecture



이 글은 완전히 iOS VIPER 아키텍처에 관한 내용이다. 우리는 3가지 포인트를 통해 이야기해 나갈 예정이다.
  • VIPER 아키텍처란?
  • VIPER 아키텍처로 iOS 앱 만들기
  • VIPER 아키텍처의 이점들
이 표준 아키텍처는 재사용성과 테스트용이함에대해 코드를 분리시켜주는 중요한 역할을 한다. 이 아키텍처는 그 역할에 맞춰 앱 컴포넌트를 분리시키며, 이것은 seperation of concern이라 부른다.

이제 iOS를 위한 VIPER 아키텍처에대해 탐험해보자.

VIPER 아키텍처란?
VIPERView, Interactor, Presenter, Entity, Router로 구성되있다.

이 아키텍처는 단일책임원칙(링크)를 기반으로 하는데, 이것이 명확한 아키텍처로 만들어준다.
  • View : View의 책임은 사용자의 동작을 Presenter로 보내주고 Presenter가 요청하는 모든 것을 보여준다.
  • Intereactor : 이것은 비지니스 로직을 가지고있는 앱의 뼈대이다.
  • Presenter : 이것의 책임은 사용자 동작에 Interactor에서 데이터를 뽑아온 뒤, 그것을 보여주기 위해 View에 보낸다. 또한 네비게이션(화면이동)을 위해 router(혹은 wireframe)에게 물어보기도한다.
  • Entity : Interactor에서 사용하는 기본 오브젝트 모델이다.
  • Router : 이것은 어느 화면이 언제 나타날지에대한 정보를 담고 있는 네비게이션 로직이다. 보통 wireframe으로 쓰인다.

VIPER 아키텍처의 청사진


보통 VIPER 아키텍처는 큰 프로젝트에서 쓰인다. 그러나 이해를 돕기위해 이것을 위한 작은 앱을 하나 만들었다.

VIPER 아키텍처로 iOS 앱 만들기
나는 VIPER 아키텍처를 사용한 샘플 iOS 앱을 하나 만들었다.


이해를 돕기위해 프로젝트의 구조를 보자


샘플 앱의 스크린샷이다.



이 앱은 3가지 화면으로 구성된다.
  • 시작화면 : 일반적인 시작화면이다 따라서 더 설명할건 없다.
  • PostListView :화면  이 PostListView는 포스팅 목록을 가져와라고 Presenter에게 말한다. 그런 다음 Presenter는 관련 데이터를 위해 Interactor에게 접근한다. Intereactor는 로컬 데이터베이스에서 그 데이터의 사용 가능 여부를 확인해보고, 만약 데이터가 있으면 Presenter로 반환하고, Presenter는 View로 반환한다. 데이터가 로컬 데이터베이스에 없을경우 네트워크를 호출하여 데이터를 가져온 다음 Presenter에게 반환한다. 그리고 역시 이 데이터를 로컬 데이터베이스(CoreData)에 저장한다.
  • PostDetailView 화면 : 사용자가 PostViewList에 표시된 포스트를 클릭하면, PostListPresenter는 PostDetailView를 열어도 되는지 Router(PostListWireFrame)에게 물어본다. 선택된 포스팅의 세부사항이 이 화면에 나타난다.

그리고 이 프로젝트에서 대부분 클래스간의 의사소통은 정의된 프로토콜을 통해 일어난다.

이것을 완전히 이해하는데 가장 좋은 방법은 소스코드를 확인하고 구현해보는 방법일 것이다. 프로젝트를 깃으로 클론해서 빌드&런 해보자.

VIPER 아키텍처의 이점들
  • 재사용성과 테스트용이함을 위해 코드를 분리할 수 있다.
  • 그 역할에 맞춰 앱 컴포넌트를 분리할 수 있으며, 이것을 seperation of concern이라 부른다.
  • 새 기능을 추가하기 쉽다.
  • UI 로직이 비지니스 로직으로부터 떨어져있기 때문에 자연스럽게 테스트를 만들기 쉬워진다.

이게 다다. 즐거운 코딩하길 바란다 :)


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Unconventional way of learning a new programming language


세상에는 500개 이상의 프로그래밍 언어가 있다. 그러므로 오늘날 새 언어를 배우기 시작하는것은 종종 있는 일이다. 당신이 C++자바를 할 수 있지만, 작업에서 파이썬을 필요로 한다던지, 파이썬을 할 수 있는데 자바로 코드를 짜야한다던지 그럴 수 있다. 혹은 여러분의 전문성을 넓히기 위해 멋진 언어를 배우려 할 수도 있다.

새로운 프로그래밍 언어를 배우기위해 어떤 선택지가 있을 수 있을까?
  • 온라인 튜토리얼으로 배운다.
  • 온라인 강의(MOOC)로 배운다.

여러분 중 몇몇은 실제로 아래처럼 하는게 제일 좋은 방법이라고 주장할 수도 있다.
  • 새로운 프로그래밍 언어의 문법을 배운다.
  • 그 언어를 이용해서 개인적인 프로젝트를 한다.

이제 됐다! 이것은 여러분이 배우고 싶은 언어의 문법을 배운 것으로부터 얻은 지식을 적용할 수 있게 해준다.

나는 서로 다른 언어를 배우는 동안 20개 이상의 작은 프로젝트를 개발해왔다. 나를 믿어보고, 여러분이 주말이나 밤사이에 개인적인 프로젝트를 할때, 동작하는 코드를 짠다. 여러분이 관심 있는 부분은 "내 코드가 동작하는가?"이다. 반면 여러분 코드의 퀄리티는 거의 신경쓰지 않는다.

"바보같은 사람들은 컴퓨터가 이해할 수 있느 코드를 짜지만, 좋은 프로그래머들은 사람이 이해할 수 있는 코드를 짠다" -(Martin Fowler)


그래서 새로운 프로그래밍 언어를 좋은 방법으로 배울 수 있을까?

그 언어로 된 오픈소스 프로젝트에 기여하기
놀랐는가? 여러분 중에 몇몇은 이렇게 생각할것이다. "잠시만. 오픈소스는 어려운데, 그 언어에대해 박식하게 알고 있어야 오픈소스 프로젝트에 기여할 수 있는것 아닌가?" 그 대답은 틀렸다.

내 이야기를 들려주겟다.

작년에 나는 Booking.com에서 풀타임 직장을 제안받았는데, 나는 펄 언어(백엔드에 특화된 언어)로 작업한다는 것을 알고있었다. 2016년 6월에 대학을 마칠때, 대학이후 첫 직장을 가지기위해 나는 펄 언어를 배우기 시작했다. 7월 둘째주에 들어가면서부터 거친 한달을 겪었다.

나는 펄에대한 문법을 읽는 것과 이 언어의 일반적인 패턴을 이해하는 것부터 시작했다. 이제 나는 파이썬을 이용해서 뭔가 만들어보고 싶었으므로 그 언어에대한 내 지식을 적용시켜보고 그 언어에대한 다양한 개념을 연습할 수 있었다. 내가 펄 언어로 뭔가 만들 수 있는 방법을 찾아보다가, 깃헙에 있는 DuckDuckGo 오픈소스 프로젝트 오가니제이션을 찾았다. 그 오픈소스의 일부는 펄로 작성되었다는 것을 알게되었고, 이슈를 확인해보니 수많은 이슈들이 "초보"적인 것임을 발견했다. 즉시 작업을 해서 몇 풀리퀘스트를 제출했다. 오늘까지 빠르게 앞으로 나갔고, 현재 나는 몇몇 오픈소스 프로젝트에서 메인 컨트리뷰터이며, DuckDuckGo에서 20명의 오픈소스 커뮤니티 리더중 한명이 되었다.

이 이야기의 교훈 : 펄 언어로 쓰여진 오픈소스 프로젝트에 기여하여 펄 언어를 배우게 되었다.

그래서 왜 이렇게 작업했을까?
펄 언어의 문법을 배우자마자, 나는 오픈소스 프로젝트에 기여하기 시작했다. 이렇게하는동안, 나는 현재 존재하는 모듈을 보는데 익숙하다. 펄 언어를 사용한 것의 패턴을 알아차리는데 익숙하다. 따라서 이 좋은 방법들을 내 코드에 집어넣을 수 있었고, 펄 언어로 어떻게 좋은 코드를 작성하는지 배우는데에도 큰 도움이 되었다.

이것이 단순한 우연이 아니다; 나는 이 비슷한 또다른 이야기를 들려주려한다.

최근에 Booking.com에서 일하는동안 Go 언어로 쓰여진 서비스가 들어간 작업(새 기능 추가)을 잡았다. 아래에는 내 팀원들과 내가 대화한 내용이다.

나 : 나는 이 작업이 굉장히 좋습니다. 이 작업을 하고싶은데 그쪽 생각은 어떤가요?
그 : 좋아요, 아주 흥미로운 것이에요. 그런데 Go 언어에대해 알고있어야하는데, Go 언어를 아시나요?
나 : 아니오.
그 : Go 언어를 배우고 싶은가요?
나 : 그래요!
그 : :) 시작하죠!

그래서 다른 언어를 배우는 지점에서는 바로 Go 언어였다!

나는 Go 문법을 읽기 시작했고 공식 사이트에 초급자를 위한 멋진 투어를 발견했다. 나는 이 투어를 통해 언어의 기본 개념을 익혔다.

다시 한번 나는 Go 언어로 된, 그리고 "beginner"나 "easy-fix" 이슈를 가진 오픈소스 프로젝트를 찾기 시작했다. 나는 Google이 만든 깃헙의 REST API를 Go로 감싸는 기본적인 프로젝트를 발견했다.

내가 Go언어를 배운지 이틀만에 첫 PR을 가졌다.



오픈소스가 어떻게 여러분을 도와줄까?
이제 오픈소스 기여가 어떻게 그 언어의 좋은 습관을 배울 수 있게 해주는지 궁금할 수 있다. 여기에는 여러 면이 있으니 하나씩 이야기해보자.

코드 품질
대부분의 좋은 오픈소스 프로젝트는 엄격한 코딩 가이드라인을 가진다. 여러분의 코드를 머지하려면 그 규약을 따라야한다. 이 가이드라인을 적용시키면서, 언어를 배우고 있는데도 좋은 품질의 코드를 작성하는데 도움이 된다.

단순히 그뿐만 아니라 다른 사람의 코드를 볼 수 있는 기회를 가지며, 어떻게 좋게 문서화하는지 직접 눈으로 볼 수 있다.

코드 리뷰
오픈소스 기여에대해 가장 좋은 부분은 코드 리뷰이다. 여러분이 코드를 푸시할때 그 프로젝트에 관련된 전문가로부터 피드백을 받기때문에 그 언어에대한 이해를 높힐 수 있는 기회가 된다.

이것은 좋은 코드를 짜는것에대한 무료의 개인 과외이다.

평가


우리는 소프트웨어 개발자로서 우리 작업에대해 평가를 받을 필요가 있다. 그리고 오픈소스 커뮤니티는 잘 했는지 확인할 수 있게 해준다. 오픈소스 기여에대한 내 모든 경험에의하면, 나는 한번도 주입식이나 의욕을 잃게하는 커멘트를 받아 본적이 없다. 모두가 완전 격려해주고 도움이 된다.



그러니 다음에 여러분이 새로 배우고 싶은 언어가 생기면, 어서 시작하여 뛰어들자! 기여할 오픈소스 프로젝트를 찾아서 언어를 배우고 그 늬앙스를 배워가자 ;)

그리고 이 정해지지 않은 방법이 여러분에게 어떻게 되었는지 알려달라. 또한, 이 글이 조금 유용했다고 생각하면, 포스팅에 추천( )을 해달라.

자유롭게 여러 다른 방법으로도 공유할 수 있다.
나를 트윗/팔로우하려면 @sahildua2305로 가면 된다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.


WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Looking at Model-View-Controller in Cocoa

This article is copyright 2017 Matt Gallagher, https://cocoawithlove.com. The original English version is available here: Looking at Model-View-Controller in Cocoa. This translation is produced and hosted with permission of the original author.

애플의 문서에의하면, 코코아 어플리케이션에서 표준 패턴을 Model-View-Controller라 부른다. 그 이름에도 불구하고 이 패턴은 기존의 smalltalk-80의 Movel-View-Controller 정의와 완전히 다르다. 코코아의 앱 디자인 패턴은 실제로 원래의 Smalltalk의 용어보다 Taligent(1990년대부터 애플과 공동으로 개발해온 프로젝트)에서 만들어진 방법에 더 일반적으로 겹친다.

이 글에서는 코코아에서 주로 사용하는 앱 디자인 패턴의 뒷 배경과 약간의 이론을 보려한다. 나는 코코아의 Model-View-Controller 접근법의 주요 결점에대해 이야기해볼 것이다. 이런 결점을 해결하는데 실패한 애플의 노력과, 다음 메이저 개션으로부터 생기는 의문을 보게 될 것이다.

컨텐츠
  1. Smalltalk-80
  2. 코코아(AppKit/UIKit)
  3. Taligent
  4. 컨트롤러 문제
  5. 바인딩
  6. 새로운 무언가?
  7. 결론

Smalltalk-80
아마 UI 개발에서 널리 인용된 패턴은 Model-View-Controller(MVC)이다. 또한 많이들 잘못 인용되고 있다. 나는 MVC가 이것처럼 아무것도 아니게 되버렸다고 설명한 것을 보는데 시간가는줄 몰랐다 - Martin Fowler, GUI Architectures

나는 Martin Fowler가 사용한 정의로 위의 인용에서 Fowler가 말하고자하는바를 빠르게 알려주려한다. 코코아 앱 개발에 보통 사용되는 이 접근법은 Model-View-Controller가 아니다.

Smalltalk-80에서는 상호 소통하는 뷰가 완전히 분리된 두 오브젝트로 쪼개진다. 바로 뷰 오브젝트와 컨트롤러 오브젝트이다. 뷰 오브젝트는 화면 출력을 실행하나, 모든 클릭이나 인터렉션은 뷰 오브젝트에서 하지 않고, 대신 그 파트너인 컨트롤러 오브젝트에의해 디스패치된다. 중요하게 이해하고 넘어가야할것은 컨트롤러는 뷰를 불러오거나 셋업을 관리하지 않으며, 한 컨트롤러는 여러 뷰를 위한 동작을 다루지 않는다; 원래 Model-View-Controller 정의에서는 뷰와 컨트롤러는 간단하게 동작하고, 화면상의 하나의 조작면에서만 출력한다.

Smalltalk's version of Model-View-Controller
Smalltalk-80의 Model-View-Controller

Smalltalk-80의 Model-View-Controller의 다이어그램은 오브젝트 그래프의 중앙에 모델이 있고, 모델이 뷰나 컨트롤러와 직접 우선적으로 소통한다는 것을 보여준다.

이 명확한 패턴은 Smalltalk-80이 어떻게 사용자 입력을 처리했는지 반영하며, 현대의 프로그램에는 이 명확한 패턴을 사용하는데 조금만 필요로 한다. 이런 의미에서, 어떠한 현대의 프레임워크도 Model-View-Controller가 아니거나, 혹은 용어의 정의가 다른 의미로 바뀌어가고 있는 것이다.

코코아(AppKit/UIKit)
코코아가 Model-View-Controller를 논할때, 대부분 어플리케이션 설계에서 분리된 표시와 컨텐트의 개념을 일깨우려고 노력하고 있다(이 방법은 모델과 뷰가 분리되게 설계하고, 구성에서 느슨하게 연결되있는 것이다). 사실 코코아만 Model-View-Controller를 이런식으로 사용하는게 아니다. 현대의 많은 이 용어의 사용이 원래의 Smalltalk-80 정의보다는 분리된 표시를 전달하기 위함이다.

코코아가 실제로 쓰고있는 정확한 패턴을 보면서 애플의 코코아 참조 가이드가 사용하는 Model-View-Controller가 어떻게 생겼는지 보자.

Cocoa's version of Model-View-Controller
코코아의 Model-View-Controller

주의해야할 중요한 점은 컨트롤러가 오브젝트 그래프의 중앙에서 대부분 소통을 컨트롤러를통해 한다는 것이다. 모델이 그래프의 중앙에 왓던 Smalltalk-80와는 다르다.

코코아는 앱에서 이 패턴을 강요하진 않지만, 모든 어플리케이션 템플릿으로 강력히 내포하고 있다. NIB 파일로부터 불러오는 것은 NSWindowController/UIViewController 사용을 강력하게 지향한다. NSTableView/UITableView나 그 관련 클래스의 델리게이트 필요 조건은 전체 표시의 책임을 이해하는 조정자 클래스를 강하게 의미한다. UITabBarController와 UINavigationController 같은 클래스들은 뷰를 조정하기위해 명시적으로 UIViewController인스턴스를 필요로 한다.

Taligent
학술적 토론에서, 코코아가 Model-View-Controller로 부르는 그 패턴은 모통 Model-View-Presenter라 불린다. 이 두가지는 코코아가 컨트롤러라 부르는 것을 프레젠터라 부르는 것만 빼면 동일하다. "프레젠터"라는 이름은 화면을 셋업하고 동작을 중재하는 역할을 맡는다. 몇몇 케이스에서는 프레젠터 오브젝트를 "감독 컨트롤러(Supervising Controller)"라 부르기도 한다. (왜 Model-View-Supervising Controller가 Model-View-Controller로 다시 돌아오게 되었는지 이해할 수 있을 것이다)

Model-View-Presenter라는 용어는 Taligent에서 기원된다. 일반적으로 많이 인용된 논문은 1997년에나온 “MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java”인데, 이 모델을 구현하기위한 Taligent의 클래스들은 적어도 1995년만큼 이르게 문서화 되었다.

Taligent는 원래 코드명 "Pink"(그 방법에 사용된 색깔 색인 카드 이후)라는 프로젝트로 System 7(이것은 "Blue" 색인 카드에 해당함)를 대체하는 OS를 지원하기위해 애플 안에서 시작된 회사였다. 애플이 동등하게 운이다한 Copland 프로젝트에 집중하고 시선을 돌리는 동안, 이 프로젝트는 일련의 인기있는 개발과 관리 문제를 가졌었다. Taligent는 1998년에 막을 내리기 전까지, OS 대신에 일련의 어플리케이션 프레임워크로 CommonPoint라는 이름으로 IBM이 배포해왔다.

This Wired article from 1993 gives an interesting insight into Taligent and the apparent bloat and infighting that doomed it.

NeXTStep이 Taligent보다 앞서 나왔지만, AppKit(지금은 AppKit의 Model-View-Controller 디자인 패턴의 양상을 정의하고 있지만)에는 컨트롤러 클래스가 1996년에 NeXTStep 4 전까지 없었다(NeXTStep의 메이저 재설계와 NS가 접두에 붙는 첫번째 NeXTStep 버전이 오늘날까지도 macOS에 남아있다). NeXTStep이 Taligent의 것을 직접 빌렸는지는 모르겠다(이것이 한 점으로 수렴해버린 진화일 수도 있고, 여러 회사가 같은 인재 풀에서 고용을 했기 때문일 수도 있다).

The Taligent documentation, from 1995, is fascinating to read. The Guide to Designing Programs discusses many ideas relevant to application design, 22 years later. However, the Programming with the Presentation Framework tutorial is horrifically bad: baffling, over-technical and unapproachable.

컨트롤러 문제
코코아의 Mode-View-Controller를 Model-View-Presenter 패턴으로 이해하는것이 매우 중요한데, 이 패턴에서는 "컨트롤러 문제"라는 커다란 문제를 유발한다.

"메시브(Massive)/거대한(Huge)/자이언트(Giant) 컨트롤러"라고도 불리는 컨트롤러 문제는 코코아에 있는 컨트롤러가 여러 분리된 역할(특히 뷰가 들어있어 생기는데 기능적으로나 데이터 의존성에도 이럴 필요없다)을 가지면서 끔찍하게 커져버리는 경향의 문제이다. 많은 보통 프로젝트들은 2000줄 혹은 그 이상의 컨트롤러 하나를 가진다.

명확하게 해달라. 이 문제가 단순히 그 크기에만 있는것이 아니라, 컨트롤러가 다루는것이 커지게 되는 점이다. 코코아에서 컨트롤러는 관련된것 혹은 관련되지 않은 역할들의 한 집합체이다. 하나의 뷰 컨트롤러는 대여섯개 혹은 그 이상의 뷰의 역할이 있을테고, 각각은 구조, 구성, 데이터표시, 데이터갱신, 레이아웃, 애니메이션과 동작들을 가질것이며, 끝내 부모 뷰 컨트롤러가 된 상태 보존 역할이 있을 것이다.

상당한 규모로 이 독립적인 역할들과 상호으존적인 역할들의 집합체는 악몽의 유지보수이다. 많은 양의 코드 덩어리들은 실제 의존성을 만들고, 찾기 힘든 상호 의존적인 기능을 만든다. 컨트롤러는 항상 테스트하기 힘들지만 (커다란 앱과 고립시키기는 어렵게 만드는 묶인(bundle) 상태의 의존성 때문에) 규모와 반의존성(semi-depenency)의 문제는 모든것을 더 나쁘게 만든다.

컨트롤러 문제를 해결하는 유일한 방법은 커다란 뷰컨트롤러를 계속적으로 더 작고 간편한 컨트롤러로 리팩토링하는 것이다. 이것에는 뷰컨트롤러에서 빼내와, 여러 뷰컨트롤러를 가지도록하는 분별력있는 접근법을 설계하여 의존성을 빼내고 제거하기위해 데이터 구조를 다시 설계하고 다시 생각해야한다. 완료할 수 있을지라도 수많은 일이 있을 것이고, 각 변경마다 버그가 나오는 일반적인 위험을 가지며, 여전히 테스트하기 힘들고(모든 뷰컨트롤러가 연관되있기 때문에), 이 모든 것에도 불구하고 끝단 사용자에게는 기능을 추가해 주지도 못한다.

바인딩
애플은 그들의 Mac OS X 10.3의 코코아 바인딩을 소개함으로서 얼마동안은 컨트롤러 문제에대해 알고 있었다.

바인딩은 두 컴포넌트 사이에 경로가 만들어진 런타임이다. 이 컴포넌트들은 보통 데이터의 소스이거나 데이터의 옵저버이다. 바인딩은 명시적인 코드 경로가 필요없이 이 컴포넌트들이 변경사항을 소통할 수 있게 해준다. 대신에, 두 컴포넌트 사이에 경로는 데이터안에 정의되있다(코코아 바인딩의경우 "key-path"라 부른다). 컨트롤러를통해 뷰 상태를 조절하는 모델 프로퍼티까지 key-path를 명시함으로서, 바인딩은 컨트롤러를 통하는 코드 경로를 과감히 줄여주어서(제거하기도한다) 컨트롤러 문제를 개선시킬 수 있다.

Cocoa's Model-View-Controller with Bindings
컨트롤러를 통한 코드경로는 바인딩에의해 대체된다

그 소개 이후, 수십가지의 코코아 바인딩이 모두 잊혀진채 남아있다. AppKit에서 아직 바인딩을 사용할 수는 있지만(절때 디프리케이트 시키진 않는다) 절때 UIKit에 넣진 않았고, 뷰를 더 쉽게 프로그래밍하고자하는 그 목적은 완전히 이룰 수 없는 결과를 초래한다. 내 생각에는 직면해야할 문제를 마주하고 몇몇의 경우는 매우 잘 동작할 수 있지만(특히 NSTableView에 있는 NSArrayController로 결합되어있을때) 왜 Mac 프로그래밍을 대체하지 않았는지 이해할 수 있다.

(여러분의 뷰컨트롤러에서 더 적은 코드의) 코코아 바인딩의 핵심 이점은 인터페이스 빌더 인스팩터 패널에서 수많은 설정(configuration)을 할 수 있다는 것이다. 이것은 여러분 코드에서 기능을 찾아볼 때 혼란스러울 수 있고, (Xcode의 프로젝트 범위 검색이 XIB 파일을 검색할 수 있게 되었지만) 그래도 검색하기 어렵고, 디버깅에 힘들며(모델 데이터가 여러분의 코드를 따라가는 스택 추적 없이 변한다), 인스팩터 패널로 검색하고 싶지 않은 새로운 누군가에게 가르치는 것은 매우 힘들며, 시작할때 코드보다 더 보기 힘들어지고(XIB 파일에 주석을 달거나 재구성할 수 없다), 로컬라이제이션이 섞인(localization maxup)것과같은 인터페이스 빌더 이슈나 버전 컨트롤 머지 이슈의 늪에 빠져버릴 수 있다.

내 개인적인 견해로는 코코아 바인딩의 중대한 실패는 커스텀 변형과 커스텀 프로퍼티를 추가하는데 있어 어려움으로 남아있다. 이것들은 모두 완료될 수 있지만 변형자를 등록하고 바인딩 딕셔너리를 노출시키는 일은 지루한 일이다. 항상 바인딩 없이 뷰컨트롤러를 통해 데이터를 보내는것은 쉬워보인다. 이 의미는 바인딩이 가장 간단한 문제를 도울 수 있는 경향은 있지만 (크게 도움이 되진 않는다) 힘든 문제에는 큰 효과를 얻지 못한다.

새로운 무언가?
Mac OS X 10.3의 코코아 바인딩 이래로, 애플은 코코아 앱에 사용될 대안의 디자인 패턴을 찾는데 어떠한 명확한 시도도 해보지 않고 있다.

iOS5와 Mac OS X 10.10에서 스토리보드를 내놓았지만, 스토리보드는 현재 디자인 패턴을 용이하게 만드는 시도만큼 디자인 패턴을 바꾸기위한 시도가 아니다. 스토리보드는 NS/UIViewController 사용을 지향하면서 Model-View-Presenter 디자인 패턴을 보강한다. 스토리보드는 더 작고 더 초점이 가는 뷰 컨트롤러를 지향하고, 많은 셋업과 트랜지션의 "표현(Presentation)"을 아주 약간 줄여주는 역할이다. 그러나 인터페이스 빌더에서 구성될 수 있으므로 코코아 바인딩에 영향을 준 여러 이슈들을 보여준다.

어플리케이션 디자인 패턴에서 더 야심찬 무언가를 기대한 우리로써는, 스토리보드가 그 새로운것을 많이 제공하진 않았다.

어플리케이션 디자인에서 새로운 방법이 존재한다. 애플 바깥에는, 리엑티 프로그래밍(이것을 선택하면 많은 바인딩의 역할로 채울 수 있다), Model-View-ViewModel(변형된 섹션의 모델을 뷰에 가깝게하여 연결함으로서 컨트롤러의 작업을 줄인다), 상호 방향적 데이터흐름(unidirectional dataflow)(이것은 모든 데이터 변경을 앱 전체에 방송하고 리듀서(reducer)를 통해 데이터 변경을 함으로서 바인딩의 필요를 줄인다); 이 모든것들을 다른 사이클로 인기가 있다(원문: all of which are popular within different circles).

리엑트 네이티브(React Native)나 Swift-Elm같이 근본적으로 다른 프레임워크도 있다. 비록 스위프트나 코코아가 희생되는것이 전적으로 중요한 결점을 동반할지라도 말이다.

이런 어떤 것들이 공식적인 코코아 앱 개발에 어떤 영향을 줄지는 불명확하다. 애플은 이따금 과감하게 바꾸고 싶어 한다는 것을 스위프트가 증명했고, 스위프트는 언어면의 이점을 수용한 디자인 패턴이나 뷰 프레임워크 갈망이 점점 더 커진다는 견해가 있다. 그러나 애플은 스위프트만으로된 메이저 프레임워크를 소개하려하기 전까지는 시간이 좀 걸릴것 같다.

결론
코코아의 현재 Model-View-Controller 패턴의 원래 데이터로서 NeXTStep 4를 받아드린다면 올해(2017년)로 20년째이다. 망가지진 않았지만 결점을 가지고 있고, 한번 그렇게 했었던만큼 흥분되거나 능률적인것으로 보이진 않는다.

애플은 디자인패턴 개선을 위해 비교적 이르게 코코아 바인딩을 내놓았었다. 이런 수용은 섞였고 애플의 새 플랫폼에까지 도달하지 않아왔다(원문: Its reception was mixed and it has not been carried forward onto Apple’s newer platforms.).

AppKit 혹은 UIKit 팀에게서 내부적인 노력에대한 다른 정보는 없지만, 애플이 가까운 미래에 과감한 변화를 할 것 같지는 않아보인다. 코코아에서 어플리케이션 디자인 패턴 전반을 개선하는 목적의 써드파티 프레임워크를 쓸 수 있는 여러 디자인 패턴들이 있지만, 반드시 이중 하나가 앞으로의 방향이라는 합의는 없어보인다. 나는 이런 노력들이 어떤 종류의 개선에 관심을 반영한다고 생각한다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: Thoughts on Swift access control
그리고 스위프트 에볼루션의 기회비용


스위프트에서의 접근제어에관한 엄청난 양의 swift-evolution mailing lists에 있다. 몇일전에 (Private 접근 수준을 고치자는) SE-0159제안이 거절되었다. 이것에 대한 내 생각을 공유하고 싶고, 전반적으로 접근 제어의 커다란 이야기에대한 내 생각도 들려주고 싶다. 그러나 먼저, 스위프트의 접근 제어 이력을 간단하게 알려주겠다.

⚠️주의: 나의 의견이 들어있을 것이다.



접근제어의 간단한 이력
스위프트의 이른 시기(1.0이전)에는 접근제어가 없었다. 이때가 스위프트의 황금기였다. 모든것이 퍼블릭이고 글로벌하게 어디에서나 접근할 수 있었다. 누구도 적절한 캡슐화에대해 생각하지 않았다. 한달동안 토론한 이메일도 없었다(스위프트-에볼루션이 아직 존재하지 않았었다). 접근제어가 없는것은 가장 간단하게 접근하여 제어하게 했고, 에볼루션이 없는것이 최고의 에볼루션이었다.

접근 제어가 나오다
Xcode6 베타4에서 스위프트는 접근제어를 추가하여 지원했다. 이해하기 쉬웠고, 사용하기도 쉬웠으며, 꽤 우아했었다. 즉시 이후에, 접근제어 모델과 함께 스위프트 1.0이 배포되었다. 스위프트 1.0은 Xcode 6과함께 나왔다. 상속과함께 합쳐진 "protected" 접근수준은 없었다. "friend" 접근수준도 없었는데, 이것은 그냥 지겨운 것이기 때문이다. 오직 세개의 접근수준만 있었다.
  • public은 모듈(프레임워크나 라이브러리)을 임포트한 모든 파일로부터 접근할 수 있다
  • internal은 (디폴트로서) 현재 모듈 안에서만 접근할 수 있다(앱이나 프레임워크 타겟도 "현재 디렉토리"라 생각한다 à la Swift Package Manager)
  • private은 이것이 정의된 소스파일 안에서만 접근할 수 있다

라이브러리 저자, 앱 개발자로서 이런 접근수준은 내가 원하는 모든 것을 표현하기위해 나에게 필요한 모든 기능을 제공한다. 명백한 유스케이스를 떠나서, "기존의" "private" 접근의 노테이션은 그 파일안에 타입이나 다른 엔티티를 정의하기만하면 달성할 수 있다. 예를들어 class A를 정의하고 그 모든 (private) 프로퍼티를 다른 클래스로부터 접근하지 못하게 하고 싶으면, 그 파일에 class A를 정의하고 다른것을 할 필요가 없었다. 이것이 바로 내가 좋아했던 스위프트 접근제어이다 - 디스크의 파일과 디렉토리의 "물리적인" 구조를 기본적으로 반영한 접근수준을 제공하여 최고의 실현을 만들어준다(파일이 부푸는것과 모듈이 부푸는것을 줄여준다). 적절한 캡슐화를 설계하려면, 명확하게 정의된 모듈(디렉토리)에 파일을 이동해야하고, 한 파일에 관련된 타입들을 정의하고, 한 파일안에 여러 타입 정의를 완전히 피하면된다. 코드 구성과 접근제어는 좋게 엮여있고 개발자에게 코드를 잘 구성할 수 있게 해준다. (엮이는것 일반적으로 소프트웨어 설계에서 나쁘나, 이 경우 필요한 엮임이다.)

fileprivate가 나오다
다음단계는 스위프트가 오픈소스화되고 스위프트 에볼루션 프로세스가 나왔다. 영역을 지정한 접근수준의 이 SE-0025 제안은 검토되고, 수정되서 마침내 스위프트3에서 수용되었다. 이 제안은 현재 선언된 스코프안에 엔티티에게 엄격한 접근을 하기위해 private의 의미가 바뀌었고, 이전 기능을 보존하기위해 fileprivate라는 키워드가 새로 나왔다. 이때는 새로운 (그리고 다소 의도적으로 못낳게 만든) fileprivate 키워드는 드불게 사용되어서 스위프트의 단계적 공개의 설계 철학에의해 지켜진다. 우리가 이것에대해 조금 알고있는 것은 이것이 실전에는 옳지 않다는 것이다. fileprivateprivate의 기능이 오버랩되고 private용어가 오버로드되면서, 인지적 로드가 증가하는 또다른 사이드이펙트가 있었다.

open에서 바깥
단지 삼개월후에 초기의 토론이 다른 접근제어 변화가 시작되었다. 세번의 논쟁적 검토기간 과 수정 후에 public 접근과 public 오버라이드가능함의 차이를 구별하자는 SE-0117 제안은 스위프트3에서 받아드려졌다. 이 제안은 open이라 부르는 새로운 접근수준을 만들었고 어떤 문맥에서 public의 정의가 바뀌었다. public의 의미가 서브클래스하는것과 오버라이딩하는것에 관련하여 좁아졌다. public 클래스는 더이상 이것이 정의된 곳에 모듈의 바깥에서 서브클래스될 수 없고 클래스의 모든 public 맴버들은 더이상 클래스 모듈의 바깥에서 서브클래스에의해 오버라이드 될 수 없다. 따라서 open 클래스들은 public이며 서브클래스가능하고, open 클래스에있는 모든 open 프로퍼티나 open 함수는 서브클래스에서 오버라이드될 수 있다. 이해할 수 있겠는가? publicopen에 관련된 규칙들은 혼란스럽고, 오버라이드와 서브클래스를 만드는것을 막는 final에의해 더 복잡해진다. 따라서 final publicpublic 클래스는 같은 모듈안이나 그 밖깥에 클라이언트가 있는지 없는지에따라 의미가 달라진다. 다시말해, 접근제어에대해 생각할때 인지적 로드가 증가한다. 이번시간은, openpublic의 기능을 오버랩하는것을 더 판별해야하고 구별하기 어렵다.

open의 복잡함과 그 규칙의 미로와 엣지 케이스에도 불구하고 단계적 공개 철학에의해 너무 잘 지켜졌다. 많은 스위프트 사용자(특히 초보자들)는 open를 사용하거나 알려고 하지 않을 것이다. 라이브러리 저자나 앞서간 사용자들만이 open 사용을 마주할 것으로 보인다. open의 단계적 공개는 스위프트 설계 성질에서 open이 적용되지 않은 값타입에 더 명백하게 되었다. 왜 우리가 SE-0117를 되돌리는 제안이나 open을 더 수정하는 제안을 보지 못했는지 그 이유라고 생각한다.

open은 성공적으로 단계적 공개가 되었을지라도, 적어도 내 경험상 이것은 메리트가 작다는 주장을 여전히 하고싶다. 특별히 퍼블릭 API의 부분일때, 대부분 나는 클래스를 final로 정의한다. 드물게 모듈안의 클래스를 서브클래스로 만들고 싶을때가 있지만, 모듈 바깥에서는 아니다. 특히 스위프트만큼 표현력있고 풍부한 언어에서는 동작을 내부적으로 공유하기위해 클래스와 모듈을 설계하는 다양한 방법이 있으나, 외부적으로 그 동작을 노출시키는것은 피한다. open같은 기능은 명백하게 주로 사용되지 않는 것이다. 이것이 실망스러움을 넘어 public 의미에 영향을 주게 만든다.

게다가, SE-0117SE-0025가 받아드려지고 구현된 후에 의논되고 검토되었더라도 이것은 본래 SE-0025로부터 고립된 제안이 되었다는 점을 짚어야한다. 물론, 그때 커뮤니티는 SE-0025에대해 알았으나 누구도 실제로 스위프트 3과 새로운 privatefileprivate를 사용해보지 않았었다. (스냅샷에서 누군가가 이것을 시험해보았을수 있으나, 매우 적은 개발자들이 그렇게 했을 것이다.) 우리는 그 암시와 SE-0025의 현실성에대해서 완전히 깜깜한 상태였다. 접근제어를 증대하는것에 취해있는동안, 커뮤니티는 변경하기위해 다른 프로포절을 다루고 있었다. 명확하게하면, 누구의 책임도 아니다. 그냥 우리는 인지하지 못했었다.

스위프트 3.0의 접근제어
이것은 스위프트의 현재 접근제어 상태로 나온 것이다. The Swift Programming Language eBook에서 나온 문장이다.
  • open 접근은 정의하는 모듈을 불러온 다른 모듈로부터, 혹은 정의하는 다른 모듈로부터 어떤 소스파일에서도 엔티티를 사용될 수 있다. open은 클래스에만 적용하고, 그것이 접근가능한것에서 서브클래스로 만드는게 가능하다. 또한 모든 open 클래스는 그 맴버를 오버라이드되게 하기위해 open으로 정의할 수 있다.
  • public 접근은 정의하는 모듈 안에서만 서브클래스하고 오버라이드할 수 있는 점만 빼면 open과 비슷하다.
  • internal접근(디폴트이다)은 정의하는 모듈로부터 모든 소스 파일에서 사용할 수 있으나, 모듈 밖에서는 안된다.
  • fileprivate 접근은 소유하는 정의하는 소스파일에만 엔티티를 사용할 수 있다.
  • private 접근은 정의나 스코프로 감싸진 곳에만 엔티티를 사용할 수 있다.

굉장히 짧은 시간에 스위프트는 접근수준의 갯수가 세개에서 다섯개로 두배가까이 뛰었고, 이전의 두 키워드의 의미를 고쳤다. 나는 경험한 프로그래머들이 드것들의 차이를 설명하고, 적절한 사용을 글에 담으려고 노력하는 모습을 보아왔다. 초보자에게 모나드(monads)를 설명하는게 접근제어수준을 설명하는것보다 쉬울땨 뭔가 잘못되었다는것을 알 수 있을 것이다.

단계적 공개의 철학으로 돌아와서, 이 접근수준의 어떤것이 계속 우리에게 필요한것으로 고려될까? 위에서 말한 이유료 open은 없앨 수 있다. internal도 디폴트고 명시적으로 할 필요가 없으므로 없앨 수 있다. 매일 사용하고 일반적인 것으로 public, fileprivate, private이 남았다. 이전보다 더 복잡하게 동작하게하는, 키워드 하나가 늘었다.

fileprivate을 살리거나, 혹은 커다란 타협(compromise)
최근에 private 접근수준을 고치자는 SE-0159프로포절은 SE-0025 변경을 되돌리자는 것으로 되었다. 이것은 fileprivate를 없애고 (스위프트1, 스위프트2의) private 원래 의미로 복구하자는 것이다. 왜이렇게 됐을까? 쉽게 말하면, fileprivate가 꽤 자주 사용되어서 단계적 공개를 부셨다. 새로운 private의 맴버 타입은 같은 파일안에 익스텐션으로 정의되있더라도 더이상 접근할 수 없게되므로, 근본적인 스위프트의 확장-지향 스타일을 망가뜨린다. 이 프로포절은 SE-0025만큼 논쟁적이고 열혈하게 검토되었다. 아이러니하게(혹은 우연히?) 거의 일년만에 SE-0025에대한 마지막 결정 후, 접근제어가 바뀌지 않는 상태로 놔두기로하여 거절되었다고 발표했다. 이 프로포절은 스위프트 4의 소스 안정성에서 너무 많은 영향을 주는게 중요했기때문에 받아드려질 수 없었다. 나도 이런 해결하기 어려운점에는 동의한다.

그러나 SE-0025가 예상의 결과로 되지 않은 접근제어 이휴가 있음은 명확하고, 이것을 어떻게 해결할지에대한 스위프트 커뮤니티에서의 불일치는 존재한다. 코어 팀도 잘 알고있다. Doug Gregor은 다행히도 절충안을 찾고 당장을위해, 아마도 좋은 이유료 스위프트의 접근제어 이야기를 진정시키려고 새로운 토론을 시작했다.

특별히 이 설계는 타입 "X"나 익스탠션으로 정의된 "private" 맴버가 어디서부터 접근할 수 있는지의 것이다.
  • 같은 파일안에 "X"라는 익스텐션
  • 같은 파일에 나타난 것이라면, "X"의 정의
  • 같은 파일안에 나타난 위의 것중 하나의 감싸진 타입(혹은 틱스텐션된 것)
이 설계는 여러 명확한 이점을 가진다.
  • private는 보기에 "전체 모듈보다 작다"는 올바른 디폴트가 되고, 여러 익스텐션에서 타입 정의를 나누는 스위프트 코딩스타일로서 잘 정리된다.
  • fileprivate는 현재 유스케이스로 남지만, 이제 더 작게 사용되는데, 이것은 몇몇 이점을 가진다.
    • 스위프트를 받치는 "단계적 공개" 철학에 잘 맞는다. 여러분은 fileprivate에대해 매우기전까지 당분간은 public, internal, private를 사용할 수 있다(주의: 이것이 SE-0025의 진실이 될것이라 생각하지만 명백하게 틀렸었다)
    • fileprivate가 나타나면, 같은 파일안에 다른 타입들 사이에 재미있는 엮임이 있다는 뜻이다. 이것은 잠재적으로 우리가 대강 사용하고 훑어보기 보다는 fileprivate가 유용한 경고를 해주어서 익스텐션안에 구현을 분리할 수 있게되나.
  • private는 다른 프로그래밍 언어와 비슷하게 정리되었는데, 이로하여금 스위프트로 넘어오는 프로그래머들에게 도움이 될것이다. 스위프트의 높은 익스텐션 사용때문에 약간의 스위프트 트위스트로, private를 만나게되면 그들이 생각하는 것과 비슷하게 생각할 것이다. (When they reach for private, they’re likely to get something similar to what they expect—with a little Swift twist due to Swift’s heavy use of extensions.)
  • private에서 접근제약을 느슨하게 하는것은 현재 코드를 고치지 않아도 될것 같다.

몇몇 단점도 있어보인다.
  • 현재 사전적-스코프의 private 접근재어에 의존하여 패턴을 사용하고있는 개발자들은 새로운 private 해석이 부적절한 제약이될것으로 보인다.
  • 스위프트의 접근제어는 "전체저으로 사전적인(entirely lexical)"것에서 "부분적으로 사전적이고(partly lexical) 부분적으로 타입기반인(partly type-based)" 것으로 갈것인데, 이것이 더 복잡하게 보일수 있다.

궁극적으로 나는 스위프트에 fileprivateopen 접근수준이 나타나는 변경에는 유감스럽다. 이런 변화들 모두 되돌릴수 있기를 바라고, 대신 Swift theme의 부분으로서 접착력있게 접근제어를 수정하는 방법을 고려하면 좋겠다. Doug이 말한것처럼, fileprivate가 드물게 사용될거라는 가정은 완전히 틀렸었다. 이것은 private의 사전적 스코핑을 부순 익스텐션의 주요 결과이다.

SE-0025요청된 수정은 익스텐션과 새로운 동작의 private의 잠재적 스코핑 이슈에 가볍게 알려놓았으나, 이런 암시는 메일링리스트에 넓게 의논되지 않고, 실제로 스위프트 3이 사용될때까지 개발자들에게 완전히 알리지도 못했다. 되돌아가보면, 이것은 메이저한 착오였다. 이것은 내 타입과 익스텐션을 어떻게 설계하는지 때문에 모든곳에서 fileprivate를 사용해야한다는 것을 알게되었을때, 틀림없이 나를 기습하려는 것이었다. 내 경험상, 새로운 privatefileprivate는 실제 문제에대한 솔루션이라기보다는 부담이다. 타입에서 익스텐션이 기능을 구성하고있는 곳에서는, private의 사전적 스코핑 제약이 이상적이고 규약을 잘 지키는 스위프트가 되려는 마음을 무너뜨리게 만든다. 프로토콜 익스텐션은 이런 망가짐의 징후를 증폭시킨다.

Doug이 잡아놓은 아웃라인인 코어팀의 프로퍼절은 좋은 절충안이다. 이 글을 발행하기전에, David Hart는 이것을위한 Type-based Private Access Level이라는 이름의 프로퍼절 초안으로 풀리퀘스트를 열었다. 이것이 받아들여지고 구현되면 좋겠다. 이것이 스위프트 접근제어 시스템에을 더 복잡하게 할지라도, 많은 복잡성은 세부 구현 뒷편의 이야기이다. 사용자 관점에서 privatefileprivate 수정자는 이것에대해 설명하고 설득하는데에 더 쉽게들린다. SE-0025에 정의된 실제 private 사용 이전에, 나는 많은 스위프트 사용자가 private 맴버를 익스텐션에서 접근가능한 것으로 기대했을것이라 생각한다. Doug이 설명한 "부분적으로 사전적이고 부분적으로 타입기반인" 접근제어의 이점은 명료함이고, 내 생각엔 이것들이 그 결점보다 중요하다.

우리는 분명 최적의 위치에 있지 않다. 이것은 이상에서 멀어졌으나, 실제 문제를 해결한다. 위의 제안이 구현되면, 비록 우리 뒤에 깊이 남긴 발자국이 남겠지만, 우리스스로 그려놓은 막다른길로부터 탈출할 수 있다. 많은 사용자와 많은 시간동안 publicprivate만 필요료하게될 그런 상태로 되돌아가게 될것이다. (internal은 여전히 디폴트인데, 타이핑할 필요 없다.) fileprivate를 사용하는것은 드물고 눈에띄게 될것인데, 아마도 결국 나쁜 방법으로 고려될것이다.

기회비용
이번 스위프트 3.0 릴리즈에서 스위프트 커뮤니티가 많은것을 배웠을거라고 생각한다. 접근제어대해 단지 토론하고 휘젓는것 뿐만 아니라, 전반적인 스위프트 에볼루션 프로세스에대해서도 토론해야한다. 오늘날 우리가 있는 이곳에 어떻에 도달했는지 그들의 결과물로서, 우리는 앞으로 나아가는데 마음속에 담아두고, 프로퍼절을 계속 눈여겨봐얀다. 이 프로그래밍 언어로 붕엇을 하고 싶은가? 언어의 개선사항에서 무엇이 우선시되고 무엇이 연기될까? 현재 스위프트 배포 테마에 여러분의 프로퍼절이 적절한가?

스위프트 에볼루션은 무엇이든 되지만 값싸다. 몇몇은 능동적 위험(actively harmful) 이라 생각한다. 모든 연기처럼 모든 변화에는 비용이 있다. 몇몇 변화는 명백하게 비싼반면 다른것들은 더 미묘하다. 스위프트3은 대단한 비용과함께 도착했다(완전히 다른 목표(이 차이를 보자)과 어마어마하게 고통스러운 마이그레이션) 그러나 이것들은 단지 실제 비용들이었고, 그 변화의 결과물이 만들어졌다.

아마도 고려해야할 더 중요한 것은 만들어지지 않은 변화들이다. 각 스위프트 릴리즈에대한 기회비용은 우리가 버리기로 결정한 변화의 가치이다. (이것을 구현되지 않은 모든것들의 가치라 한다) 여기에는 버그를 고친다던지, 컴파일타임 이슈를 해결한다던지, 런타임 성능을 개선한다던지, 전반적인 안정성을 증가한다던지와같은 작업까지, 주요 기능을 포함한다. 이런 일이 일어나지 않았었다고 말하지 않았다, 그들은 틀림없이 그렇게 했었다. 그러나 많은 시간이 스위프트 에볼루션 프로포절에 쓰였고, 몇몇은 지나고보니 명백하게 연기되어왔을 것이다.

어떤것도 스위프트 커뮤니티가 잘못했다고 뜻하지 않는다. 단지 어떻게 하냐의 문제이다. 우리는 코어팀까지 포함해서 모두 배우는 중이다. 다행히도 코어팀은 스위프트 4 프로포절에대해 더 엄격하고 더 생각을 많이하는것은 틀림없으므로, 이런 상황이 다시 찾아올것이라 염려한다. 그러나 이것은 소프트웨어 개발이다. 여기에는 항상 트레이드오프(등가교환)가 있다.




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: 50 iOS Interview Questions And Answers - Part3



내가 받았던 iOS 인터뷰 질문의 마지막 부분으로 올리기로 결정했다. 아직 보지 못했다면 Part1(링크)와 Part2(링크)를 확인해보아라.

1-  좋아하는 앱은 무엇이고 왜 좋아하는가?
아래 답변은 나의 실제 답변이다. 이것들은 내가 좋아하는 앱들과 그 이유이다.

  • Sleep Cycle : 애플의 헬스를 넣었고, 일어나기 모드, 유용한 원형 차트 리프토 도구와 함께 세계의 통계와 필립스 휴를 넣었다.
  • WaterMinder : 하루 물 섭취 이력을 따라갈 수 있다. 코딩하는동안 물 마시는 것을 밎을 수 있는데, 이 앱이 물을 마시라고 알려주며, 다른 종류의 액체류 섭취량(커피 휫수!)도 넣을 수 있다.
  • DayOne : 2년이상 쓰고 있는 앱이다.
  • Facebook : 모든것이 한곳에 있는 앱이다. 스포츠나 메시지, voip, 날씨나 뉴스앱을 따로 설치할 필요가 없다.
  • Mdeium : 순수하고 네이티브하고 오가닉하며 최신의 그리고 point shot 기술관련 글을 쉽게 찾을 있고 그 회사와 개발자들의 비전을 배울 수 있다.

2- JSONSerialization이 어떤 ReadingOption들을 가지는가?
  • mutableContainers는 배열과 딕셔너리를 변수 오브젝트(상수가 아닌)로 만들어준다.
  • mutableLeaves는 JSON 오브젝트 그래프에서 리프(leaf)의 문자열을 변수 문자열 인스턴스로 만들어준다.
  • allowFragments는 배열이나 딕셔너리 인스턴스가 아닌 최고수준 오브젝트로 파싱할 수 있게 해준다.

3- 서브스크립트(subscript)를 설명하시오
클래스, 구조체, 열거형에서 서브스크립트를 정의할 수 있으며, 이것은 컬랙션, 리스트, 시퀸스의 맴버 요소에 접근하기위한 단축방식(shortcut)이다.

4- 디스패치 그룹(DispatchGroup)이란 무엇인가?
디스패치 그룹은 어떤 작업의 동기화를 모을 수 있게 한다. 여러 다른 작업 항목을 보내서, 각각이 다른 큐에서 동작할지라도 모든것이 완료된 것을 확인하는데 사용한다. 이 동작은 명시된 작업이 모두 완료되기 전까지 처리하지 않도록 만들때 유용하다— 애플의 문서

가장 기본적인 답변은, 처리하기 전에 여러 비동기나 동기적인 작업을 기다릴 필요가 있을때, 디스패치 그룹을 사용할 수 있다고 하면 된다.

5- RGR(Red-Green-Refactor)가 무엇인가?


Red, Green, Refactor는 TDD(테스트 주도 개발)의 단계이다.
  1. Red : 보통 7줄이 넘지 않는 짧은 테스트 코드를 작성하고 실패를 확인한다
  2. Green : 짧은 제품 코드를 작성한다. 다시 7줄이 넘지않는 짧은 테스트 코드를 작성하고 테스트가 통과하게 만든다.
  3. Refactor : 테스트가 통과하면, 걱정없이 변경할 수 있다. 코드를 정리(clean up)한다. 여기에 훌륭한 워크샵 노트가 있다.

6- 의존성 주입(Dependency Injection)이란 무엇인가?
iOS 앱에서 스토리보드나 xib을 사용하면 IBOutlet을 만들게된다. IBOutlet은 뷰에 관련된 프로퍼티이다. 이것들은 그 뷰가 인스턴스화될때 뷰컨트롤러에 주입(inject)되며, 이것이 의존성 주입의 본질적인 형태이다.

의존성 주입의 형식에는 생성자 주입, 프로퍼티 주입, 메소드 주입이 있다.

7- 노티피케이션의 타입을 설명하라
노티피케이션 타입에는 리모트(remote)와 로컬(local), 두가지 타입이 존재한다. 리모트 노티피케이션은 서버 접속을 필요로 한다. 로컬 노티피케이션은 서버 접속을 필요로 하지 않는다. 로컬 노티피케이션은 기기 내부적으로 일어나는 것이다.

8- 프로젝트에서 언제 의존성 주입을 사용하면 좋을까?
여러분이 해볼 수 있는 몇몇 가이드라인이 존재한다.
규칙1. 테스트 가능함이 중요한가? 그렇다면 테스트하고 싶은 클래스에 외부 의존성을 식별하는 것이 필수이다. 의존성을 주입할 수 있으면, 테스트하기 쉽게 만들기위해 실제 서비스를 가짜의 서비스(mock)으로 쉽게 대체할 수 있다.
규칙2. 복잡한 클래스는 복잡한 의존성을 가진다. 앱-수준 로직을 가지고있거나 디스크나 네트워크같은 외부 리소스에 접근한다. 앱의 대부분 클래스는 복잡할 것이며, 많은 컨트롤러 오브젝트와 많은 모델 오브젝트를 가지고 있을 것이다. 이것을 시작하는 가장 쉬운 방법은 여러분의 앱에서 복잡한 클래스를 뻬내고 그 클래스안에 다른 복잡한 오브젝트를 초기화할 장소를 찾는 것이다.
규칙3. 한 오브젝트가 다른 오브젝트와 의존성을 공유하는 오브젝트 인스턴스를 만들고 있다면 의존성 주입을 고려해야할 대상이다.

9- 컬랙션 타입(collection type)에서 우리가 사용할 수 있는 오더 함수(order function)은 어떤 종류가 있는가?
map, filter, flatMap에대한 자세한 내용을 확인해보자!

10- 커밋들을 합치게 해주는 깃 명령은 무엇인가?
git squash

11- AnyAnyObject의 차이는 무엇인가?
애플의 스위프트 문서에 따르면 아래와 같다.
  • Any는 모든 타입에대해 인스턴스를 나타낼 수 있으며, 함수 타입이나 옵셔널 타입까지도 포함된다.
  • AnyObject는 모든 클래스 타입의 인스턴스를 나타낸다.

더 세부적인 내용를 확인해보자.

12- 기본적으로 SOAP과 REST의 차이는 무엇인가?
둘 다 웹 서비스의 접근을 도와준다. SOAP는 서비스에 메시지를 날리기위해 독점적으로 XML과 친하다. SOAP은 웹 서비스 접근에대해 절대적으로 무겁다. 원래는 마이크로소프트에서 만들어진 것이다.

REST (Representational State Transfer)은 경량의 대안이다. 요청시 XML을 사용하는 것 대신에 REST는 대부분 간단한 URL을 사용한다. REST는 작업을 수행하기위해 4가지의 HTTP 1.1 용어(GET, POST, PUT, DELETE)를 사용한다.

13- 당신이 좋아하는 시각화 차트 라이브러리는 무엇인가?
Charts는 iOS, tvOS, OSX를 지원하며 애플 버전의 MPAndroidChart이다.
Core Plot는 macOS, iOS, tvOS를 위한 2D 점에대한 곡선을 그리는(plotting) 프레임워크이다.
TEAChart는 iOS를 지원한다.


14- 나쁜 커밋을 찾아주는 깃 명령은 무엇인가?
git bisect

15- 코어데이터(CoreData)란 무엇인가?
코어데이터는 디스크에 영속 저장소에 오브젝트 그래프를 영속할 수 있게 만드는 오브젝트 그래프 매니저이다. 한 오브젝트 그래프는 일반적인 Model View Controller iOS 앱에서 다른 모든 Model의 지도같은 역할을 한다.

16- 연관타입(Associatedtype)을 설명하시오
제네릭 프로토콜을 만들고 싶다면 연관타입을 사용할 수 있다. 더 자세한 내용을 확인해보자.

17- 깃에서 커밋을 만들지 않고서 코드를 저장하는 명령은 무엇인가?
git stash

18- Priority InversionPriority Inheritance 설명하시오
높은 우선순위 스레드가 낮은 우선순위 스레드를 기다리는 것을 Priority Inversion이라 부른다. 낮은 우선순위 스레드가 일시적으로 제일 높은 우선순위의 스레드의 우선순위를 물려받으면(inherit) Priority Ingeritance라 부른다.

19- Hashable이 무엇인가?
Hashable는 딕셔너리에서 키로서 오브젝트를 사용할 수 있게 해주는 것을 말한다. 따라서 우리만의 커스텀 타입을 만들 수 있다.

20- 옵셔널 체이닝(optional chaining)과 if let, guard는 언제 사용할까?
옵셔널 체이닝은 작업이 실패하는것을 별로 신경쓰지 않을때 사용한다. 그렇지 않으면 if let이나 guard를 사용한다. 이 대답은 Parsing JSON in Swift의 확장판에서 가져온 대답이다.

물음표 연산자를 사용하는 것을 옵셔널 체이닝이라한다. 애플의 문서에는 이렇게 설명하고 있다.
옵셔널 체이닝은 현재 nil일 수 있는 옵셔널에대해 프로토콜, 메소드, 서브스크립트를 쿼리하고 호출하기위해 진행한다. 옵셔널이 값을 가지고 있다면 프로퍼티, 메소드, 서브스크립트 호출은 성공할 것이고; 옵셔널이 nil이라면 프로퍼티, 메소드, 서브스크립트는 nil을 반환할 것이다. 다중 쿼리가 체인(chained)될 수 있으며, 체인에서 어떤 연결이라도 nil이면 모든 체인이 우아하게 실패한다.

21- 스위프트에서 데이터를 전달하는 방법에는 몇가지가 있는가?
델리게이트, KVO, Segue, NSNotification, Target-Action, Callback등 여러 방법들이 존재한다.

22- 어떻게 프로젝트 코드를 깨끗하게 만들 수 있는가?
GithubSwiftLint의 스위프트 프로젝트를 위한 코딩 규약, 스타일 가이드를 따른다.

23- iOS10에서 새로 발표된 내용은 무엇인가?
  • SiriKit
  • Proactive Suggestions
  • 메시지 앱과 동합하기
  • 사용자 노티피케이션
  • 앱 익스텐션
  • CallKit
  • 음성 인식
  • 앱 검색 증진

더 자세한 내용을 확인하자.

24- 옵셔널에서 nil.None의 차이점은 무엇인가?
차이는 없다. Optional.None( .None은 단축형이다)은 값을 잃어버린 옵셔널 변수의 올바른 초기화 방법이다. 반면  nil.None에대한 간편한 문법형태(sugar)이다. 확인해보자.

25- iOS 프로젝트를 위한 다른 IDE를 사용하고 있는가?

그렇다 혹은 아니다. 만약 그렇다고 하고싶다면 Appcode를 확인해보자.

26- VIPER 아키텍처란 무엇인가?
여기에 VIPER에대한 완벽한 설명이 있다.

27- Continuous Integration이 무엇인가?
Continuous Integration은 앱 개발시 뭔가 잘못 되었을때 빠른 피드백을 줄 수 있는 어떤것이다. 수많은 Continuous Integration 툴을 사용할 수 있다.

스스로 호스팅되는 서버

클라우드 솔루션

28- 델리게이트와 콜백의 차이점은 무엇인가?
델리게이트는 NetworkService가 델리게이트에게 "뭔가 바뀌었어"라고 말하는 것이고, 콜백은 델리게이트가 NetworkService를 주시하면서 지켜보고 있는 것이다.


29- 이전에 디자인 툴이나 프로토타입 툴을 사용해 본 적이 있는가?
디자인 툴을 아무것도 모른다면 오늘 바로 시작해보길 바란다.

Sketch + Marvel는 좋은 궁합이다. 코딩을 하면서 프로토타입을 만들어보고 싶으면 Framer도 좋은 선택일 것이다.

30- 백엔드 개발에대해 알고있는가?
사람마다 다를것이다. 나는 PARSE에 있어보았고 FBStart를 받았었다. 나는 순수 백엔드를 배우기로 결정했었다. 여러분은 두가지 선택지가 있을것이다. node.js + express.js 그리고 mongodb를 배우거나, Vapor 나 Kitura를 배울 수도 있을 것이다.

Firebase를 좋아하거나 사용하지 못해보았는가?

Firebase는 macOS 개발자들을 위한 길이 없다.

여러분이 Firebase를 배우고 싶으면, 한달코스의 Firebase Google Group를 따라가보아라.

31- 오토레이아웃을 설명하라
오토레이아웃은 유연하고 강력한 레이아웃 시스템을 제공하며, 이것은 뷰와 UI 조작이 그 계층안에서 크기와 위치를 어떻게 계산할지 표현한다.

32- 하드코딩 로그 절의 단점은 무엇인가?
첫째로, 로그를 시작할때이다. 모으기위해서 이것을 시작한다.많아 보이지는 않을 것이지만 매 분마다 추가된다. 프로젝트 끝쯤에는 잃어버린 분들이 시간 단위가 되있을 것이다.
둘째로, 코드베이스에 추가할 때마다 우리 코드에 새로운 버그를 주입하는 위험을 겪는다.

33- 포인터란 무엇인가?
포인터는 메모리 주소에 직접 참조하는 것이다. 한 변수는 값을위한 투명한 용기로서 동작하는 반면, 포인터들은 추상화층을 없애고 값이 어떻게 저장되있는지 볼 수 있게 해준다.

34- 계약직으로 일해본 적이 있는가?
그렇게 해보면 좋다. 그것은 그것의 장점이 있다. 내 생각에는 클라이언트를 관리해보고 소통하고 작업 관리 능력을 증진시키는데 좋은 경험인 것 같다.

35- 쌍 프로그래밍(pair programming)이란 무엇인가?
쌍 프로그래밍은 하급 개발자와함께 정보를 공유하는 도구이다. 하급 개발자와 상급 개발자가 나란히 앉아서, 상급 개발자에게 배울 수 있는 방법이다.

Martin Fowler의 “Pair Programming Misconceptions” WikiHow의 "Pair Programming" 를 확인해보아라

36- 블럭(block)에대해 설명하라
블럭은 단일 작업이나 동작의 유닛을 전체적인 Objective-C 클래스를 쓰지 않고 정의할 수 있는 하나의 방법이다. 익명 함수이기도 하다.

37- 키체인(Keychain)이란 무엇인가?
키체인은 iOS 앱에서 데이터를 보안상 저장하기위한 API이다. 좋은 라이브러리가 있다(Locksmith).

38- UserNotification에서 가장 크게 바뀐 점은?
  • 오디오, 비디오, 이미지를 넣을 수 있게 되었다.
  • 노티피케이션에 커스텀 인터페이스를 넣을 수 있게 되었다.
  • 노티피케이션 센터에서 인터페이스를 관리할 수 있게 되었다.
  • 새로운 노티피케이션 익스텐션은 우리가 전달하기 전에 리모트 노티피케이션 적재량을 관리할 수 있게 되었다.

39- atomicnonatomic synthesized 프로퍼티의 차이에대해 설명하라
atomic : 디폴트 동작이다. 한 오브젝트가 atomic으로 선언되있으면 스레드 세이프하게된다. 스레드 세이프의 의미는, 클래스의 특정 인스턴스의 한 스레드만 그 오브젝트에대해 조작할 수 있다.
nonatomic : 스레드 세이프 하지 않다. nonatomic 프로퍼티 속성을 사용하면 synthesized 접근자가 간단하게 직접 값을 세팅하거나 리턴하도록 지정할 수 있다. 서로 다른 스레드에서 동시에 같은 값에 접근하려 할때 어떤 일이 일어날지는 보장하지 못한다. 이런 이유로, atomic보다 nonatomic이 접근 속도가 더 빠르다.

40- availability 속성은 왜 사용하는가?
애플은 한 시스템 버전 back을 지원하고 싶어하는데, 이 의미는 우리가 iOS9와 iOS8을 지원해야한다는 것이다. Availability 속성은 이전 버전의 iOS를 지원하게 해준다.

41- 디바이스 토큰을 얻는 방법에는 무엇이 있는가?
두가지 단계가 있다. 첫째로 사용자 허가 화면을 반드시 보여야한다. 그 뒤에 리모트 노티피케이션을 등록할 수 있다. 이 단계가 잘 넘어가면 시스템에서 디바이스 토큰을 줄 것이다. 만약 앱을 지우거나 재설치하면 디바이스 토큰은 변경될 것이다.
 
42- 캡슐화란 무엇인가?
캡슐화는 객체지향 설계 원리이며 내부 상태와 오브젝트의 기능을 숨긴다. 이 의미는, 오브젝트가 자신의 상태 정보를 사적으로(private) 들고 있는다.

43- 빅오(Big-O) 노테이션이란 무엇인가?
알고리즘은 N 크기의 입력에대해 결정되는 작업 횟수를 표현하는 방법이다. 빅오 노테이션 등급은 가장 높은 값으로 표현된다. 그리고 빅오 노테이션은 질문의 O(n)으로 답을 찾는다. 여기 cheat sheet swift algorithm club가 있다. 아래는 예제이다.
for 루프의 빅오 노테이션은 O(n)이다. for 루프가 n번 동작하기 때문이다.
변수(var number: Int = 4) 빅오 노테이션은 O(1)이다.

44- 의존성 관리란 무엇인가?
오픈소스 프로젝트를 통합하고 싶을때나 써트파티 프로젝트로부터 프레임워크를 추가하거나 어떤 고유의 프로젝트의 코드를 재사용할때, 의존성 관리는 이 관계를 관리할 수 있도록 해준다. 여기를 왁인해보자

45- UML 클래스 다이어그램란 무엇인가?
UML 클래스 다이어그램은 규칙의 집합이고 소프트웨어의 명세에대한 노테이션인데, 오브젝트 관리 그룹(Object Management Group)에의해 관리되고 생성된다.

46- throw를 설명하라
throws 키워드를 이용하면 컴파일러에게 에러를 던저라고 할 수 있다. 에러를 던지기 전에, 여러분이 받고 싶은 에러 목록을 만들어야한다.

47- 프로토콜 익스텐션이란 무엇인가?
익스텐션을 이용하여 프로토콜을 적용할 수 있는데, 기존의 타입 정의에도 가능하다. 가지고 있지 않는 타입에 프로토콜을 추가할 수 있게 해준다.

48- 2017년 iOS 앱 개발에서 트랜드는 무엇이었나?
  • 스위프트 코딩
  • 인자화된 현실성
  • IoT
  • 보안성 증진
  • 엔터프라이즈 앱에서의 성장
  • 하이브리드 기술(리엑트 네이티브)에서의 성장

49- ObjC에서 Selectors를 설명하시오
Selectors는 Objective-C의 내부적인 메소드이름 표현법이다.

50- 리보트 노티피케이션 첨부물의 한계는 어떻게 되는가?
푸시 노티피케이션으로 비니오나 이미지를 보낼 수 있다. 그러나 최대는 4kb이다. 만약 높은 퀄리티의 첨부물을 보내고 싶으면, 노티피케이션 서비스 익스텐션을 사용할 수 있다.


나의 추천들




WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목 :



반갑다 , Part2가 준비되었다. Part1을 확인해보고 싶으면 여기(링크)를 확인해보자.

1- 스위프트에서 메소드 스위즐링(Method Swizzling)을 설명하라
메소드 스위즐링은 Objective-C나 동적 메소드 디스패치를 지원하는 다른 언어에서는 잘 알려진 방법이다.

스위즐링은 통해, 특정 #selector(method)와 그 구현을 담은 함수 사이의 맵핑을 변경하여, 메소드의 구현이 런타임시 다른 것으로 대체될 수 있다.

스위프트 클래스와함께 메소드 스위즐링을 사용하기 위해서는 반드시 따라야할 두가지 필수조건이 있다.
  • 스위즐될 메소드를 가진 클래스는 NSObject를 확장해야한다.
  • 스우즐하고싶은 메소드는 동적 속성을 가지고 있어야한다.

2- Non-Escaping 클로저와 Escaping 클로저의 차이는 무엇인가?
non-escaping 클로저의 라이프사이클은 단순하다.
  1. 함수에 클로저를 전달한다.
  2. 함수가 클로저를 실행시킨다.
  3. 함수는 반환한다.

escaping 클로저의 의미는, 함수 안에서, 여전히 클로저를 실행시킬 수 있다; 클로저의 추가적인 부분은 함수 바깥에서 죽지않는 장소에 저장되있다. 여기에는 클로저가 그 담겨진 함수를 escape하는 여러 방법들이 잇다.
  • 비동기 실행 : 디스패치 큐에서 비동기적으로 클로저를 실행시킨다면 큐는 클로저를 잡고 있을 것이다. 클로저가 언제 실행될지는 알지 못하고 함수가 리턴되기 전에 이것이 완료될지는 보장하지 못한다.
  • 스토리지 : 전역 변수, 프로퍼티, 아니면 다른 스토리지(이전의 함수호출에 살고있는)에 클로저를 저장하면 클로저는 escape된다.


3- [weak self][unownded self]를 설명하라
unowned는 한가지만 빼고서 weak와 같다. 바로, 이 변수는 nil이 될 수 없다는 것이고 그러므로 이 변수는 절때 옵셔널이 될 수 없다.

그러나 그 인스턴스가 메모리 해제된 후에 변수에 접근하려하려한다면, 인스턴스가 메모리 해제된 후에 절때 그 변수를 사용하지 않을것이라고 확신할때만 unowned를 사용해야한다.

그러나 변수를 weak로 만들고 싶지 않고 해당 인스턴스가 메모르 해제된 후에 그 변수에 접근하지 않을 확신이 있을때 unowned를 사용할 수 있다.

[weak self]로 정의하면, 클로저 내의 어떤 부분이 nil일 수도 있는 경우를 다룰 수 있게 해주므로 변수는 반드시 옵셔널이여야한다. 비동기 네트워크 요청에서 [weak self]를 쓰는 경우는 그 요청이 빈번하게 사용되는 뷰의 View Controller에서 이다.

4- ARC란 무엇인가?
ARC는 애플에서만든 컴파일타임의 자동 메모리 관리 기능이다. Automatic Reference Counting의 약자이다. 이 말은 오브젝트의 강참조(strong reference)가 0이될때만 그 오브젝트를 메모리에서 해제한다는 뜻이다.

5- #keyPath()를 설명하시오
#keyPath()를 사용하면, StaticString이나 StringLiteralConvertible로 사용된 key-path 리터럴 문자열의 이점에의해 스테틱 타입 체크가 시행될 것이다. 이 시점에서 아래의 것을 보장하는 체크가 일어난다.
  • 실제로 존재하는가
  • Objective-C로 적절하게 노출되었는가

6- IGListKit가 개발자에게 제공하는 것이 무엇인가?
IGListKit은 자동으로 다른 오브젝트들을 비교하여, 변경된 것을 UICollectionView에 배치(batch) 업데이트 애니메이션을 시행한다. 우리가 절때로 배치를 짜지 않는 방법으로 갱신한다.

7- 리엑트 네이티브가 iOS에게 특별하게 만드는 것은 무엇인가?
  1. (PhoneGap과 다르게) 앱을 리엑트 네이티브로 짜면 자바스크립트로 작성하고 실행시키지만, 앱 UI는 전적으로 네이티브이다. 따라서 HTML5 UI와 소통하는 요소는 없다.
  2. 추가적으로(Titanium과 다르게), 리엑트는 novel, radical, 매우 함수적인 접근법을 UI 구성에 사용한다.요약하면, 앱 UI는 간단히 현재 앱 상태의 함수로 표현된다.

8- NSFetchRequest란 무엇인가?
NSFetchRequest는 Core Data에서 패치하는 역할의 클래스이다. 패치 요청은 강력하기도하고 유연하기도하다. 어떤 기준에따른 오브젝트 집합을 패치하라고 요청할 수도 있고 개별의 값들을 패치하라고 요청할 수도 있고 등등이 있다.

9- NSPersistentContainer란 무엇인가?
퍼시스턴트 컨테이너는 앱을 위해 로드한 것을 가지고 있는 컨테이너를 만들어서 반환한다. 저장된 것을 생성하는데 실패한 정규의 에러 상태가 있으므로, 이 프로퍼티는 옵셔널이다.

10- NSFetchedResultsController를 설명하시오
NSFetchedResultsController는 컨트롤러이지만 뷰컨트롤러는 아니다. 사용자 인터페이스를 가지고 있지 않는다. 이것은 Core Data에서 나온 데이터 소스와 테이블뷰의 동기화 코드를 추상화시켜 개발자들을 좀 더 편하게 만들어주는 목적이 있다.

옳바르게 NSFetchedResultsController를 초기설정하면 여러분의 테이블은 몇줄 코드 없이 데이터 소스를 표현해줄것이다.

11- Xcode8에서 주된 3가지 디버깅 개선사항이 무엇인가?
  • View Debugger는 런타임시 우리의 레이아웃을 시각화해주고 constraint 정의를 보여준다. 이것이 Xcode6부터 있었지만 Xcode8에서 constraint 충돌에대한 경고가 새로  나왔는데 매우 유용하며, 다른 편리한 기능들도 소개되었다.
  • Tread Sanitizer는 Xcode8에서의 완전히 새로운 런타임 도구인데, 스레드 이슈를 경고해준다-가장 눈에띄게는 잠재적인 경쟁상태가 있다.
  • Memory Graph Debugger도 Xcode8에서 완전히 새로 나온 것이다. 이것은 시간상 한 지점의 앱 메모리 그래프를 시각화해주고 이슈 네비게이터에서 누수를 표시한다.

12- 테스트 주도 개발의 세가지 규칙은 무엇인가?
  1. 실패한 유닛테스트를 통과시키지 않은 것을 제품의 코드로 만들지 않아야한다.
  2. 충분히 실패한 것보다 더 많은 코드를 작성하지 않으면 안된다; complication 실패는 실패이다.
  3. 한가지 실패한 유닛테스트를 통과시킨것보다 더 제품의 코드를 쓰지 않아야 한다.
  1. You are not allowed to write any production code unless it is to make a failing unit test pass. 
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

13- 클래스 안에서 final 키워드가 무엇인지 설명하시오
메소드 이름 앞에 final이라는 키워드를 넣으면, 그 메소드가 오버라이드 되는 것을 막아준다. 만약 final class 키워드를 static이라는 한 단어로 바꿔도 같은 동작을 한다.

14- Yak Shaving이 의미하는 것은 무엇인가?
Yak Shaving은 한 프로젝트가 다음 단계로 진행하기 전에 실행해야할 일련의 작업들을 말하는 프로그래밍 용어이다.

// 역자: 접근자 수준은 스위프트4에서 또 크게 바뀌었습니다. 이 번역글(링크)를 참조해 주세요.
15- openpublic 접근 수준의 차이는 무엇인가?
open은 다른 모듈이 클래스를 사용하거나 상속받는게 가능한 것이다; 맴버의경우 다른 모듈이 그 맴버를 사용하거나 오버라이드할 수 있다.
public은 다른 모듈이나 public 클래스나 public 맴버로 사용할 수 있다. public 클래스는 더이상 하위클래스를 만들 수 없고 public 맴버도 오버라이드할 수 없다.

16- fileprivateprivate 접근 수준의 차이는 무엇인가?
fileprivate는 현재 파일 안에서 접근할 수 있음을 의미하고, private는 현재 선언 안에서 접근할 수 있음을 의미한다. 

17- 내부 접근(Internal access)이 무엇인가?
내부 접근(internal)은 모든 소스 파일 안에서 그들이 정의한 모든 모듈 안에서 엔티티를 사용할 수 있게 하는 것이지만 모듈 바깥의 소스파일은 안된다.

내부 접근은 접근 수준의 디폴트이다. 코드에 어떤 접근 제어자도 쓰지 않았다면 디폴트로 내부접근 수준으로 되있을 것이다.

18- BDD와 TDD의 차이는 무엇인가?
BDD와 TDD의 가장 큰 차이는 공학도가 아닌 사람도 BDD 테스트 케이스를 읽을 수 있다는 점인데, 팀에게 매우 유리하다.

나는 iOS에서 Quick라는 BDD 프레임워크를 선호하고 그것과 궁합이 잘 맞는 Nimble가 있다.

19- Arrange-Act-Assert를 설명하라
AAA는 유닛테스트에서 코드를 정렬(arrange)하고 포맷화(format)시키는 패턴이다. 만약 XCTest를 쓰기로 했으면 각 테스트는 빈 줄을 기준으로 기능적인 섹션별로 그룹화할 것이다.
  • Arrange, 필요한 모든 예측과 입력
  • Act, 테스트중에 오브젝트나 메소드에서 일어나는 것
  • Assert, 예상한 결과가 나오는 것

20- iOS앱에서 테스트를 만드는 것에는 어떤 이점이 있는가?
  • 좋은 테스트는 예상되는 동작의 훌륭한 문서가 된다.
  • 우리가 뭔가 바꾸면 테스트가 실패할거라는 것을 알고 있기 때문에 계속해서 우리 코드를 리팩토링할것이라는 확신을 준다.
  • 테스트를 짜기 어려우면 그 아키텍처를 개선시킬 수 있다. 다음 RGR(Red-Green-Refactor)는 초기에 개선할 수 있게 도와준다.

21- 모바일 제품 설계의 타이포그래픽 퀄리티를 개선시키기위한 필수의 다섯가지 가이드라인은 무엇인가?
  1. 텍스트의 서체를 정하는 것부터 시작하라
  2. 서체를 섞어 쓰는 것을 피하라
  3. 줄 길이를 확인해라
  4. 줄의 높이와 포인트 크기의 균형을 맞춰라
  5. 적절한 아포스트로피(Apostrophe)와 데쉬(Dash)를 사용하라

22- if let의 구조를 설명하라
if let은 스위프트에서 옵셔널 rhs가 값을 가지고 있는지 확인하게 해주는 특별한 구조이다. - 만약 값이 있다면 언랩시켜서 lhs에 할당한다.

23- 어떻게 문맥으로 앱을 학습시킬 것인가?
사용자를 돕는 문맥 기술상의 학습은 요소와 상호소통하고 아주 이전에 완성하지 못하는 방법에 직면한다. 이 기술은 종종 약간의 시각적 단서나 미묘한 애니메이션을 포함한다.



24- bitcode란 무엇인가?
XML타입 목적으로 사용할 수 있는 매우 일반적인 인코딩 포맷이다. 스스로 설명하는 파일 포맷이며 여러 다른 것들이 Bitcode로 인코딩 될 수 있다. Apple Watch 앱은 Bitcode로 되어야하고 Apple TV도 필요하다. iOS앱은 아직 선택적으로 가능하다. 이것에대한 이점은 컴파일러가 keep getting better 하는 것이다.

25- 스위프트 표준 라이브러리 프로토콜을 설명하라
세가지 프로토콜이 있다. equatable 프로토콜은 같은 타입의 두 인스턴스가 어떻게 구별되는지를 결정한다. 우리 앱에서 특정 값을 가지고 있다면. comparable 프로토콜은 같은 타입의 두 인스턴스를 비교하며 sequence 프로토콜은 prefix(while:)drop(while:)이다. [SE-0045]

26- SVN과 Git의 차이점은 무엇인가?
SVN은 버전 관리를 위한 중앙집중식 체계를 따른다. 중앙 저장소는 작업물의 복사본을 생성하고 접근시 네트워크 접속을 필요로한다.

Git은 버전 관리를 위한 분산형 체계를 따른다. 작업하고있는 로컬 저장소를 가질 것이고, 동기화가 필요할 때만 네트워크 접속을 한다.

27- CollectionViewTableView의 차이점은 무엇인가?
TableView는 한 컬럼으로 여러 항목들의 리스트를 보여주고, 수직적 방법이며, 수직 스크롤로 제한된다.
CollectionView도 여러 항복의 리스트를 보여주지만 여러 컬럼과 로우를 가질 수 있다.

28- Alamofire가 하는일은 무엇인가?
Alamofire는 요청/응답 메소드, JSON 파라미터, 응답 시리얼라이즈, 인증등 여러 기능을 연결할 수 있게(chainable) 해준다.

29- REST, HTTP, JSON가 무엇인가?
HTTP는 어플리케이션 프로토콜이며 규칙들의 집합이다. 웹사이트는 웹서버에서부터 클라이언트까지 데이터를 전송하는데 사용한다. 클라이언트(웹브라우저나 앱)는 필요한 행동을 나타내는데 사용한다.
  • GET : 웹페이지같은 것이 데이터를 검색하는데 사용한다. 그러나 서버의 데이터를 변경할 순 없다.
  • HEAD : GET의 식별이지만 헤더에 되돌려받으며 실제 데이터는 들어있지 않다.
  • POST : 서버에 데이터를 보내는데 사용되며, 보통 양식(form)을 채워넣고 보내기(submit)버튼을 누를때 사용된다.
  • PUT : 특정 제공된 위치에 데이터를 보낼때 사용한다.
  • DELETE : 특정 제공된 위치의 데이터를 지울 때 사용한다.

REST(혹은 REpresentational State Teansfer)는 일관성을 설계하기위한 규칙들의 집합이며, 웹-API를 사용하기 쉽고 유지보수하기 용이하게 만든다.

JSON은 JavaScript Object Notation의 약자로서, 직관적이고 사람이 읽을 수 있도록 해주며, 두 시스템간의 데이터 전송을 위한 포터블 메커니즘이다. 애플은 오브젝트를 변환하거나 그 반대로 할때 JSONSerailization 클래스를 사용할 수 있게 해놓았다.

30- 델리게이션이 해결할 수 있는 문제는 무엇인가?
  • 오브젝트들이 엮이는것을 막는다
  • 자식클래스 오브젝트 필요 없이 동작이나 모습을 수정한다
  • 임의의 오브젝트에게 작업을 넘겨줄 수 있다

31- 프레임워크의 주 목적이 무엇인가?
프레임워크는 세가지 목적을 가진다
  • 코드의 캡슐화
  • 코드의 모듈화
  • 코드의 재사용성

당신은 다른 앱이나 팀원, 아니면 iOS 커뮤니티에서 프레임워크를 공유할 수 있다. 스위프트의 접근 제어와 함께 쓰면 프레임워크는 강력하게 정의하고 코드 모듈간에 테스트할 수 있는 인터페이스를 선언할 수 있게 해준다.

32- 스위프트 패키지 매니저를 설명하시오
스위프트 패키지 매니저는 스위프트 생태계를 광활하게 증대시켜주며, 리눅스같은 Xcode가 없는 플랫폼에서도 스위프트를 배포하고 사용하기 쉽게 만들어준다. 스위프트 패키지 매니저는 의존성 지옥이라는 문제를 안고있는데, 이것은 서로 의존하는 라이브러리를 사용할때 나타날 수 있다.

스위프트 패키지 매니저는 마스터 브런치 사용만 지원한다.

33- 일대다 패턴으로 엮임을 막기 위한 소통 방법은 무엇인가?
노티피케이션

34- 일대일 패턴으로 엮임을 막기 위한 소통 방법은 무엇인가?
한 오브젝트에서 메소드를 호출하는 것

35- 텍스트 필드의 이벤트를 받기위해 왜 델리게이트 패턴을 사용하는가?
그 이벤트에 대해서는 대부분 한 오브젝트에서만 필요로 하기때문이다

36- inout을 명시한 파라미터가 보통 파라미터와 다른점은 무엇인가?
보통 파라미터가 pass by value여도 inout은 pass by reference로 해준다.

37- 뷰컨트롤러의 라이프사이클 이벤트 순서를 설명하라
몇몇 다른 라이프사이클 이벤트가 있다.

- loadView
컨트롤러가 관리하는 뷰를 만든다. 이것은 뷰컨트롤러가 생성되고 순차적으로 완성되었을때만 호출된다.

- viewDidLoad
컨트롤러의 뷰가 메모리에 올라간 뒤에 호출된다. 뷰가 생성될때만 호출된다.

- viewWillApear
화면에 뷰가 표시될때마다 호출된다. 이 단계는 뷰는 정의된 바운드를 가지고 있지만 화면회전은 적용되지 않는다.

- viewWillLayoutSubviews
뷰컨트롤러에게 그 자식뷰의 레이아웃을 조정하는 것에 대한 것을 알려주기위해 호출된다. 이 메소드는 frame이 바뀔때마다 호출될 것이다.

- viewDidLayoutSubviews
뷰가 그 자식 뷰의 레이아웃에 영향을 준 것을 뷰컨트롤러에 알려주기 위해 호출된다. 뷰가 그 자식뷰의 레이아웃을 바꾸고난 뒤에 추가적인 변경을 하고싶으면 여기서 하면된다.

- viewDidAppear
뷰가 뷰 계층에 추가되었다고 뷰컨트롤러에 알려준다.

- viewWillDisappear
다음 뷰컨트롤러로 트랜지션하기 전에 일어나며, 기존의 뷰컨트롤러가 화면으로부터 사라지기 전에 이 메소드가 호출된다.

- viewDidDisappear
뷰컨트롤러가 화면에서 사라지고나면 이 함수가 호출된다. 뷰컨트롤러가 화면에 없을때, 실헹시키지 않을 작업들을 중지시키는 용도로 이 메소드를 오버라이드한다.

38- LLVM과 Clang의 차이점은 무엇인가?
Clang는 LLVM 툴 체인의 프론트엔드이다(“clang” C Language Family Frontend for LLVM). 대부분 컴파일는 세가지 파트로 나뉜다.
  1. 앞단-front end(lexical analysis, 파싱)
  2. 최적화기-optimizer(추상 문법 트리를 최적화)
  3. 뒷단-back end(기계어를 생성)

앞단(clang)은 코드를 받아서 추상 문법 트리(LLVM IR)을 생성한다.

39- 클래스란 무엇인가?
클래스는 오브젝트의 선언과 그것이 어떻게 동작하는지를 담고있다. 이 경우, 클래스는 오브젝트의 청사진과 같다.

40- 오브젝트란 무엇인가?
오브젝트는 클래스의 인스턴스이다.

41- 인터페이스란 무엇인가?
Objective-C에서 @interface는 Java의 interface처럼 아무것도 하지 않는다. 간단하게 클래스의 공개된 인터페이스를 선언하며, 그것의 공개 API이다.

42- 언제, 왜 구조체대신에 오브젝트를 사용해야하나?
구조체는 값타입이다. 클래스(오브젝트)는 참조타입이다.

43- UIStackView란 무엇인가?
UIStackView는 수평 혹은 수직으로 일련의 뷰 레이아웃을 잡아주는 방법이다. 사용가능한 공간에 담겨진 뷰들을 어떻게 조정할지 정의할 수 있다. 이 글을 꼭 읽어보길 바란다.

44- iOS 앱 상태는 무엇이 있나?
  1. Non-running : 앱이 실행되지 않고 있음
  2. Inactive : 앱이 포그라운드에서 실행되고 있으나 이벤트는 받지 않음. iOS앱은 inactive 상태로 들어갈 수 있는데, 전화나 SMS 메시지를 받을때가 그 예이다.
  3. Active : 앱이 포그라운드에서 실행되며 이벤트도 받고 있음.
  4. Background : 앱이 백그라운드에서 실행되고 코드를 실행하고 있음.
  5. Suspended : 앱이 백그라운드에 있으나 코드가 실행되지 않음.

45- 개잘자가 다뤄야할 가장 중요한 어플리케이션 델리게이트 메소드는 무엇인가?
운영체제는 다양한 상태로/로부터 변경된것을 편리하게 하기 위해 앱 델리게이트를 이용하여 특정 메소드를 호출한다.아래 일곱가지는 개발자가 다룰 가장 중요한 어플리케이션 델리게이트 메소드이다.

application:willFinishLaunchingWithOptions
시작 프로세스가 초기화되면 호출되는 메소드이다. 이 메소드는 앱에서 어떤 코드보다 가장 먼저 실행되는 기회를 갖는다.

application:didFinishLauchingWithOptions
시작 프로세스가 거의 완료되면 호출되는 메소드이다. 앱 창에 표시되기 직전에 호출되는 메소드이기때문에 인터페이스를 준비하고 최종 조정을 할 수 있는 마지막 기회이다.

applicationDidBecomeActive
앱이 활성상태가 되면 앱 델리게이트는 applicationDidBecomeActive 메소드를 통해 콜백 노티피케이션 메시지를 받을 것이다.

또한 이 메소드는 이전의 비활성(inactive) 상태에서 활성상태로 돌아올때마다(전화나 SMS에서 돌아올때마다) 호출된다.

applicationWithResignActive
applicationWillResignActive 메소드가 호출되는데는 여러 조건들이 있다. 임시적인 이벤트(핸트폰 전화같은)가 일어날때마다 메소드가 호출된다. 또한 iOS앱을 "끈다(quitting)"는 말은 프로세스를 죽인다는 뜻이 아니라 앱을 백그라운드(background)로 보낸다는 뜻임에 주의하자.

applicationDidEnterBackgroud
이 메소드는 iOS 앱이 동작하고 있지만 더이상 포그라운드(foreground)에 있지 않을때 호출된다. 다른말로는 사용자 인터페이스가 현재 보여지고 있지 않다는 뜻이다. 애플의 UIApplicationDelegate Protocol Reference에따르면 앱은 작업을 실행하고 리턴하기위해 대략 5초정도 가지고 있는다. 이 메소드가 5초안에 나타나지 않았다면 앱은 죽은 것이다.

applicationWillEnterForeground
이 메소드는 앱이 백그라운드에서 포그라운드로 넘어갈 준비가 되었을때 호출된다. 그러나 앱이 applicationDidBecomeActive 메소드가 호출되지 않은채로 활성 상태로 넘어가지 않는다. 이 메소드는 앱이 활성상태로 오기 전에 이전 실행 상태로 다시 세팅할 수 있는 기회를 준다.

applicationWillTerminate
이 메소드는 종료 이벤트가 발생했을때 알려주는 어플리케이션 델리게이트이다. 홈버튼을 눌러서 앱일 끈다. iOS를 강제로 종료하거나 기기를 끄게되면 applicationWillTerminate 메소드가 호출된다. 이것은 앱 구성, 설정, 사용자 선택을 저장할 수 있는 기회를 제공해준다.

45- 코드 사이닝(code signing)이 하는 일은 무엇인가?
앱을 사이닝하면 iOS가 우리 앱에 사인한 사람이 누군지 식별하고 사인한 이휴로 앱이 변경되지 않았다는 것을 보장한다. 사이닝 식별(Signing Identity)는 애플이 만든 public키-private키 쌍으로 이루어져있다.

46- 프로퍼티와 인스턴스 변수의 차이는 무엇인가?
프로퍼티는 추상적인 개념이다. 인스턴스 변수는 그냥 저장 슬롯이다(구조체의 슬롯처럼). 일반적으로 다른 오브젝트는 그것에 직접 접근할 수 없다고 가정한다. 보통 프로퍼티는 인스턴스 변수를 겟(get)하거나 셋(set) 하지만, 어떨때는 여러곳으로부터 데이터를 불러와 사용할 수도 있고 불러오지 않고서 바로 사용할 수도 있다.

47- 스위프트 패키지 매니저
스위프트 패키지 매니저는 UIKit을 지원하지 않는다. 스위프트 패키지 매니저를 이용하면 여러분은 파일 템플릿이나 다른 프로젝트를 위한 프레임워크를 만들 수 있다.

48- SDK와 프레임워크의 차이를 설명하라
SDK는 소프트웨어 개발 툴의 집합이다. 이 집합은 앱을 만드는데 사용한다. 프레임워크는 기본적으로 소프트웨어 어플리케이션을 개발하는데 사용되는 플랫폼이다. 이것은 특정 플랫폼에서 개발될 수 있는 플랫폼에 필요한 파운데이션을 제공한다. SDK와 프레임워크는 각자의 완성본이며 SDK는 프레임워크로 이용가능하다.

49- 다운캐스팅(Downcasting)은 어떻게 사용할까?
Objective-C에서 다른 타임으로 케스팅할때는 그렇게 하는 방법이 하나밖에 없었으므로 아주 간단했다. 스위프트에서는 두가지 방법의 캐스팅이 존재하는데, 한가지는 세이프하고 한가지는 그렇지 않다.
  • as는 업캐스팅(Upcasting)에 사용되고 타입을 연결된 타입에 타입캐스팅에 사용된다.
  • as?는 실패하면 nil을 반환하는 안전한 캐스팅으로 사용된다.
  • as!는 실패하면 크래쉬가나는 강제 캐스팅에 사용된다. 다운캐스팅이 실패하지 않을 것이라는 것을 알때만 사용한다.

50- do-catch 블락은 왜 있는가?
스위프트에서 에러는 do-catch 블락 안에서 에러를 받아(thrown)서 다룬다(handled),



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,
제목: 50 iOS Interview Questions And Answers - Part1


 



1- 실시간 랜더링(Live Rendering)은 어떻게 세팅하는가?
@IBDesignable 속성은 특정 뷰에서 인터페이스 빌더를 실시간 갱신시키게 해준다.

2- 동기 & 비동기 작업의 차이가 무엇인가?
동기: 작업이 완료되기 전까지 기다린다.
비동기: 백그라운드에서 작업을 완료하고나면 알림을 받을 수 있다.

3- B-Tree란?
B-Tree는 정렬된 키-값을 제공하는 서치 트리이며 훌륭한 퍼포먼스의 특징을 가진다. 각 노드는 정렬된 그 고유의 요소 배열을 가지고 있고, 그 자식 노드를 위한 또다른 배열을 가지고 있다.

4- NSError 오브젝트는 무엇으로 구성되있는가?
NSError 객체는 도메인, 에러코드, 사용자 정보 딕셔너리의 세가지 부분으로 나뉜다. 도메인은 어떤 카테고리의 에러인지, 어디서부터 온 에러인지 식별하는 문자열이다.

5- 좋아하는 WWDC 영상은 무엇인가?
2015년도의 Protocol-Oriented Programming in Swift이다.
2016년도의 Visual Debuggin with Xcode이다.

6- 바운딩 박스(bounding box)란?
바운딩 박스는 기하학에서 쓰이는 용어인데, 주어진 점의 집합으로 나타내는 가장 작은 단위(넓이나 부피)이다.

7- Objective-C에서는 왜 열거형을 속성에서 strong을 쓰지 않을까?
열거형은 객체가 아니므로 특정 strong이나 weak를 지정할 수 없다.

8- Objective-C에서 synthesize는 무엇인가?
synthesize는 여러분의 프로퍼티를 위해 gettersetter 메소드를 생성해준다.

9- Objective-C에서 dynamic이란 무엇인가?
우리는 NSManagedObject의 자식 클래스를 위한 dynamic을 사용한다. 또한 @dynamic는 델리게이트를 접근자 구현의 역할에 익숙하다.

10- 왜 synchronized를 사용할까?
synchronized는 주어진 시점에서 한 스레드가 블럭안의 코드를 실행시킬 수 있음을 보장한다.

11- strong, weaks, read only, copy의 차이는 무엇인가?
strong는 참조 카운트가 증가될 것이고 그것에 참조는 그 객체의 생명주기를 통해 유지될 수 있음을 의미한다.
weak는 객체에 포인터를 가리키고 있지만 잠조 카운팅을 올리진 않는다. 부모 자식 관계가 만들어질때 종종 사용된다. 부모는 자식에게 strong 참조를 가지지만 자식은 부모에게 weak 참조를 가지게 만든다.
read only, 초기에 속성을 세팅할 수 있지만 변경할 수 없다.
copy는 객체가 생성될때 객체의 값을 복사한다는 의미이다. 또한 변경으로부터 값을 보호한다.

12- 다이나믹 디스패치란(Dynamic Dispatch)?
다이나믹 디스패치는 여러 형태의 오퍼레이션 구현 선택의 처리인데, 이 오퍼레이션은 런타임시 호출하기위한 메소드나 함수이다. 이 의미는 오브젝트 메소드처럼 우리 메소드를 불러내고 싶을 때이다. 그러나 스위프트는 다이나믹 디스패치에 디폴트가 아니다.

13- 코드 커버리지란 무엇인가?
코드 커버리지는 유닛테스트의 측정 값을 측정하기 위한 도구이다.

14- 컴플리션 핸들러란 무엇인가?
컴플리션 핸들러는 우리 앱이 그 작업이 끝났을때 뭔가 API 호출 하게 만들고 싶을때 최고의 편의를 만들어준다. 애플의 dataTaskWithRequest같은 API에서 컴플리션 핸들러를 볼 수 있고 당신 코드에서 꽤 간편하게 사용할 수 있게 해준다.

컴플리션 핸들러는 세가지 인자(NSDate?, NSURLResponse?, NSError?)로 코드를 받는데, 아무것도 반환하지 않는다(Void). 이것은 클로저이다.

15- 디자인에서 유용성 우선순위를 어떻게 세우는가?
유용성을 우선순위 매기기 위해서는 4단계로 디자인 과정을 쪼갠다.
  • 사용자처럼 생각하고 UX를 설계하기
  • 사용자가 밀집된 인구가 아닌 사람들이라는 점을 기억하기
  • 앱을 알릴때 모든 상황에서 유용한지 고려하기
  • 앱을 출시한 이후에서 계속 앱 사용성을 고민하기

16- framebounds의 차이는 무엇인가?
UIViewbounds는 그 고유 좌표 시스템 (0, 0)에 연관된 position (x, y)size (width, height)로 표현되는 사각형이고,
UIViewframe은 그 뷰를 담고있는 부모뷰에 연관된 position (x, y)size (width, height)로 표현되는 사각형이다.

17- Responder Chain이란 무엇인가?
ResponderChain은 받은 이벤트에 응답하기위한 기회를 가진 객체들의 계층을 의미한다.

18- 정규식(Regular expressions)이란 무엇인가?
정규식은 문자열을 통해 어떻게 검색할지 표현하는 특수한 문자열 패턴이다.

19- 연산자 오버로딩(Operator Overloading)이란 무엇인가?
연산자 오버로딩은 이미 있던 연산자의 동작을 바꿀 수 있게 해준다.

20- TVMLKit이란 무엇인가?
TVMLKit은 TVML과 JavaScript, 네이티브 tvOS 앱을 붙여주는 역할을 한다.

21- tvOS의 플랫폼 한계는 무엇인가?
첫째, tvOS는 어떤 종류로더 브라우저를 제공하지 않으며 여러분이 프로그래밍할 수 있는 어떠한 WebKit이나 웹기반 렌더링 엔진도 없다. 이 말은, 여러분의 앱이 웹 링크나 OAuth, 소셜 미디어 사이트같은 어떤것도 링크를 타고 웹브라우저로 갈 수 없다는 의미이다.

두번째, tvOS 앱은 명시적으로 로컬 스토리지를 사용할 수 없다. 제품 출시 시점에 기기들은 32GB나 64GB의 하드 용량을 탑재한다. 그러나 앱은 그 탑재된 스토리지에 직접 쓰기를 할 권한이 없다.

tvOS 앱 번들은 4GB를 초과하지 못한다.

22- 함수란 무엇인가?
함수는 어떤 작업을 수행하기위해 일련의 명령을 함께 모을 수 있게 해준다. 함수를 만들어 놓으면, 코드상에서 계속 사용할 수 있다. 만약 여러분의 코드에서 중복되는 명령을 찾는다면, 함수가 그 반복을 피하는데 답이 되어 줄 것이다.

23- ABI란 무엇인가?
ABI는 외부 라이브러리를 사용하게 되는 앱이라면 중요하다. 프로그램이 특정 라이브러리를 사용하도록 만들어져있고, 나중에 그 라이브러리가 업데이트될때마다 앱을 다시 컴파일 하고 싶지 않을 것이다(그리고 끝 사용자 관점에서, 그 소스를 가지고 있지 않을 것이다). 업데이트된 라이브러리가 같은 ABI를 사용한다면 여러분의 프로그램은 변경할 필요가 없다.



24- 디자인 패턴이 왜 중요한가?
디자인 패턴은 소프트웨어 설계에서 흔히 만나는 문제들에대한 재사용가능한 솔루션이다. 이것은 이해하기 쉽고 재사용하기 쉬운 코드를 짜는데 도움을 주기위한 설계의 템플릿이다. 아래는 아주 일반적인 Cocoa 디자인 패턴들이다.
  • 생성 : 싱글톤
  • 구조 : MVC, Decorator, Adapter, Facade
  • 동작 : 옵져버, Memento

25- 싱글톤 패턴이란 무엇인가?
싱글톤 디자인 패턴은 주어진 클래스에대해 오직 하나의 인스턴스만 존재하도록 보장하고 그 인스턴스를 가리키는 전역 접근을 가진다. 보통 그 싱글톤이 필요한 첫번째 호출에서 싱글톤 인스턴스를 lazy하게 불러와서 생성한다. 

26- Facade 디자인 패턴이란 무엇인가?
Facade 디자인 패턴은 복잡한 서브세스템에 하나의 인터페이스를 제공해준다. 클래스들이나 API들의 집합을 사용자에게 보여주는 것 대신에, 하나로 모인 간단한 API를 보여준다.

27- Decorator 디자인 패턴이란 무엇인가?
데코레이터 디자인 패턴은 그 코드를 수정하지 않고 한 오브젝트에대한 행동자와 책임들을 동적으로 추가한다. 다른 오브젝트로 그 클래스의 동작을 수정하려할때 서브클래스로 만들지 않고 서 가능하게 해준다.

28- 어답터(Adapter) 패턴이란 무엇인가?
어답터는 클래스들의 호환되지 않는 인터페이스 간에 함께 작업할 수 있게 해준다. 오브젝트를 감싸고 그 오브젝트와 소통하기위해 표준 인터페이스를 보여준다.

29- 옵저버 패턴이란 무엇인가?
옵저버 패턴에서 어떤 오브젝트의 상태변화를 다른 오브젝트에게 알려준다.

30- Memento 패턴이란 무엇인가?
Memento 패턴에서는 어딘가 작업을 저장한다. 그뒤에 이 외부화된 상태는 캡슐화 파괴를 하지 않고서 다시 저장될 수 있다; private 데이터는 private로 남는다. 애플에서 Memento 패턴의 구현 중 하나는 Archiving이다.

31- MVC를 설명하시오
  • Models : 도메인 데이터의 역할이나 데이터를 조작하는 레이어를 접근하는 데이터. 'Person' 이나 'PersonDataProvider'를 생각하면 된다.
  • Views : 표현하는 레이어(GUI)의 역할, iOS 환경에서는 접두에 UI로 시작하는 모든 것들이라고 생각하면 된다.
  • Controller/Presenter/ViewModel : Model과 View의 중개인, 보통은 View에서 실행된 사용자 행동에 반응하여 Model을 바꾸고 바뀐 Model로부터 View에 갱신하는 역할을 한다.

32- MVVM을 설명하시오
View와 그 상태의 표현이 독립적인 UIKit. ViewModel은 Model에서 바뀐 것을 부르고, 갱신된 Model로 그 자신을 갱신한다. 그리고 View와 View Model 사이에 바인딩되있으므로 첫번째가 적절하게 갱신된다.

View Model은 실제로 여러분의 Model안에 들어있을 것이고 View에 나타날 정보의 틀을 잡을 수 있다.

33- Objective-C에서 어떤 주석(annotation)을 사용할 수 있을까?
  • _Null_unspecified, 이것은 스위프트스위프트 묵시적 언랩핑된 옵셔널에 연결해준다. 디폴트로 되어있다.
  • _Nonnull, 이 값은 nil이 되면 안될때 정규 참조에 연결해준다.
  • _Nullable, 이 값은 nil이 될 수 있고, 옵셔널로 연결해준다.
  • _Null_resettable, 읽을때, 그러나 재설정한다는 것을 알기위해 이것을 설정할 수 있을때, 이 값은 절때 nil이 될 수 없다. 이것은 프로퍼티에만 적용가능하다.

34- JSON/PLIST의 한계은 무엇인가?
  • 당신의 오브젝트들를 생성하고 디스크에 시리얼라이즈한다(serialize)
  • 이것은 훌륭하나 매우 제한된 유스케이스이다
  • 여러분의 결과물을 필터링하기위한 복잡한 쿼리를 명확하게 사용할 수 없다.
  • 매우 느리다
  • 뭔가 필요할 때마다, 시리얼라이즈나 디시리얼라이즈를 해야한다.
  • 스레드-세이프 하지 않다

35- SQLite의 한계는 무엇인가?
  • 테이블간의 관계를 정의해야한다. 모든 테이블마다 스킴을 정의한다
  • 데이터를 패치하기위해 손수 쿼리를 작성해야한다
  • 데이터를 쿼라한 다음 모델에 매핑 시켜야한다
  • 쿼리는 매우 빠르다

36- Realm의 장점은 무엇인가?
  • 오픈소스 데이터베이스 프레임워크이다
  • 스크래치에서 구현되었다
  • Zero copy object store
  • 빠르다

37- 베터리-효율 위치 추적을 위한 API가 몇개나 있는가?
3개의 api가 있다.
  • 중요한 위치 변화(Significant location changes) : 약 500미터마다 위치가 전송된다.(때로는 1km 이상일 때도 있다)
  • 지역 모니터링(Region monitoring) : 반경이 100m나 그 이상으로의 원형 지역에서 들어가고 나가는 것을 추적한다. 지역 모니터링은 GPS 이후 가장 정확한 API이다.
  • 방문 이벤트(Visit events) : 한 장소(집/사무실)로 들어가고 나오는 방문 이벤트 장소를 모니터링한다.

38- 스위프트스위프트의 주 이점은 무엇인가?
  • 옵셔널타입, 앱의 크레쉬를 방지해준다
  • 내장된 에러 핸들링
  • 클로저
  • 다른 언어에비해 빠르다
  • 타입-세이프 언어
  • 패턴 매칭을 지원한다

39- 스위프트에서 제네릭을 설명하라
제네릭은 특정 데이터타입에 의존하지 않는 코드를 만드는 것이다.

40- 스위프트에서 lazy를 설명하라
프로퍼티에 저장된 lazy로된 초기 값은 이 프로퍼티가 제일 처음 호출되었을때 계산된다. 개발자에게 lazy 프로퍼티가 아주 유용할때가 있다.

41- defer이 무엇인지 설명하라
defer 키워드는 실행이 현재 스코프를 벗어날때 호출될 코드 블럭을 제공해준다.

42- 변수를 어떻게 참조하여 전달할 수 있는가?
변수에는 두가지 타입이 있다는 점부터 언급해야한다. 참조타입과 값타입이다. 이 두 타입의 차이점은, 값타입을 전달할때는 그 데이터의 복사본을 만들어낼것이고, 참조타입의 변수는 그냥 메모리에서 원래 데이터의 포인터를 가리키고 있을 것이다.

43- 왜 higher order 함수를 사용하는 것이 낫나?
파라미터로 함수를 받을 수 있거나 결과로 함수를 반환할 수 있는 함수들을 higher-order 함수라 부른다. 스위프트는 이런 함수들을 CollectionType으로 정의한다.

아주 기본적인 higher order 함수로는 filter가 있다.

44- 동시성(Concurrency)이란 무엇인가?
동시성은 여러분의 프로그램에서 실행 경로를 쪼개는 것이므로 동일한 시간에 실행될 것이다. 일반적인 용어에는 프로세스, 스레드, 멀티스레드, 등이 있다.
  • 프로세스, 실행되고있는 한 앱의 한 인스턴스
  • 스레드. 코드상의 실행 경로
  • 멀티스레드, 동시간에 실행되는 다중 스레드 혹은 다중 경로
  • 동시성, 스케일 조절이 가능한 방법으로 한번에 여러 작업을 실행시키는 것
  • 큐, 큐는 먼저들어간 것이 먼저나온다는 FIFO(First-in, First-out)방식으로 오브젝트를 관리하는 경량의 자료구조
  • 동기 vs 비동기 작업

45- Grand Central Dispatch(GCD)
GCD는 뒤편에서 스레드를 관리하면서 동시적으로 작업을 실행시키는 저수준 API를 제공하는 라이브러리이다. 용어에는 다음이 있다.
  • Dispatch Queues, 디스패치 큐는 FIFO 순서로 작업을 실행시키는 역할을 담당한다.
  • Serial Dispatch Queue, 시리얼 디스패치 큐는 한번에 한 작업만 실행시킨다.
  • Concurrent Dispatch Queue, 컨커런트 디스패치 큐는 시작한 작업이 끝나는것을 기다리지 않고 가능한 많은 작업을 실행시킨다.
  • Main Dispatch Queue, 앱의 메인 스레드에서 작업을 실행할 수있는 전역에서 사용가능한 시리얼 큐

46- Readers-Writers
읽기는 동시에 여러 스레드에서 할 수 있지만 쓰기는 한 스레드에서만 할 수 있다. 이 문제에 대한 해결책은 readers-writers인데, 이것은 이 방법은 동시에 read-only 접근을 허용하고 독점적인 write 접근을 하게 해준다.
아래는 용어들이다.
  • Race Condition(경쟁 상태), 경쟁상태는 공유된 데이터를 2개 이상의 스레드가 접근하거나 동시에 서로 변경하려고 할 때 나타난다
  • Deadlock(데드락), 데드락은 2개 혹은 그 이상의 작업이 다른 작업이 끝나기를 서로 기다리고 있을때 나타난다
  • Readers-Writers problem, 읽기는 동시에 여러 스레드에서 할 수 있지만 쓰기는 한 스레드에서만 할 수 있다
  • Readers-Writer lock, 이런 락은 동시에 공유된 데이터에 read-only 접근은 허용하되 write 작업은 독점적인 접근을 요구한다
  • Dispatch Barrier Block, 디스패치 베리어 블럭은 동시적인 큐와 작업할때 시리얼-스타일(serial-style)의 버틀넥을 만든다

47- NSOperation-NSOperationQueue-NSBlockOperation
NSOperation은 GCD와 비교했을땐 추가적인 오버해드가 있으나, 다양한 작업들 가운데 의존성을 추가할 수 있고, 재사용, 취소, 중지시킬 수 있다.
NSOperationQueue, 이것은 NSOperation들을 만들어서 병렬로 실행시키는 스레드 풀을 제공한다. Operation queue가 GCD의 일부는 아니다.
NSBlockOperation은 하나 혹은 그 이상의 클로저에서 NSOperation을 생성할 수 있게 해준다. NSBlockOperation들은 동시적으로 실행하는 다중 블락을 가질 수 있다.

48- KVC-KVO
KVC는 Key-Value Coding을 의미한다. 이 메커니즘은 개발시점에 프로퍼티 이름을 알아야하는게 아니라, 런타임중에 문자열을 사용하여 한 오브젝트의 프로퍼티들에 접근할 수 있게 해준다. 
KVO는 Key-Value Observind을 의미하고 컨트롤러나 클래스가 프로퍼티 값이 변경되는지 관찰(observe)하게 해준다. KVO에서는 한 오브젝트가 특정 프로퍼티가 변경되는 모든 알림을 달라고 요구할 수 있으며, 그 프로퍼티는 자기자신것도 되고 다른 오브젝트의 프로퍼티도 된다.

49- 스위프트스위프트의 패턴 매칭 기술들을 설명하라
  • 튜플 패턴(Tuple patterns)은 튜플타입에 해당하는 값을 매칭시키는데 사용된다.
  • 타입케스팅 패턴(Type-casting patterns)은 타입을 캐스팅하거나 매칭할 수 있게 해준다.
  • 와일드카드 패턴(Wildcard patterns)은 어떤 종류나 타입의 값을 매칭 혹은 무시한다.
  • 옵셔널 패턴(Optional patterns)은 옵셔널 값을 매칭하는데 사용된다.
  • 열거형 케이스 패턴(Enumeration case patterns)은 존재하는 열거형 타입의 케이스를 메칭한다.
  • 표현식 패턴(Expression patterns)은 주어진 식에대한 주어진 값을 비교할 수 있게 해준다.

50- guard의 장점은 무엇인가?
guard에는 두가지 큰 장점이 있다. 한가지는 다른사람들이 언급해왔던 피라미드 코드를 피한다는 것이다-엄청나게 많은 수의 if let 절은 각 안으로 점점 오른쪽으로 가면서 감싸이게 된다. 다른 이점은 breakreturn을 사용하여 초기에 함수를 빠져나갈 수 있게 한다는 것이다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

제목 : Swift4 Release Process

이 포스팅은 Swift4의 목표, 배포과정, 산정된 스케줄에대해 이야기한다.

Swift4는 2017년 가을에 메이저 배포를 완료할 것으로 보인다. 이 언어에서 바이너리 안정성을 필요로하는 필수의 기능 작업들을 구현하면서, Swift3 코드에대한 소스 안정성을 제공하는 것을 중심으로 돌아가고있다. 이것은 핵심 언어와 표준 라이브러리를 증진시키는 중요한 것을 포함할 것이다. 특히 제네릭 체계와 String 타입의 큰 변화에 대해 말이다. 더 자세한 내용은 Swift Evolution 페이지에서 확인할 수 있다.

소스 호환성
Swift4 컴파일러는 -swift-version 3 모드와 -swift-version 4 모드 두가지를 제공한다.

Swift Version 3 모드
-swift-version 3 모드는 현재 코드의 디폴트이다. 이 모드로 할때 강력한 목표는 다음과 같다. Swift3.1로 만들어진 막대한 소스들을 계속해서 Swift4로 만들게 하는 것이다. 한편, 예외가 있는데, 한번에 받아드리지 못한 코드를 거절하기위해 발생한 버그를 고치는 경우이다. 실제로는 이런 경우가 상대적으로 드물 것으로 보인다.

여러분의 코드가 Swift3.1 컴파일러로는 컴파일 되었는데 Swift4 컴파일러로는 의도치않은 거절을 당한다면 버그리포트를 보자.

Swift Version 4 모드
-swift-version 4 모드는 이 배포에서 새로운 것들을 가능하게 해주고 파괴적인 변화를 가능하게하는 모드이다. 주목할만한 중요한점은 String API의 철저한 정비이다. 핵심은 API의 인간공학의 증진과 그 퍼포먼스 증진에 있다.

이 변화들은 기존의 소스를 바꾸게 만들고 새 API를 사용하기위해 현재 코드를 마이그레이션 해야할 것이다.

코드 마이그레이션 관점에서 보면 그 부담의 크기가 2.3에서 3.0으로 넘길때보다 3.0에서 4로 갈때가 훨씬 작을 것이다.

다른 언어로 코드 합치기 모드
의도했던 설계는, 다중 Swift 타겟으로 한 Xcode 프로젝트처럼, 다중 Swift 모듈을 담은 프로젝트가 모듈의 (타겟) 레벨마다 특정 Swift 언어 모드로 적용할 수 있게 하고, 컴파일된 같은 바이너리 안에서 자유롭게 상호 소통할 수 있게 하는 것이다. 타겟이 같은 컴파일러로 컴파일 되었을 때만 바이너리 레벨에서 이런 상호 소통이 가능하다는 점을 잊지말자.

상호소통이 가능해지면 어떤것이 가능한지 예시들을 보자.
  • Xcode에서 앱 타겟은 Swfit4로 작성하는데(-swift-version 4) 여러 프레임워크는 개별의 Swift3으로 작성된 것을 사용할 수 있다(-swift-version 3).
  • Swift4로 작성된 Swift 패키지(-swift-version 4)는 현재 페키지 소스를 Swift4로 업데이트하지 않고서도 Swift3으로 작성된 패키지인채로 사용할 수 있다.

전체적으로는 이런 규약이 점차 Swift3코드를 Swift4로 마이그레이션 시키도록 해줄 것이다(한번에 한 타겟이나 한 패키지씩).

Swift 배포에서 소스 호환성의 더 자세한 계획을 보고싶다면 Swift-evolution 메일링 리스트의 이 스레드에서 확인할 수 있다.

Swift4의 스넵샷
Swift3.1의 경우처럼, Swift4도 매일 다운받을 수 있는 배포 브랜치의 스넵샷이 있을 것이다. 스넵샷은 연속적인 통합 테스트의 부분으로서 만들어지 질 것이다. 다운로드가능한 스넵샷의 주기는 더 빈번해질 것이다. 테스트가 통과되었다면 스넵샷은 매일 찍힐 것이다.

Swift4로 바꾸기
마지막 브랜치 날짜 전까지 현재 메인으로 개발되는 모든 변경사항들(master 브랜치)은 배포 관리자가 발표한다. 아마 2017년 초여름에 될 것으로 보인다. 그 시점 이후, 정해진 사항만 그 기간에 "만들" 것이고, 중요한 수정사항은 swift-4.0-branch로 갈 것이며, master를 개발하여 다음 배포를 준비할 것이다.

브랜치들
  • master : swift-llvm, swift-clang, swift-lldb를 제외하고 Swift4 개발은 master에서 일어난다. master에서 일어나는 모든 변경사항은 마지막 브랜치 날짜까지 마지막 Swift4 배포의 부분으로 들어갈 것이다. 그 시점에서 master는 다음 배포에대한 개발을 따라간다.
  • swift-4.0-branch : Swift4를 위한 배포 관리는 swift-4.0-branch에서 일어난다. 모든 Swift4의 스넵샷은 이 브랜치로부터 만들어지고, 또한 Swift4는 이 브렌치로부터 GM이 된다.
계획상, master는 마지막 브랜치 날짜 전까지 약 2주에 한번꼴로 swift-4.0-branch에 머지(merge)된다. 2주라는 시기에 master 브랜치와 그 보조의 배포 브랜치 개발 사이의 버퍼 역할을 한다. 변경사항들은 (풀 리퀘스트를 통해)신중하게 골라져서 master의 머지들 사이에 swift-4.0-brach로 들어간다.

이 계획의 주목할만한 예외는 swift-package-manager이다. 이것은 매일 master 브랜치에서 swift-4.0-brach로 머지될 것이다.

Swift4로 바꾸는 철학
  • Swift3.1의 소스 호환성은 -swift-version 3모드에서 가장 높은 우선순위이다.
  • Swift4의 보장으로서는 배포의 핵심 목표를 맞추는 변경사항만이 고려될 것이다.
  • 언어와 API에대한 모든 Swift4의 변화는 Swift Evolution 절차에따라 진행될 것이고, 여기에는 배포에대해 어느 범위안에 무엇이 바뀔지 그 기준들을 적어놓았다.
  • 배포 보장처럼 4회로 변경사항을 풀(pull)하는 기준들이 점점더 엄격해질것이다.

영향을 받는 저장소들
아래 저장소들은 Swift4 배포의 일부분으로서 소스를 따라가기위해 swift-4.0-branch 브랜치를 가질 것이다.
swift-llvmswift-clangswift-lldb 저장소는 이미 master에서 swift-4.0-brach로 나왔으며, 다시 브랜치가 나오진 않을 것임을 기억하자.

배포 관리자
배포의 전반적인 관리는 아래 사람들이 개별적으로 감독한다. 배포의 의견으 모아지고, 제한하는 사람(strictoer)이 변경사항을 조정하는게 Swift4 배포에 영향을 줄 때, 이 사람들이 발표할것이다.

배포 관리 과정에 대해 궁금한점이 있다면 마음편하게 swift-dev로 메일을 보내거나 Ted Kremenek로 직접 메일을 보내달라.

릴리즈 브랜치를 위한 풀리퀘스트
릴리즈 브랜치에서 적용시킬 변경사항을 담은 모든 풀리퀘스트는 아래 정보를 담아야한다.
  • 설명(Explanation) : 고쳐진 이슈나 개선된 것에대한 설명. 간단해도 되지만 명확해야한다.
  • 범위(Scope) : 이 변경사항이 주는 영향/중요성의 판단. 예를들어 이 변경사항은 소스를 고쳐야하는 언어 변경사항이다 등..
  • SR 이슈 : SR은 bugs.swift.org에서 이슈/개선점을 고치는/구별하는 변경사항일때이다.
  • 위험성(Risk) : 이 변경사항이 배포에 줄 수 있는 특정 위험성은 무엇인가?
  • 테스트 : 이 변경사항의 영향을 검증하기위해 어떤 특정 테스트를 하였고 나중에 어떤 것이 더 필요한가?
영향을 줄 수 있는 컴포넌트를위해 한명 혹은 그 이상의 코드 소유자들이 이 변경사항을 검토해야한다. 기술적 검토는 코드 소유자로부터 위임받거나, 아니면 적절하거나 유용하다고 간주되게 요청될 수 있다.

swift-4.0-brach로 가는 모든 변경사항(master에서 자동으로 머지된 바깥의 변경사항들)은 반드시 해당 배포 관리자가 승인한 풀리퀘스트를 거처야한다.



    WRITTEN BY
    tucan.dev
    개인 iOS 개발, tucan9389

    ,

    Swift는 escaping 클로저와 non-escaping 클로저에 차이를 두고 있다. escaping 클로저는 한번 호출되고나면 리턴값으로 클로저를 반환하는 함수이다. 클로저는 인자로 받은 함수 스코프를 escape한다.

    클로저를 escape 하는 것은 종종 아래 예시처럼 비동기 컨트롤 플로우와 연관되있다. 
    • 함수가 백그라운드 작업을 시작하고 즉시 리턴하면, 완료 핸들러를 통해 백그라운드 작업의 결과를 알린다.
    • 뷰 클래스가 버튼 탭 이벤트를 다르기위해 프로퍼티에 클로저를 저장해둔다. 이 클래스는 사용자가 버튼을 탭 할때마다 클로저를 호출한다. 클로저 프로퍼티는 세터를 escape한다.
    • 여러분은 DispatchQueue.async를 사용하여 디스패치 큐에서 비동기 실행을 위한 작업을 스케줄링한다. 이 테스크 클로저는 비동기에 호출된 이후에도 소멸되지 않은 채 살아있다.
    DispatchQueue.sync와 대조되는데, 이것은 리턴되기 전에 테스크 클로저의 실행이 끝나기 전까지 기다린다. 이 클로저는 절때 escape하지 않는다. 표준 라이브러리에 map, 다린 일반적인 sequence, 그리고 collection 알고리즘에도 동일하다.

    escaping 클로저와 non-escaping 클로저의 차이가 왜 중요할까?
    간단히 말해 메모리관리 때문이다. 클로저가 붙잡고있는 모든 오브젝트는 강참조로 들고 있으며, 클로저 안에서 self의 프로퍼티나 self의 메소드에 접근하려 한다면 이 모든것들이 묵시적으로 self 파라미터를 다루기 때문에 self까지 포함하여 들고 있는다.

    이러한 방식은 굉장히 참조 사이클(reference cycle)을 마주치기 쉬운데, 이것이 왜 컴파일러가 클로저 안에서 명시적으로 self를 쓰게 만드는지에대한 이유이다. 명시적으로 쓰게 만듦으로서 당신에게 잠재적인 참조 사이클에대해 생각해볼 수 있게 해주고, 붙잡고 있는 항목들을 이용해 손수 해결할 수 있게 해준다.

    그러나 non-escaping 클로저로는 참조 사이클을 만드는게 불가능하다. 클로저는 함수 리턴시점까지 붙잡아둔 모든 오브젝트를 릴리즈(release) 시킬 것이라는 것을 컴파일러가 보장한다. 이러한 이유로 escaping 클로저에서만 명시적으로 self를 사용하여 참조할 것을 요구한다. 이 점이 non-escaping 클로저 사용을 더 확실히 즐겁게 해준다.

    non-escaping 클로저의 또다른 장점은 컴파일러가 더 적극적으로 퍼포먼스 최적화를 수행할 수 있다는 점이다. 예를들어 클로저의 라이프타임을 알고 있을때 몇몇 리테인(retain)과 릴리즈(release) 호출은 생략할 수 있다. 또, non-escaping 클로저라면 클로저의 컨텍스트를 위한 메모리가 힙이 아닌 스택에 담아둘 수 있다.(현재 Swift 컴파일러가 이러한 최적화를 시키는지는 잘 모르지만 2016년3월 버그리포팅에서 그렇게 하지말자고 제안이 들어왔었다)

    디폴트로 클로저는 non-escaping이다.
    Swift3부터 non-escaping 클로저가 디폴트이다. 만약 클로저 파라미터에 escape 시키고 싶으면 그 타입에다가 @escaping 지시자를 써야한다. 예를들어 DispatchQueue.async (escaping)와 DispatchQueue.sync (non-escaping) 선언이 있다.

    Swift3 전까지는 좀 다른 방식으로 동작했었는데, escaping이 디폴트이고 오버라이드에 @nonescape를 추가할 수 있었다. 이러한 새로운 동작은 디폴트에의해 더 안전해진다는 면에서 더 좋은데, 이제 함수 인자는 반드시 참조 사이클의 잠재성이 있다는 것을 명시적으로 표시해주어야한다. 따라서 @escaping 지시자는 이 기능을 사용하는 개발자에게 경고를 해주는 역할을 한다.

    ...그러나 오직 즉석 함수 파라미터(immediate function parameters)로서
    디폴트에의한 non-escaping 규칙에 대해 주목할 것이 있다. 이는 직접 함수 매개 변수 위치의 클로저에만 적용된다. 즉, 함수 타입을 가지는 모든 함수 인자에 적용된다. 다른 모든 클로저는 escaping하고 있다.

    직접 파라미터 위치가 무슨 뜻일까?
    예제를 한번 보자. 가장 간단한 예제로는 map이 있다. 이 함수는 직접 클로저 파라미터를 받는다. 우리가 보았듯 클로저는 non-escaping이다. (여기서 실제 map의 표시를 말하려는게 아니므로, 그것들을 조금 생략하겠다)

    함수 타입의 변수들은 항상 escaping이다.
    반대로, 변수나 프로퍼티가 함수 타입을 가질 수 있다. 이때는 명시적인 지시 없이 자동으로 escaping이 된다.(사실은 @escaping을 넣으면 에러가 뜬다) 이렇게 이해할 수 있는데, 변수에 값을 할당하면 묵시적으로 값을 변수의 범위로 escape할 수 있기 때문이다. 이것은 non-escaping 클로저로 허가될 수 없다. 그러나 드물게 지시되지 않는 함수는 파라미터에서의 의미가 아닌 다른 곳에서는 다른 의미를 가지기 때문에 혼란스러울 수 있다.

    옵셔널 클로저는 항상 escaping이다.
    더욱 놀라운 점은, 파라미터로 쓰이지만 다른 타입(튜플이나 enum case, 옵셔널 같은)으로 감쌓여진 클로저들 또한 escaping이라는 것이다. 이 경우에 클로저는 더이상 직접 파라미터가 아니므로 자동으로 escaping된다. 그 결과 Swift3에서는 파라미터가 옵셔널이면서 동시에 non-escaping한 곳에 함수인자로 받는 함수를 만들지 못한다. 아래의 다소 인위적인 예제를 생각해보자. transform 함수는 정수 n과 옵셔널 변환 함수인 f를 받아 f(n)를 반환하거나 f가 nil이면 n을 반환한다.

    여기서 ( (Int) -> Int )?가 Optional<(Int) -> Int>의 축약이기 때문에 함수 f는 escaping이며, 따라서 함수 타입은 직접 파라미터 위치에 있지 않다. 이 결과는 우리가 바라는 것이 아닌데, 여기서 f가 non-escaping 될 수 없는 이유가 없기 때문이다.

    옵셔널 파라미터를 디폴트 구현으로 대체하자.
    Swift 팀은 이 한계를 알고있고, 미래의 배포에서 고치기로 계획했다. 그전까지 우리가 이것을 알고 있어야한다. 현재 강제로 옵셔널 클로저를 non-escaping할 방법은 없지만, 많은 경우에 클로저에다 디폴트 값을 제공하여 옵셔널 인자를 피할 수 있을 것이다. 우리 예제에서는 디폴트 값이 항등 함수(identity function)이고, 이 함수는 간단하게 인자를 바꾸지않고 그대로 반환한다.

    옵셔널과 non-escaping 변형을 제공하기 위해 오버로드를 사용하자
    디폴트 값을 제공하기 힘든 경우라면, Michael Ilseman이 오버로드를 사용할 것을 제안했다. 여러분은 함수의 두기자 변형을 만드는데, 하나는 옵셔널(escaping) 함수 파라미터를 받고, 하나는 non-옵셔널, non-escaping 파라미터를 받는다.

    어떤 함수가 호출되었는지 설명하기 위해 print 상태를 추가했다. 여러 인자로 이 함수를 테스트해보자. 당연하게도 nil을 보내면, 그 인풋과 일치하는 것이 하나밖에 없으므로 타입 체커가 첫번째 오버로드를 선택한다.
    동일하게, 옵셔널 함수 타입을 가지고 있는 변수를 보낸다.
    그 변수가 non-옵셔널 타입일지라도, Swift는 여전히 첫번째 오버로드를 선택할 것이다. 그 이유는 변수에 저장된 함수는 자동으로 escaping되고, 따라서 non-escaping 인자를 예상한 두번째 오버로드와는 일치하지 않는다.

    그러나 클로저 표현식을 보낼때 이것은 바뀐다. 즉 함수 리터럴이 이 자리에 있을때 말이다. 이제 두번째 오버로드 non-escaping이 선택된다.

    리터럴 클로저 표현식으로 higher-order 함수를 호출하는 것은 굉장히 일반적이므로, 대부분의 경우 선택적으로 여전히 nil을 보낼 수 있게 해줌으로서 여러분을 즐거운 길(참조 사이클을 생각할 필요 없는 non-escaping)로 안내해줄것이다. 이런 방식으로 한다면 왜 두가지 오버로드가 필요한지 증명할 수 있을 것이다.

    타입에일리어스(typealiases)는 항상 escaping이다.

    마지막으로 한가지 알고 있어야 하는 것은 Swift3에서는 타입에일리어스에 escaping 혹은 non-escaping 지시자를 넣을 수 없다는 것이다. 함수 선언에서 함수 타입을 위해 타입에일리어스를 사용한다면 그 파라미터는 항상 escaping으로 생각될 것이다. 이 버그 수정은 이미 마스터 브런치에서 이루어졌고, 다음 배포에 적용될 수 있을 것이다. 



    WRITTEN BY
    tucan.dev
    개인 iOS 개발, tucan9389

    ,

    프로그래밍 패러다임 관점에서나 탄생 이유의 관점에서나 리액티브(Reactive)를 이해하기 위해서는 현재 개발자와 회사가 직면한 문제들과 10년전의 문제를 비교하여 생각해보아야한다.

    개발자와 회사에 바뀐 두가지는 아래와 같다.
    • 하드웨어의 발전
    • 인터넷

    "모닥불 주위에 모여 옛날일을 이야기하는 것"은 일부에의해 대화의 수준이 낮아질 수 있음으로 고려해야하는 반면, 우리는 모든 개발자가 직면한 문제를 고심하기 위해 직종의 역사를 탐구해볼 필요가 있다.

    왜 이것들이 이제 달라졌을까
    10년간 컴퓨팅 연구 끝에 발견한 의미있는 지식이 하나 있다. 리액티브 프로그래밍은 소프트웨어의 새 세대를 만들기 위한 시도이다.

    1999년
    1999년, 내가 캐나다 임패리얼 상업은행에 있고 처음 자바를 배우기 시작할 무렵이다.
    • 인터넷은 2.8억 유저에 달성했다.
    • JSEe는 여전히 썬(Sun MicroSystems)사의 꿈으로서 가슴속에 있는 상태였다.
    • 온라인 뱅킹이 5년정도 된 유아기 시점이었다.
    1999년으로 돌아가면 나는 동시성에 대한 문제에 직면하고 있었다. 그 해결책은 스레드와 락(lock)에 관련이 있었으며, 유경험자의 개발자까지도 그 문제를 해결하기 힘들어하는 상황이었다. 자바의 특징은 "한번 작성하면, 어디서든 실행할 수 있다"인데, 여기서 "어디서든"은 JVM이 설치되어있는 OS에 한에서 "어디에든"이지, 클라우드나 IoT 세대를 위해 설계한 다른 동시접속의 개념이 아니다.

    2005년
    2005년이 오래전은 아니지만 컴퓨팅쪽과 인터넷쪽이 크게 바뀌었다. J2EE, SOA, XML이 인기를 끌었고 루비온레일즈가 J2EE의 고통받는 컨테이너 기반 개발 문제를 해결하기 위해 탄생하였다.

    이때의 인테넷에는
    • 1억유저가 있었다.
    • 페이스북이 550만 유저를 가지고 있었다.
    • 유튜브가 탄생했다.(2005년 2월)
    • 트위터가 아직 없었다.(2006년)
    • Netflix가 비디오 스트리밍을 소개했다.(2007년)

    2014년
    이 글을 쓰고 있는 시점인 2014년은 Internet Live Stats에 의하면 약 2,950,000,000(29억 5천만)정도의 인터넷 유저가 있다고 한다. 중국이 혼자서 6억 4천만 인터넷 유저를 보유하고 미국이 2억 8천만을 보유하고 있었다.

    오늘날 가장 인기있는 웹사이트이다.
    • 페이스북—13억 유저
    • 트위터—2억 7천 유저

    시간이 흐르면서 한 웹사이트의 트래픽이 지난 20년전의 인터넷 전체 트래픽보다 많다.

    1999년부터 2015년까지 인터넷, 페이스북, 트위터 유저 수1999년부터 2015년까지 인터넷, 페이스북, 트위터 유저 수



    우리는 점점 확장과 예측에 관한 이슈가 중요해지고, 우리의 삶에서의 소프트웨어가 중요해짐을 쉽게 확인할 수 있다. 또한 과거의 패러다임이 현재까지 이어질 수는 없을 것이고, 분명 미래까지도 이어지기 힘들 것이다.

    4가지 리액티브 요소
    리액티브 앱은 4가지 요소로 구성되있다.

    • 반응성(responsive)있는 앱이 그 목표이다.
    • 반응성있는 앱은 확장가능(scalable)하고 탄력(resilient)있다. 반응성은 확장성과 탄력성 없이 불가능하다.
    • 메시지-주도(message-driven) 구조는 확장가능함과 탄력있음을 근간으로 하고 궁극적으로 반응성있는 시스템을 기반으로 한다.

    반응성(Responsive)
    "앱이 반응성 있다"는 것이 어떤 의미일까?

    반응성있는 시스템은 시종일관으로 분명한 유저 경험을 보장하기위해 좋은 상황이나 나쁜  상황이나 상관없이 모든 유저에게 즉각 반응하도록 하는 것이다.

    외부 시스템의 실패나 트래픽 급증과 같은 다양한 상황에서의 재빠른 처리분명한 사용자 경험탄력성확장성이라는 두 성질에 의존한다. 메시지-주도 구조는 반응성 있는 시스템을 위한 전반적인 근간을 제공해준다.

    왜 반응성 있는 것이 메시지-주도 구조에 중요할까?

    세상은 비동기적이다. 당신이 한 포트의 커피를 끓이는데 크림과 설탕이 없음을 깨닭았을때의 그 예시가 있다.

    아래는 그 한가지 방법이다.
    • 한 포트의 커피를 끓이기 시작한다.
    • 커피가 끓는동안 가게에 간다.
    • 크림과 설탕을 산다.
    • 집으로 돌아온다.
    • 즉시 커피를 마신다.
    • 여유를 즐기면 된다.

    또 다른 방법이다.
    • 가게에 간다.
    • 크림과 설탕을 산다.
    • 집으로 돌아온다.
    • 커피를 끓을 때까지 시계를 보면서 기다린다.
    • 카페인 금단현상을 겪는다.
    • 젠장

    여러분이 볼 수 있듯, 메시지-주도 구조는 당신을 시공간으로부터 분리된 비동기 바운더리를 가능하게한다. 우리는 남은 이 포스트에서 비동기적 바운더리 개념에 대해 이야기해볼 것이다.

    왈마트 캐나다(Wlamart Canada)에서의 일관성
    Typesafe에 들어가기 전에, 나는 왈바트 캐나다의 플랫폼을 만든 Play and Scala 팀의 기술 리더였다.

    우리의 목표는 분명한 사용자 경험의 일관성을 만드는 것이었다. 다음 것들에 관계없이 말이다.
    • 데스크탑, 테블릿, 모바일 기기등 어떤 기기에서도 walmart.ca를 서핑할 수 있다.
    • 현재 피크 트래픽이 튀어오르든 유지되든 관계없어야 한다.
    • 전체 데이터 센터의 손실과 같이 주요 기능이 실패해도 관계없어야 한다.

    응답 시관과 전반적인 사용자 경험은 위 시나리오와 관계없이 일관성있다. 일관성은 당신의 웹사이트를 전달하는데 근본적으로 중요하며 오늘날 웹사이트는 당신의 브렌드임을 생각해야한다. 좋지않은 사용자 경험은 실제 상점이 아니라 온라인에서 일어나기 때문에 쉽게 잊어지거나 무시되지 않는다.

    Glit에서의 반응성있는 소매(retail)
    전자상거래 도매인에서의 일관성은 우연에의해 일어나지 않는다. Glit의 경우, 매일 저녁에 하루 세일을 공지하는데, 그 때 트래픽이 급증하는 플래시 스케일 사이트이다. 플래시 스케일 사이트의 사용자 경험에대해 이야기해보자. 만약 당신이 오전 11:58에 한번 Glit를 접속하고 오후 12:01에 한번 더 접속한다면 Glit는 일관성있게 정해진 응답 경험을 제공하고 이것을 리액티브를 사용하여 시행하였다. 스칼라 기반의 마이크로 서비스 구조로 마이그래이션한 Glit를 더 배우고 싶다면 interview with Eric Bowman of Gilt 여기를 보아라.

    탄력있는(Resilient)
    많은 앱들이 이상적 환경만을 고려하면서 설계, 개발하지만 사실 이상적이지 않을때도 많다. 이것은 매일 주요앱 기능이 실패하는 다른 보고서를 받거나, 해커에의해 서비스 멈춤, 데이터 손실, 지속적인 손상을 초례하는 다른 co-ordinated breach 시스템이 있을 수 있다.

    탄력있는 시스템은 이상적이지 않는 상황에서도 반응성을 보장하기 때문에 바람직한 설계 요소와 구조 요소를 사용한다.

    자바와  JVM은 다중 OS에서 한 앱을 문제없이 실행시키는 것에 대한 것이었다면, 201x년대의 상호연결된 앱은 앱단의 구성, 연결성, 보안성에 대한 것이다.

    이제 한 앱은 웹사이트나 다른 네트워크 규약을 통해 통홥되어 여러 앱으로 구성되어있다. 오늘날 하나의 앱은 신뢰된 방화벽을 가진 바깥은 외부 서비스들에(10개, 20개, 혹은 더 많이) 의존하며 만든다.

    이 복잡한 통합을 고려하면 얼마나 많은 개발자가 필요할까?

    • 모든 외부 의존성을 분석하고 모델링하는 사람
    • 통합된 각 서비스의 이상적인 응답 시간을 문서로 만들고 피크일때나 아닐때 모두 퍼포먼스 테스트가 초창기 기대와 일치하는지 확인하기위해 관리하는 사람
    • 모든 퍼포먼스, 실패, 핵심앱 로직으로 포함되는 다른 비기능적인 요구사항을 문서화하는 사람
    • 각 서비스의 모든 실패 시나리오를 다시 분석하고 테스트하는 사람
    • 외부 의존성의 보안성을 분석하고, 외부 시스템과 통합했을때 새로운 취약점이 있는지 알아내는 사람

    탄력성은 가장 정교한 앱의 가장 약한 연결 중 하나이지만, 추가적으로 탄력있어야하는 것은 곧 끝난다.(원문: Resiliency is one of the weakest links of even the most sophisticated application, but resiliency as an afterthought will soon end.) 현대의 앱들은 이상적인 상황 보다는 다양한 현실세계에서 반응성을 유지해야하기 때문에, 앱의 핵심(core)이 반드시 탄력적이어야한다. 퍼포먼스, 내구성, 보안성은 모든 면에서 말이다. 여러분의 앱은 단지 몇 부분이 아닌 모든 부분에 걸쳐 탄력적이어야한다.

    메시지-주도 탄력성
    메시지-주도 핵심에서 가장 아름다운 제작방법은 여러분이 자연스럽게 작업에 필요한 것들을 한조각 한조각 얻어내는 것이다.

    고립(Isolation)은 시스템의 자체 회복을 위해 필요하다. 잘 고립되있을때, 우리는 실패의 위험이나 퍼포먼스 특징, CPU와 메모리 사용 등과 같은 요인에 기반하여 여러 타입으로 나눌 수 있다.

    정확한 위치는 마치 같은 VM에서 동작하게 한 것 처럼 서로다른 노드위에서 서로다른 프로세스들이 상호소통할 수 있게 해준다.

    특정 목적용 에러 채널은 단지 에러 신호를 호출자에게 던저버리는게 아니라 다른 우리가 원하는 곳으로 보낼 수 있다. 

    이러한 사실은 우리 앱에서 확고하게 에러 핸들링을 구체화하고 결함에 내성을 만들어준다. 이것은 아카의 감독 계층(Akka's supervisor hierarchies)처럼 구현함으로서 입증되었다.

    조각(block)을 만드는 핵심은 이상적인 환경 뿐만 아니라 좋지않은 환경에서도 메시지-주도 구조가 탄력성에 기여하는 것에의해 제공하고 다음으로 반응성에 기여한다.

    44억 달러의 탄력성 실수
    2012년에 를 생각해보자. 소프트웨어가 향상되는동안 통합된 앱 방식은 점점 인기있고(fired up) 거래 규모가 점점 커지기 시작했다.

    다음은 45분동안 일어나는 악몽같은 시나리오였다.

    Knight의 자동교환 시스템이 잘못 거래하여 나스닥(NASDAQ)을 침수시켰고, 10억달러가치를 의도치않은 회사에 놓았다. 이러한 사고는 다시 반환(reverse)하는데 회사에 44억달러의 비용이 들게 되었다. 나스닥이 침수되고 거래의 범람을 고치는 동안 나스닥은 Knight 돕는 것을 중단했다. Knight의 주식은 하루만에 63%나 떨어졌으며 그들은 가까스로 살아남았고, 일부를 회복한 후에 투자자에의해 다음 인계를하고 살아갈 수 있었다. 

    그때 Knight의 시스템은 동작했었지만 탄력성이 없었다. 탄력성이 없는 Knight 시스템은 문제를 확장시키는데 한 몫을 하였다. Knight는 최악의 버그가 발생했을때 그들의 시스템을 끌 수 있는 스위치(kill-switch) 매커니즘도 없는 상태였으므로, 나쁜 환경에서 그들의 자동 거래시스템이 45분만에 회사의 모든 주요 자산을 고갈시켜버린 것이다.

    이것은 이상적인 환경을 위한 설계 정의이자 개발 정의였다. 이제 소프트웨어는 우리 개인 삶이나 회사에서 핵심 요소이다. 예측되지 못한 나쁜 상황의 시나리오 된다면 굉장히 많은 비용을 감당해야할 수 있다.

    확장가능한(Scalable)
    일관성있는 반응성 앱을 만들때 탄력성과 확장성을 잘 이용해야한다.

    확장 가능한 시스템은 다양한 요구량의 상황(various load conditions)에서도 반응성을 보장하기 때문에 그에 맞춰 쉽게 업그레이드 시킬 수 있다.

    온라인에서 물건을 팔아본 사람이라면 물건을 최대로 많이 팔때 가장 큰 트래픽이 생긴다는 사실을 알 것이다. 대부분의 경우(사이버상 공격을 제외하고) 트래픽이 폭발하는 것은 당신이 뭔가 잘하고 있을 때이다. 트래픽이 치솟았을대 사람들은 당신에게 돈을 주고 싶어하고 있는 것이다.

    그럼 어떻게 치솟는(혹은 꾸준히 증가하는) 트래픽을 다룰까?

    첫째로 당신의 패러다임을 먼저 고른다. 둘째로 그 패러다임을 구현할 수 있는 언어와 툴킷을 정한다. 많은 개발자가 종종 너무 가볍게 언어와 프레임워크를 선택한다. 한번 툴을 선택하면 그것을 다시 바꾸기 쉽지 않으므로 당신은 주요 투자가와 함께 그 결정을 내려야한다. 만약 여러분이 기술적인 선택 결정을 원칙과 분석에 기반하여 하고 있었다면 굉장히 잘하고 있는 것이다.

    동시성을 위한 스레드-기반 제한
    기술적인 선택에서 가장 중요한 것중 하나는 동시성 모델 프레임워크이다. 고수준에서 두개의 서로다른 동시성 모델이 있을 수 있다.
    • 전통적인 스레드-기반 동시성으로 콜스택과 공유 메모리를 기반으로 한다.
    • 메시지-주도 동시성

    레일즈와같은 몇 인기있는 MVC 프레임워크는 스레드 기반이다. 이 프레임워크의 전형적인 특징은 아래와 같다.
    • 공유 가변 상태(Shared mutable state)
    • 요청당 한 스래드(A thread per request)
    • 가변 상태에 동시 접근(Concurrent access to mutable state)—이것(변수나 객체 인스턴스)은 또다른 복잡한 동기화 방법이나 락(lock)으로 관리한다.

    루비와 같은 인터프리트 언어로 다이나믹 타입의 특징을 합치면 여러분은 쉽게 퍼포먼스와 확장성의 상한선에 도달할 수 있을 것이다( Combine those traits with a dynamically typed, interpreted language like Ruby and you can quickly reach the upper bounds of performance and scalability). 이것이 어떠한 스크립트 언어라도 그 본질은 같다고 말할 수 있을 것이다.

    Out 혹은 Up?
    앱 확장을 좀 다른 방법으로 생각해보자.

    스케일업(Scale up)은 단일 CPU/서버의 리소스를 최대화 하는 것인데, 파워풀하고 희귀하고 값비싼 그런 컴퓨터를 종종 사야한다.

    스케일아웃(Scale out)은 여러 저렴한 하드웨어를 연결하여 컴퓨테이션을 제공하는 것인데, 비용면에서 효과적이다. 그러나 당신의 시스템이 시공간 개념에 기반하였다면 매우 어려울지도 모르겠다. 위에서도 이야기했듯 메시지-주도 구조는 시공간으로부터 분리하기위해 필요한 비동기 바운더리를 제공하며, 필요에따라 스케일아웃 할 수 있는 유연성(elasticity)을 제공한다. 반면 스케일업은 이미 가지고있는 자원의 효율을 높히는 것이고, 유연성은 당신의 시스템이 바뀌기 원하는대로 새 자원을 추가할 수 있음에 관한 것이다. 필요에따라 스케일아웃 할 수 있는 능력은 리액티브 앱의 궁극적인 확장성의 목표이다.

    공유 가변 상태, 스레드, 락 기반의 앱을 스케일아웃하는게 어렵기 때문에 리액티브 앱들을 스레드 기반으로 만드는것은 어려운 일이다. 개발자들은 한 머신에서 멀티 코어의 이점을 활용해야 할 뿐만 아니라, 특정 시점의 개발자들은 머신의 클러스터를 활용해야 한다. 그게 불가능할지라도, 공유 가변 상태 또한 스케일업하기 어렵게 만든다. 한번이라도 두개의 스레드에서 공유 가변 상태를 다뤄본 사람이라면, 스레드 세이프를 보장하는 과정이 얼마나 어려운지, 스레드 세이프를 위해 과한 작업을 하게되는 실적 패널티가 얼마나 큰지 이해할 수 있을 것이다.

    메시지-주도(Message-driven)
    메시지-주도 구조는 리액티브 앱의 근간이다. 메시지 주도 앱은 이벤트 주도 이거나 행위자 기반일 것이고, 혹은 이 둘 모두를 합친 것일 것이다.

    이벤트 주도 시스템은 0개 혹은 그 이상의 Observer에의해 관찰된 이벤트 기반이다. 이것은 명령형 프로그래밍과는 좀 다른데, 호출자가 부른 루틴으로부터 응답을 블락된 상태로 기다릴 필요가 없기 때문이다. 이벤트는 바로 특정 장소를 지정하는게 아니라 나중에 일어날 어떤 결과를 지켜보고 있는 것이다.

    행위자-기반 동시성은 메시지-전달 아키텍처의 확장이며 메시지는 수신자에게 전달된다. 메시지는 스레드 경계를 넘거나 실제 다른 서버의 다른 행위자의 메일함으로 전달 될 수 있다. 행위자가 네트워크를 통해 배포 될 수 있지만 여전히 동일한 JVM을 공유하는 것처럼 서로 통신 할 수 있으므로 요구에 맞게 스케일-아웃 할 수 있다.

    메시지와 이벤트의 차이점은 메시지는 전송되는 것이고 이벤트는 일어나는 것이다. 메시지는 명확한 도착지가 있지만 이벤트는 0혹은 그 이상(0-N)의 Observer에의해 관찰되고 있을 것이다.

    이벤트-주도와 행위자-기반 동시성에 대해 좀 더 세부적으로 들어가보자.

    이벤트-주도 동시성
    일반적인 앱들은 명령형 스타일(오퍼레이션 순서)로 개발되고 콜스택을 기반으로 개발한다. 콜스택의 주 기능은 프로세스에서 호출자가 블럭되고 리턴값과 한께 호출자에게 컨트롤을 돌려주는 동안, 주어진 루틴에서 호출자를 계속 쫓고, 호출된 루틴을 실행하는 것이다.

    겉으로 보았을 땐 이벤트 주도 앱이 콜스택에 맞추는게 아니라 이벤트 트리거에 맞춘다. 이벤트는 0개 혹은 그 이상의 Observer에의해 지켜보고 있는 큐에 메시지로 인코딩 되어 있을 것이다. 명령형 스타일과 비교하여 이벤트-주도의 큰 차이점은 응답을 기다리는 동안 호출자가 한 스레드 위에서 블락되거나 멈추지 않는다. 이벤트 루프 자체는 단일 스레드 일 수 있지만, (단일 스레드 이기도 한)스레드 된 이벤트 루프가 들어오는 요청을 처리 할 수 있도록 허용하면서 호출 된 루틴이 업무를 수행하는 동안 (잠재적으로 IO 자체를 차단하면서) 동시성은 여전히 달성된다. 프로세스가 완전히 끝나지 않는한 요청을 블락시키는 대신에 호출자의 id가 요청 메시지의 바디와 함께 전달되므로 깨어있는 루틴이 이것을 선택하면 호출자는 응답과 함께 콜백될 수 있다. 

    이벤트 주도 구조를 선택하는 이유는 콜백지옥(링크)이라는 것에 괴로워할 수 있기 때문이다. 콜백지옥은 메시지를 받는 놈이 정해져 있는 것이 아니라 익명의 콜백이기 때문에 발생한다. 콜백지옥을 해결하는 일반적인 방법은 이러한 문제가 생기는 이유를 잊고 코드에 표현된 이벤트 순서대로 디버깅하기 어려운것도 생각하지 않으면서 온전히 구문(aka, the Pyramid of Doom) 형태에만 초점을 맞춘다.

    행위자-기반 동시성
    행위자-기반 앱은 여러 행위자 사이에서 비동기 메시지를 보낸다.

    행위자는 아래 속성들을 갖는다.
    • 메시지를 받기 위한 메일 박스
    • 각 타입별로 메시지를 어떻게 받는지 결정하기 위해 패턴매칭의 행위자 로직
    • 요청 사이에 컨텍스트를 저장하기 위한 고립된 상태
    이벤트-주도 동시성처럼 행위자 기반 동시성에서는 콜스택은 피하고 가벼운 메시지 전달을 지향한다. 행위자는 메시지를 밖으로든 자기자신에게든 보낼 수 있다. 한 행위자는 그 큐의 첫번째 요청을 먼저 제공한 뒤에 처리가 긴 요청을 처리하기 위해 메시지를 자기자신에게 보낼 수도 있다. 행위자-기반 동시성의 큰 장점은 이벤트-주도 구조에서 얻은 장점을 얻을 수 있다는 점이다. 네트워크 경계를 통해 컴퓨테이션을 스케일-아웃하기도 쉽고, 행위자에게 직접 메시지를 전해주기 때문에 콜백 지옥을 피할 수도 있다. 이러한 강력한 컨셉은 설계, 구현, 유지보수하기 쉬우면서 확장성이 있는 앱을 만들 수 있게 해준다. 시공간에대해 생각하거나 깊게 감쌓인 콜백들에대해 생각하는것보다, 행위자 사이에 메시지 흐름이 어떻게 되는지만 고민하는게 더 낫다.

    또 다른 주요 장점은 요소들끼리 느슨하게 연결된다는 점이다. 호출자는 응답을 기다리기 위해 스레드를 멈추지 않으므로 빨리 다른 일을 할 수 있다. 호출자에의해 켭슐화되있는 현재 루틴은 필요에따라 호출자를 다시 호출하면 된다. 이것은 다양한 가능성을 열어준다. 콜백 스택이 한 메모리 공간에 있기 위해 앱을 묶어버리지 않으며 행위자 모델은 프로그래밍적인 일보다 구성에 관련된 일을 추상화하여 만듦으로 여러 머신을 통해 루틴을 분배할 수 있다.

    Akka는 행위자-주도 툴킷이고 타입 세이프 리액티브 플랫폼의 일부로서 JVM에서 높은 동시성, 분배, 실패에 대한 내성을 가진 행위자-기반 앱을 만들기 위한 런타임이다. Akka는 탄력을 위한 관리계층이나 확장성을 위한 일 분배와 같은 리액티브 앱 개발에 필요한 멋진 기능들을 탑재하고 있다. Akka에대해 더 깊게 보고싶으면 Let it Crash blog를 확인해보길 바란다.

    또한 이 세션 일부의 자료로 사용된 Benjamin Erb’s Diploma Thesis의 글(Concurrent Programming for Scalable Web Architectures)을 읽어보기를 강력 추천한다.

    결론
    위의 모든 것들이 오늘날 앱 개발에 흠집을 내고, 왜 리액티브 프로그래밍이 단지 또다른 트렌드가 아닌지 이야기하며, 그러나 왜 현대 소프트웨어 개발자들이 배워야하는 패러다임인지도 이야기했다. 여러분이 선택하는 언어나 툴킷에 관계없이 반응성을 얻기위해 확장성과 탄력성 또한 탑재하는 것이 사용자의 기대를 충족시키는 유일한 방법이다. 이것은 몇년간 더욱 중요하게 떠오를 것이다.

    저자에대해
    Kevin Webber는 Lightbend에서 Enterprise Advocate이다. 그는 heritage 구조에서 리액티브 프로그래밍 원칙을 포괄하는 실시간 분배 시스템까지 큰 구조의 트랜지션을 돕는 것에 열정적이다. 남는 시간에 ReactiveTOProgramming Book Club Toronto를 운영한다. 그는 가끔 제 3자에서 자기 자신에 대해 글을 쓰기도 하는데, 이 단락이 그러한 순간이다. 


    '그 외' 카테고리의 다른 글

    (번역)Android Architecture  (0) 2016.10.01

    WRITTEN BY
    tucan.dev
    개인 iOS 개발, tucan9389

    ,

    NSCodingNSObjectProtocol이라는 클래스 프로토콜이 필요하다. 그리고 그 프로토콜은 구제체가 따를 순 없다. NSCoding을 사용해서 인코딩하고 싶다면 가장 쉬운 방법이 클래스로 만들어 NSObject를 상속받는 것이다.

    나는 구조체를 NSCoding으로 감쌀 수 있는 말끔한 방법을 찾았고 거추장스러운 작업 없이 저장할 수 있다. 예제로서 Coordinate를 사용할 것이다.

    두개의 스칼라 프로퍼티를 가지는 간단한 한 타입이다. 이제 NSCoding을 따르고 Coordinate를 감싸는 클래스를 만들어보자.

    이 로직을 다른 유형으로 사용하는 것이 좋으며 단일 책임 원칙에 더 잘 지킨다. 눈치 빠른 독자는 위 클래스에 Coordinate 프로퍼티의 EncodableCorrdinate가 옵셔널이라는 것을 눈치 챘을 것이지만, 꼭 그럴 필요는 없음을 알 수 있을 것이다. 우리는 옵셔널이 아닌 Coordinate를 받는(혹은 실패하게 만드는) 생성자를 만들 수 있는데, 그렇다면 이미 init(coder:) 메소드는 불가능하다. 그리하여 EnabledCoordinate 클래스 인스턴스를 들고 있을때 항상 coordinate를 가지고 있음을 보장해주어야한다.

    그러나 더블(Double)타입(혹은 어떤 원시 타입)을 인코딩 할 때 NSCoder 동작 방법의 특징으로 인해 Any?를 반환하는 decodeObejct(forKey:)로 추출해낼 수 없게 되었다. DoubledecodeDouble(forKey:)로 하여 특정 윈시타입에 특정 메소드가 있다. 불행히도 이 특정 메소드는 옵셔널을 반환하지 않으며, 키가 맞지 않거나 오류가 나면 0.0을 반환해 버린다. 이러한 이유 때문에 coordinate 프로퍼티를 옵셔널로 두기로 했고 옵셔널로 인코딩한다. 그리하여 나는 decodeObject(forKey:)를 사용해 Double?을 얻어냄으로써 추가적인 안정성을 보장했다.

    이제 Coordinate 오브젝트를 인코딩/디코딩 하기 위해 EncodableCoordinate를 하나 만들고 NSKeyedArchiver로 디스크에 저장할 수 있다.

    이러한 추가적인 오브젝트를 만드는게 이상적이진 않으며, 나는 "가능하면 나를 캐싱해줘" 글에서 사용한 SKCache와같은 오브젝트로 작업하기를 좋아한다. 그리하여 내가 인코더와 인코딩 된 사이의 관계를 형식화 할 수 있다면, 아마 매번 NSCoding 컨테이너를 만들지 않아도 될 것이다.

    그 끝으로 두가지 프로토콜을 추가하자.
    그리고 우리의 두가지 타입에 대해 적용시킨다.

    이렇게하여 이제 타입 시스템은 오브젝트 쌍에서 타입과 값 사이에서 어떻게 변환하여 넣고 빼는지 안다.

    블로그 포스팅에서 나온 SKCache 오브젝트는 Encoded 타입을 넘어 제네릭으로 업그레이드 되어왔다. 인코더 값 타입이 그 자신이라는 조건으로, 이것은 이 두 타입 사이에 쌍방향으로 변환할 수 있게 해준다.

    이 타입에서 마지막 퍼즐조각인 savefetch 메소드가 남아있다. saveencoder를 잡아두고있고(사실 이것은 NSCoding을 따르는 오브젝트이다) path로 저장해둔다.

    패칭은 약간의 컴파일러 댄스(compiler dance)를 포함한다. 우리는 언아카이브(unarchive)된 오브젝트를 T.Encoable로 캐스팅 해야하는데, 이것은 encoder 타입이다. 그리고 그 값을 잡아두고, 동적으로 그것을 캐스팅하여 T로 돌려준다.

    이제 캐시를 사용하기위해 한 인스턴스를 만들고 Coordinate 제네릭으로 만든다.

    이렇게 하면 좌표 구조체를 쉽게 저장하고 검색할 수 있다.
    이것을 가지면 NSCoding을 이용해 구조체를 인코딩할 수 있고, 단일 책임 원칙에 따렴, 타입 세이프티하게 만들어준다. 



    WRITTEN BY
    tucan.dev
    개인 iOS 개발, tucan9389

    ,

    작년에 나는 Swift String Cheat Sheet라는 글을 썼는데, 이 글은 Swift 표준 라이브러리에서 더 복잡한 API중 하나를 어떻게 사용하는지 기억나게 해주었다. 이번 Swift3에서는 중요한 변화를 겪으면서 코드 마이그레이션을 힘들게 만들었따. 이것은 부분적으로 API 네이밍 가이드라인이 새로 바뀌면서, 컬렉션, 인덱스, 범위(Range)의 새로운 모델이 적용되었기 때문이기도 했다.

    이 글은 Swift3을 위해 업데이트한 Swift Playground에 필요한 것들을 메모한 것이다.

    좋은 리네이밍
    표준 라이브러이에서 새로운 API 가이드라인을 적용시키면 사용하고 있던 String에서 많은 프로퍼티와 메소드를 바꿔야한다. Xcode에서 이 작업을 어느정도 대신 해주기 때문에 그 모든 바뀐점에대해 언급하진 않겠다. 아래에는 일반적으로 바뀐것에 대한 방법을 알려준다.

    문자열 초기화
    표준 라이브러리는 String 생성자를 init(count: repeatedValue)에서 init(count: repeatedValue)로 바뀌었다. repeatedValue는 Charater 대신에 String으로 바뀌었는데, 더 유연하게 되었다.

    upper/lower case로 변환하기
    uppercaseString과 lowercaseString 프로퍼티가 이제 uppercased()와 lowercased() 함수로 바뀌었다.

    조금 더 있다가 다른 바뀐 이름에대해 다룰것이다.

    인덱스를 사용하여 컬렉션을 탐색하기
    Swift3에 들어오면서 String에 가장 큰 영향을 준 변화 중 하나는 컬렉션과 인덱스의 새로운 모델이다. 고쳐 쓰기위해 String의 요소에 직접 접근할 수 없고 대신에 컬렉션에 있는 인덱스를 사용해야한다.

    Swift3에서 각 컬렉션의 startIndex와 endIndex 프로퍼티는 바뀌지 않았다.

    character에 있는 것을 원할때 character 프로퍼티를 생략할 수도 있따.

    인덱스로 문자열을 탐색할 수 있도록 바뀌었다. 이제 successor(), predecessor(), advancedBy(n) 함수들은 없어졌다.

    Swift3에서는 이제 같은 결과를 얻기 위해 index(after:), index(before:), index(_: offsetBy:)를 사용한다.

    또한 end 인덱스를 넘어가버릴때 에러를 피하기위해 offset 한계를 정할 수도 있다. index(_: offsetBy: limitedBy:) 함수는 너무 멀리까지 가버리면 nil을 반환하는 옵셔널 반환 함수이다.

    첫번째로 일치하는 요소(아래는 character이다)의 인덱스를 찾는다.

    마지막으로는, 두 인덱스 사이의 거리를 계산해주는 메소드 이름이 바뀌었다.

    범위(Range) 사용하기
    범위는 Swift3에서 바뀌었는데, character에 시작 인덱스(lower bound)와 끝 인덱스(upper bound)를 가지고 있다고 가정하자.

    upper와 lower 바운드로부터 범위를 생성하기 위한 생성자 전체

    ..<와 ... 연산자를 사용하면 더 쉽게 생성할 수 있다.

    하위 문자열에 일치하는 문자가 있는지 확인하고 범위를 반환한다.

    Playground
    여러분은 전체적으로 업데이트된 playground 내 예제 코드를 저장소에서 확인할 수 있다. 또한 이전에 작성한 포스팅(영문)도 업데이트 되었다.

    더 읽을거리



    WRITTEN BY
    tucan.dev
    개인 iOS 개발, tucan9389

    ,