Swift 3 에서 private 으로 정의된 프로퍼티나 함수의 액세스 컨트롤 범위는 바로 정의된 범위 그 자체입니다.

struct Date: Equatable, Comparable {
  private let secondsSinceReferenceDate: Double
  static func ==(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
  }
  static func <(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
  }
}     

위 예제에서 secondsSinceReferenceDate 라는 프로퍼티는 private 으로 지정되었기 때문에 struct Date { } 가 정의된 범위 내에서만 사용할 수 있습니다.

Swift 3 에서는 private 의 사용 범위를 더욱 더 제한함으로써 데이터를 안전하게 사용하려고 했지만 불편한 부분이 있었습니다. 바로 extension 입니다.

extension 은 Objective-C(Objective-C 에선 category 라고 부릅니다.) 와 Swift 에서 개발자들에 의해 가장 빈번히 그리고 즐겨 사용하게 되는 기술입니다.

위 코드를 extension 을 이용해 분리하면 다음과 같이 됩니다.

struct Date {
  private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
  static func ==(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
  }
}
extension Date: Comparable {
  static func <(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
  }
}

논리적으로 구분되는 부분을 명시적으로 다른 영역에 표현해서 코드 가독성을 높여줍니다.

하지만 위와 같이 바꿨을 때 Swift 3 에선 에러가 나게 되는데요. 바로 private 의 액세스 범위가 struct Date { } 를 벗어날 수 없기 때문입니다.

새로 생성된 extension 에선 secondsSinceReferenceDate 에 접근할 수 없습니다.

이 문제를 해결하는 방법은 secondsSinceReferenceDate 의 접근 제한자를 private 에서 fileprivate 으로 바꾸는 것입니다.

struct Date {
  fileprivate let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
  static func ==(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
  }
}
extension Date: Comparable {
  static func <(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
  }
}

fileprivate 은 액세스 범위를 해당 파일로 두고 있습니다.

그래서 private 을 fileprivate 으로 바꾸면 되는데, 경험 있는 Swift 개발자들은 이 모습을 봤을 때 뭔가 이질감을 느꼈을 겁니다.

private 은 말 그대로 외부에서 접근 불가능하다는 의미를 가지지만, extension 에서도 private 에 접근하지 못한다는 건 뭔가 취지에 맞지 않아 보였습니다.


Swift 3 의 디자인은 private 을 적극적으로 사용하는게 목표였다고 합니다.

그리고 적절하게 extension 을 사용하는 것은 Objective-C 와 Swift 에서 굉장히 오래된 관습이고 중요한 패턴입니다.

하지만 Swift 3 에선 이 두 가지 패턴이 상극이 됐으니, 저 fileprivate 을 쓸 때마다 뭔가 불편함을 느끼지 않을 수 없었습니다.


Swift 4 에선 드디어 이 부분이 해결되었습니다.

fileprivate 이 아니라 private 을 그냥 쓰면 됩니다.


이제 다시 위 두번째 코드를 쓰면 되네요 :)

struct Date {
  private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
  static func ==(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
  }
}
extension Date: Comparable {
  static func <(lhs: Date, rhs: Date) -> Bool {
    return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
  }
}

규칙은 간단합니다.

같은 파일 내에 쓰여진 extension 은 정의된 struct나 class 안에서 private 접근을 서로 공유합니다.

extension 이 다른 파일에 쓰였다면 private 은 서로 접근되지 않습니다.


다음은 몇 가지 예제입니다.(출처 : https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md)

struct S {
  private var p: Int
  func f() { 
    use(g())    // ok, g() is accessible within S
  }
}

extension S {
  private func g() {
    use(p)      // ok, g() has access to p, since it is in an extension on S.
  }
}

extension S {
  func h() {
    use(g())    // Ok, h() has access to g() since it defined in the access control scope for S.
  }
}
// FileA.swift
struct A {
  private var aMember : Int 
}

// FileB.swift
extension A {
  private func foo() {
    bar()    // ok, foo() does have access to bar()
  }
}

extension A {
  private func bar() {
    aMember = 42  // not ok, private members may not be accessed outside their file.
  }
}

https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md

https://developer.apple.com/videos/play/wwdc2017/402/