접근 제어(Access Controls), 접근 수정자(Access Modifiers), 접근 지정자(Access Specifiers)는 모두 같은 의미로 사용되고 있으며, Swift에서는 "접근 제어(Access Controls)"라는 말을 주로 사용합니다.
(사실 Swift의 대부분 타입은 접근 제어 기능을 사용할 수 있음)
코드의 세부적 구현 내용에 접근을 제어하는 기능을 부여하여 외부/내부의 접근을 통제할 수 있습니다. (객체지향의 4대 특징: 은닉화)
이를 통해 코드의 안정성 및 보안성, 사용 의도, 특정 로직 구현 등의 기능을 도출할 수 있습니다.
모듈(module): 프레임워크, 라이브러리, 앱 등 import 해서 사용할 수 있는 외부의 코드를 모듈(module)이라고 부릅니다.
특정 접근 제어를 부여받은 타입을 사용하기 위해서는 해당 타입 보다 낮은 수준의 접근 제어가 필요합니다.
✅ 기본 규칙 관련 코드
public class Man{ var name: String init(name: String){ self.name = name } } // 변수 "kim"의 접근 수준은 "Man" 클래스의 접근 수준보다 낮으므로 사용할 수 있습니다. fileprivate var kim = Man(name: "김철수") // 변수 "shin"의 접근 수준은 "Man" 클래스의 접근 수준보다 높으므로 사용할 수 없습니다. open var shin = Man(name: "신짱구") // 에러 발생
실제 실무에서 많이 사용하는 패턴 방식은 "private 접근 제어"가 적용된 변수(속성...)에 암묵적/관습적으로 언더바( _ )를 사용하여 이름을 짓는 방식입니다.
✅ 언더바( _ )를 사용한 패턴 예시
class Name{ private var _name: String var name: String{ // 읽기 기능만을 할 수 있는 로직을 생성 return _name } init(_name: String){ self._name = _name } } var kim = Name(_name: "김철수") //kim._name // 에러 발생: private로 선언했기 때문에 읽을 수가 없습니다. kim.name // 읽기 기능을 사용하고 싶을 때 특별 로직을 사용
Swift에서는 언더바( _ )를 사용한 패턴 방식의 코드 보다 간결하게 코드를 작성할 수 있도록 해주는 문법이 존재합니다.
접근 제어에 괄호를 열어 특정 기능(get, set)만을 지정하는 방식입니다.
✅ 언더바( _ )를 사용한 패턴 방식보다 간결한 코드 예시
class Name{ private(set) var name: String //읽기(get)는 internal, 쓰기(set)는 private // internal private(set) var name: String // internal는 생략 가능 init(name: String){ self.name = name } } var kim = Name(name: "김철수") kim.name //kim.name = "신짱구" // 에러 발생: 쓰기(set) 기능은 private
읽기(get)는 public, 쓰기(set)는 fileprivate으로 구현할 수도 있습니다.
public fileprivate(set) var test: String // 읽기(get)는 public, 쓰기(set)는 fileprivate으로 구현
커스텀 타입(클래스, 구조체, 열거형)에 접근 제어를 선언하면 해당 커스텀 타입의 멤버는 커스텀 타입의 접근 수준을 넘을 수 없습니다.
또한 내부 멤버에 접근 수준을 명시적으로 선언하지 않으면, 접근 수준은 internal(디폴트 값) 수준으로 유지됩니다.
✅ 커스텀 타입(Custom Type)의 접근 제어
class Man{ // class Man = internal class Man{} open var test1: String // test1의 접근 제어 선언은 open으로 했지만, 실제 작동은 internal 수준으로 작동합니다. public var test2: String // test2의 접근 제어 선언은 public으로 했지만, 실제 작동은 internal 수준으로 작동합니다. private var test3: String // test3의 접근 수준은 Man 클래스의 접근 수준보다 낮으므로 private 수준으로 작동합니다. var test4: String // 접근 수준을 명시하지 않았기 때문에 디폴트 접근 제어인 internal 수준으로 작동합니다. init(test1: String, test2: String, test3: String, test4: String){ self.test1 = test1 self.test2 = test2 self.test3 = test3 self.test4 = test4 } }
하위(자식) 클래스는 상위(부모) 클래스의 접근 수준을 넘어설 수 없습니다.
하지만 클래스 내부의 멤버는 상위(부모) 클래스의 접근 수준을 넘어설 수 있습니다.
✅ 상속 가능한 경우
// 상위(부모) 클래스 A open class A{ } // 하위(자식) 클래스 B private class B: A{ } // 자식 클래스의 접근 수준이 낮으므로 상속 가능
✅ 상속 불가능한 경우
// 상위(부모) 클래스 A private class A{ } // 하위(자식) 클래스 B open class B: A{ } // 자식 클래스의 접근 수준이 높으므로 상속 불가능
✅ 멤버의 접근 수준
상위(부모) 클래스 내 멤버의 접근 수준을 하위(자식) 클래스에서 높은 수준으로 또는 더 낮은 수준으로 재정의할 수 있습니다.
// 상위(부모) 클래스 A open class A{ fileprivate func AAA(){ print("A 출력") } } // 하위(자식) 클래스 B class B: A{ override public func AAA() { print("B에서 A 출력") } }
본체의 내용/기능을 확장하더라도 접근 수준은 변하지 않습니다. (접근 수준은 원래 본체와 같은 수준을 유지)
또한 본체에서 private 수준으로 정의한 멤버는 확장 scope(범위: { })에서도 사용할 수 있습니다.
✅ 확장(Extension)의 접근 제어
open class Man{ private(set) var name: String init(name: String){ self.name = name } } extension Man{ // 확장을 해도 접근 수준은 open func change(nameChange: String){ name = nameChange } } var kim = Man(name: "김철수") kim.change(nameChange: "신짱구") print(kim.name) // 신짱구