nil(값없음)
을 허용하지 않지만 개발을 하다 보면Person
struct
에서 프로퍼티로 소유하고 있는 자동차를 넣고 싶은데 모든 사람이 자동차를 갖고있지는 않다.Optional
이다.Optional
을 사용하면 값이 없는 상황을 개발할 수 있다.
struct Person {
var name: String
var age: Int
var car: String? // ?를 붙여서 옵셔널 타입이라고 명시하여 값이 없을수도 있다고 알려줄수 있다
func introduce() {
print("안녕하세요. 제 이름은 \(name)이고, 나이는 \(age)살 입니다.")
}
}
Swift는 기본적으로 nil
을 허용하지 않지만, Optional
를 사용하면 값이 없을 수 있는 상태를 안전하게 처리할 수 있다.
nil
키워드는 값이 없음
을 의미한다.옵셔널
이라고 하며, 옵셔널 타입은 nil
값을 저장할 수 있다.?
키워드를 사용하여 옵셔널 타입
으로 선언할 수 있다.struct
, class
, enum
)클로저
Optional
로 감싸진 값이 나온다.Optional로 래핑된 값
이라고 부른다.옵셔널 타입 선언 및 사용 방법
- 옵셔널 타입으로 선언하는 방법
// 기본 타입 옵셔널로 선언하기
var intValue: Int?
var stringValue: String?
// Collection Type 옵셔널로 선언하기
var array: [String]?
var dictionary: [String: String]?
// struct, class, enum 을 타입처럼 사용하므로 Optional로 선언할 수 있다.
struct Person {
let name: String
}
var optionalPerson: Person?
// 클로저 옵셔널로 선언하기
var closure: (() -> Void)?
옵셔널 타입을 사용하는 방법 (할당과 접근)
// 값을 할당할 때는 기존의 타입과 동일하게 사용하면 된다.
var intValue: Int? = 1
var stringValue: String?
stringValue = "안녕하세요"
var nilValue: String? = nil
struct Person {
let name: String
}
var optionalPerson: Person? = Person(name: "Brody")
var closure: (() -> Void)? = {
print("Fire")
}
// 값에 접근하면 Optional로 래핑된 값으로 나온다.
print(intValue) // Optional(1) : 옵셔널로 래핑된 값 1이 출력된다.
print(stringValue) // Optional("안녕하세요") : 옵셔널로 래핑된 값 "안녕하세요"가 출력된다.
print(nilValue) // nil : 값이 없음을 의미하는 nil이 출력된다.
Optional 값
기본 타입과 연산이 불가능하다.
옵셔널 타입
과 기본타입
은 다른 타입이기 때문에 연산이 불가능하다.var intValue: Int? = 5
intValue += 5 // Error 발생
// Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
// Int? 타입은 Int 타입과 다르기 때문에 컴파일 오류 발생!
옵셔널 체이닝은 옵셔널 타입을 포함하는 복잡한 데이터 구조에서 옵셔널 값이 nil
인지를 간결하게 체크하고 접근할 수 있는 방법이다.
nil
이 있는 경우 자동으로 nil
을 반환한다.?
를 붙여서 체이닝할 수 있다.struct Person {
var name: String = "Default Name"
var animal: Animal? // 사람 struct은 반려동물이 있을 수도 있고 없을수도 있어요!
}
struct Animal {
let name: String
var age: Int? // 언제 태어난지 모른다면 나이를 정할 수 없어서 Optional 타입으로 설정
}
let person: Person? = Person(name: "Ryu", animal: nil)
print(person?.animal?.name) // person은 옵셔널 값이여서 ?를 붙였으며, nil이 아다. 다음으로 넘어간다.
// animal?을 확인해보니 nil이여서 nil을 반환한다.
// 출력 값 : nil
let person2 = Person(name: "Ryu", animal: Animal(name: "Dog", age: 5))
print(person2.animal?.name) // person2는 옵셔널이 아니기 때문에 ?를 붙이지 않는다.
// animal?을 확인하니 값이 있다. 다음으로 넘어갑니다.
// name은 값이 있어서 Animal의 "Dog"를 옵셔널 래핑하여 반환한다
// 출력 값 : Optional("Dog")
let stringValue: String? = "안녕하세요"
print(stringValue?.count) // Optional(5)
// 옵셔널 체이닝으로 연결되어 있기 때문에 Optional로 래핑된 값이 출력된다.
// 옵셔널 값이기 때문에 언래핑하여 사용하면 된다고한다.
옵셔널 타입은 값이 없을수도 있는 경우를 안전하게 처리하기 위해 사용되지만,
값에 접근하면 Optional(값)
으로 래핑되어 있어서 바로 사용하지 못하는 불편함이 있다.
따라서 이 옵셔널로 래핑된 값에서 옵셔널을 제거하고 값으로 변환하는 과정을
옵셔널 언래핑
이라고 부르며, 몇가지 방법으로 이를 수행할 수 있다.
옵셔널 바인딩 (Optional Binding)
if
, guard
구문을 사용하여 안전
하게 옵셔널을 언래핑하는 방법이다.if let
if let
을 사용하여 옵셔널 바인딩을 할 수 있다.if let
코드블록이 실행되고, 이 블록 안에서 언래핑 된 값을 사용할 수 있다.else
코드블록을 작성하면 된다.// 값이 있을 때 if let 옵셔널 바인딩 코드
var intValue: Int? = 10
if let intValue = intValue {
print(intValue) // 출력 값 : 10
// Optional 언래핑되어 실제 값이 출력된다.
// 옵셔널 언래핑된값은 해당 블록에서만 사용 가능하다.
}
print(intValue) // Optional(10)
// 값이 없을 때 if let 옵셔널 바인딩을 사용하여 else에서 작업 진행
var optionalValue: String? // 아무런 값을 주지않아서 nil인 상태
if let optionalValue = optionalValue {
print(optionalValue) // optionalValue가 nil이 아니면 해당 코드블록이 실행되어 값이 출력된다.
} else {
print("optionalValue 값은 nil 입니다.") // 출력 값 : optionalValue 값은 nil이다.
}
guard let
guard let
구문은 옵셔널 바인딩 결과가 nil
일 경우,// 값이 없는 경우
func guardLetFunction() {
var optionalValue: String? // 아무런 값을 주지않아서 nil인 상태
guard let optionalValue = optionalValue else {
print("guard 실행") // optionalValue가 nil이면 해당 코드블록이 실행된다.
return // 함수를 종료하여 아래의 코드로 내려가지 못하게 막는다.
}
print(optionalValue) // 위의 guard let에서 함수가 종료되어 실행되지 못함
}
guardLetFunction()
/* 출력 값
"guard 실행"
*/
func guardLetFunction() {
var optionalValue: String? = "Hello"
guard let optionalValue = optionalValue else {
print("guard 실행")
return // 함수를 종료하여 아래의 코드로 내려가지 못하게 막는다.
}
print(optionalValue) // Optional이 언래핑된 값 "Hello"가 출력된다.
}
guardLetFunction()
/* 출력 값
"Hello"
*/
기본값 제공
??
를 붙인 후 기본값을 제공할 수 있다.nil
이면 ??
뒤에있는 기본 값을 사용한다.let name: String? = nil
print(name ?? "Default Name") // 값이 nil이여서 ?? 뒤에 있는 "Default Name"이 출력된다.
let greeting = "안녕하세요 \(name ?? "A")님!"
print(greeting) // "안녕하세요 A님!" 이 호출된다.
// name이 nil이여서 기본 값 "A"를 사용한다.
옵셔널 강제 언래핑(force unwrapping)
!
를 붙여서 사용한다.nil
이 아니라면 언래핑이 되지만 nil이면 런타임오류
를 발생시킨다!nil
이 아님을 확신할 때 사용한다.var name: String? = "Ryu"
print(name!) // "Ryu" 출력
name = nil
print(name!) // 런타임 오류 발생
옵셔널 묵시적 언래핑(Implicitly Unwrapped Optional)
?
가 아닌 !
를 사용하면 묵시적 언래핑 되는 옵셔널로 정의할 수 있다.nil
일 때 접근하면 런타임 오류가 발생한다.var name: String! = "Ryu" // 타입을 String!으로 설정하여 묵시적 옵셔널 언래핑을 사용
print(name) // Optional("Ryu") : 값 자체는 옵셔널
print(name.count) // 5 : 값에서 가져온 값은 옵셔널값이 아닌 일반 값 출력
if let name = name {
print(name) // "Ryu" 출력
}
중첩된 타입은 하나의 타입 안에 다른 타입을 정의하는 것을 의미한다.
- 구조적으로 복잡한 클래스나 구조체 등을 더 조직적으로 관리할 수 있다.
class
,struct
,enum
등에서 사용할 수 있다.- 중첩된 타입을 사용하면 코드의 가독성을 높이고, 타입 간의 연관성을 명확히 할 수 있다.
- 타입의 블록 안에서 다른 타입을 정의하고 사용하는 방식으로 구현한다.
struct Car {
struct Company { // Car 안에 중첩된 Company 구조체
var name: String
var phoneNumber: String
func contact() {
print("\(name) 회사의 A/S 센터 번호는 \(phoneNumber)입니다.")
}
}
enum Model {
case sedan
case hatchback
case suv
}
var model: Model
var company: Company
var name: String
var color: String
}
let myCar = Car(model: .sedan,
company: Car.Company(name: "스파르타!", phoneNumber: "000-000-000"),
// company: .init(name: "스파르타!", phoneNumber: "000-000-000"), // .init을 해서 만들어도 된다.
name: "붕붕이",
color: "Black")
myCar.company.contact() // myCar의 company 프로퍼티의 contact 함수를 호출합니다.
// 결과값 : 스파르타! 회사의 A/S 센터 번호는 000-000-000다.
print(myCar.model) // myCal의 model 프로퍼티를 출력한다.
- class, struct을 사용하다가 보면 외부에서 특정 데이터에 접근을 제한하고 싶을 때가 있다.
- 예를들어,
Person
struct에서 내가 가진돈을 표현하는 havingMoney 프로퍼티를 추가하면 된다.
struct Person {
var name: String
var age: Int
var havingMoney: Int
func introduce() {
print("안녕하세요. 제 이름은 \(name)이고, 나이는 \(age)살 입니다.")
}
}
var me = Person(name: "Ryu", age: 28, havingMoney: 10000)
print(me.havingMoney) // Person 외부에서 havingMoney 에 접근이 가능하다.
- 내가 가진 돈은 나만 알고 싶은데 다른 사람에게 알리고 싶지 않은경우가 있다.
이럴 때 접근제어자를 사용하면 외부에서 접근을 못하도록 막을 수 있다.- 접근제어자는 외부에서 코드에 대한 접근을 제한하는 기능을 제공한다.
접근을 제어함으로써, 불필요한 정보의 노출을 막을 수 있다.class
,enum
,struct
등에서 사용 가능하며,프로퍼티
,메소드
에 적용할 수 있다.
open
class
에서만 사용 가능public
internal
기본값
접근제어자로 설정하지 않았다면 internal
fileprivate
private
struct
에서 private 프로퍼티
가 있다면 멤버와이즈 init을 사용할 수 없어서 직접 init을 작성해야 한다.struct Person {
var name: String
public var age: Int
private var havingMoney: Int
init(name: String, age: Int, havingMoney: Int) {
self.name = name
self.age = age
self.havingMoney = havingMoney
}
private func printMoney() {
print("나는 \(havingMoney) 원 있다!")
}
func test() {
printMoney()
}
}
let person = Person(name: "Ryu", age: 20, havingMoney: 3000)
✅
person.test() // test 함수는 internal(default) 이여서 호출이 가능하다.
// test 함수 안에서는 private printMoney 함수에 접근이 가능하다.
// 출력 값 : 나는 3000 원 있다.
person.age // public 이기 때문에 접근 가능하다.
// ❌ 빌드 오류 발생
person.havingMoney // person의 havingMoney는 private 프로퍼티 이기 때문에 접근할 수 없다.
// 'havingMoney' is inaccessible due to 'private' protection level
person.printMoney() // person의 printMoney함수는 private 메소드여서 접근할 수 없다.
// 'printMoney' is inaccessible due to 'private' protection level
// 프로퍼티와 메소드뿐만 아니라 struct, class, enum도 동일하게 사용 가능
public struct Person2 {
}
fileprivate class Animal {
}
private enum Season {
}
class, struct, enum 에서 공통으로 구현해야 하는 메소드와 프로퍼티의 청사진을 정의하는 기능.
프로토콜
자체는 기능을 구현하지 않으며, 오직 설계만 제공한다.class
, struct
, enum
에서 프로토콜을 채택
할 수 있으며, 프로토콜에서 정의한 프로퍼티와 메소드를 모두 구현해야 한다.채택
하는 방법은 타입의 이름 뒤에 :
콜론을 넣은 후 프로토콜 이름을 작성하면 된다.채택
할 수 있으며, 프로토콜 이름을 ,
로 구분한다.프로토콜
에서 정의된 프로퍼티는 항상 var
로 선언되어야 한다.프로토콜
에서 정의하는 프로퍼티는 읽기 전용 { get }
또는 읽기-쓰기 가능 { get set }
으로 설정할 수 있다.{ get }
으로만 설정해도 프로퍼티의 값을 변경할 수 있지만, 명시적으로 작성하면 코드의 의도를 쉽게 파악할 수 있다.이름
,파라미터
, 리턴타입
만 선언하며, 구현부 { }
는 작성하지 않는다.프로토콜 정의 방법
- 기본 정의 방법
protocol 프로토콜이름 {
// 프로퍼티 정의
// 메소드 정의
}
protocol FullyNamed {
var fullName: String { get }
func sayMyFullName() -> String // 구현부는 작성하지 않는다.
}
- 프로토콜 채택하여 구현하는 방법
// 1개의 프로토콜 채택
protocol FullyNamed {
var fullName: String { get }
func sayMyFullName() -> String // 구현부는 작성하지 않는다.
}
class Person: FullyNamed { // FullyName 프로토콜을 채택합니다.
var fullName: String // FullyName 프로토콜에 있는 fullName 프로퍼티를 구현해야 한다.
func sayMyFullName() -> String { // 프로토콜에 있는 메소드를 구현해야 한다.
return fullName
}
init(fullName: String) {
self.fullName = fullName
}
}
var person = Person(fullName: "Ryu")
print(person.fullName) // "Ryu" 출력
print(person.sayMyFullName()) // "Ryu" 출력
- 여러개의 프로토콜 채택 하는 방법
// 여러개의 프로토콜 채택
protocol FullyNamed {
var fullName: String { get }
func sayMyFullName() -> String
}
protocol ShortNamed {
var shortName: String { get }
}
class Person: FullyNamed, ShortNamed { // 프로토콜 여러개를 채택하는 클래스다.
var fullName: String
func sayMyFullName() -> String {
return fullName
}
var shortName: String {
return "ShortName"
}
init(fullName: String) {
self.fullName = fullName
}
}
var person = Person(fullName: "Ryu")
print(person.fullName) // "Ryu" 출력
print(person.sayMyFullName()) // "Ryu" 출력
print(person.shortName) // "ShortName" 출력
클래스
전용 프로토콜 만들기
class
전용 프로토콜은 struct
, enum
에서 사용될 수 없다.protocol OnlyClassProtocol: AnyObject {
}
기존의
class
,struct
,enum
,protocol
타입에 새로운 기능을 추가할 수 있는 키워드다.
타입의 원본 코드를 수정하지 않고도 수평적인 기능을확장
할 수 있어 코드의 유지 보수와 가독성이 향상된다.
extension
키워드를 사용하여 기존 타입을 확장할 수 있다.extension
으로 추가해 적용할 수 있다.extension
여러 번 가능하다.init
사용 방법
- 기본 사용 방법
// 기본 사용 방법
struct Person {
let name: String
}
// extension 키워드 작성 후 확장시키고 싶은 타입 이름을 명시한다.
extension Person {
}
// 특정 타입의 프로토콜을 확장시키고 싶을 때
extension Person: Equatable {
}
- 확장할 수 있는 것들
// 확장할 수 있는 것들
struct Person {
let lastName: String
let firstName: String
let age: Int
}
protocol FullyNamed {
var fullName: String { get }
func sayMyFullName() -> String
}
// extension에서 연산 프로퍼티를 구현할 수 있다.
extension Person {
var nameAge: String {
return "\(firstName)(\(age)세)"
}
}
// extension에서 메소드를 구현할 수 있다.
extension Person {
func sayHello() {
print("\(firstName)님 안녕하세요?")
}
}
// extension에서 protocol을 채택하여 구현할 수 있다.
extension Person: FullyNamed {
var fullName: String {
return "\(lastName)\(firstName)"
}
func sayMyFullName() -> String {
return "제 이름은 \(fullName)입니다."
}
}
let person = Person(lastName: "홍", firstName: "길동", age: 20)
print(person.nameAge) // extension에서 구현한 연산프로퍼티를 사용할 수 있다.
person.sayHello() // extension에서 구현한 메소드를 호출할 수 있다.
print(person.fullName) // extension에서 구현한 프로토콜을 사용할 수 있다.
print(person.sayMyFullName()) // extension에서 구현한 프로토콜을 사용할 수 있다.
/* 출력 값
길동(20세)
길동님 안녕하세요?
홍길동
제 이름은 홍길동입니다.
*/