상세 컨텐츠

본문 제목

[iOS/Swift] 옵셔널?!(Optional)

IOS/키워드 정리

by 카키IOS 2022. 11. 5. 23:25

본문

1. 옵셔널?

  • 값이 있을수도 있고 없을 수도 있음
  • 옵셔널 타입만 nil을 반환할 수 있음
    • (String)값이 있으면 “가나다”, 없으면? “” ?? 아니다 → nil
    • (Int)값이 있으면 1, 없으면 ? 0 ?? 아니다 → nil (0도 값이다)
    • nil은 값이 없다를 뜻함

2. 옵셔널의 구조

  • 열거형
enum Optional<Wrapped>{
    case some(Any) // 값이 존재하는 경우
    case none // 값이 존재하지 않은 경우
}

Optional.none의 케이스로 값이 만들어진 것이 nil값 이다.

 

  • 값이 없는 경우 - Case none
    1. nilType -> 없는 타입?
    2. value1 -> 타입을 요구
    3. value2 -> 제네릭 파라미터 요구
    4. value3 -> 가능
var value: Optional<Any> = nil
print(value)
##################################################################
>>> nil

##################################################################

var value: Any? = nil
print(value)
##################################################################
>>> nil
let valueNone = Optional<Int>.none
let valueNil: Int? = nil

print(valueNone)
print(valueNil)
#########################################
>>> nil
>>> nil
  • 값이 있는 경우 - Case some
enum myOptional<Wrapped> {
    case some(Any)
    case none
}

//옵셔널 값이 있다면 어떤 타입이든 상관없다는 Optional

 

3 - 1. 옵셔널 해제

  • 1.강제 해제
    • 값이 100프로 있다고 판단 될 경우(= 값이 nil이 아니라고 확신할 경우)
    • 값 뒤에 “!”만 붙여주면 됨 (옵셔널 강제 해제 → 만약 값이 nil일 경우 앱이 꺼짐)
    • 조건문을 이용해서 안전하게 처리한다면?
    if value != nil {
    	#code
    } else {
    	#code
    }
    
    //이런식의 코드 작성보다는 "**옵셔널 바인딩**"을 사용함
    
  • 2.옵셔널 바인딩(Optional Binding)
    • 옵셔널의 값을 가져오고 싶은 경우에 사용
    • if let ~, guard let ~ 등과 같이 조건문과 함께 사용한다.
    //if let
    if let value = num {
    	#code
    } else {
    	#code
    }
    // -> 만약 값이 nil 이라면 앱이 꺼지지 않고 else문 실행
    
    //guard let
    guard let value1 = num1 else { return }
    
    guard let value2 = num2 else {
    	print("Jack: 어디까지 알아보셨나요?")
    }
    // -> guard 안의 조건문이 참(true)이 아니면 else문 실행
    
    //while let 도 사용가능
    
    • if let VS guard let
      • if let
        • 조건문 값이 nil인지 확인 후 분기처리
        • 지역변수로만 사용가능
        var num: Int?
        num = 1
        
        if let value = num {
            print(value)
        } else {
            print("nil")
        }
        
        value = 2 <<< 에러!
        
      • guard let
        • 조건이 true일 때, 코드가 계속 실행됨
        • 조건이 false라면 else문이 실행되므로, 상위 코드블럭을 종료하는 함수 필요(continue, break, return 등..)
        • 전역변수로 사용 가능
        extension ViewController : UITableViewDataSource
        {
            func numberOfSections(in tableView: UITableView) -> Int {
                return 1
            }
            
            func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                
                guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomTableViewCell else {return UITableViewCell()}
            
                return cell
            }
        }
        
        • Early Exit
          • guard를 이용한 값이 잘못 전달됐을 경우 특정 실행구문을 빠르게 종료시킨다
          • guard의 else 블럭 내부에는 특정 코드블럭을 종료하는 지시어(return, break등)가 꼭 있어야 한다.
          func earlyExit(num: Int?) {
          	guard let unwrappedNum = num,
          		unwrappedNum < 10, // 여기서 , 는 AND 논리연산(&&)과 동일
          		unwrappedNum >= 0 else {
          			print("잘못된 숫자를 입력하셨습니다.")
          			return
          		}
          	print("입력한 숫자는 \\(unwrappedNum) 입니다.")
          }
          
  • 3.컴파일러에 의한 자동 해제 (비교연산, 값 할당시)
    • 옵셔널 타입의 값 할당
    • 옵셔널 타입과 일반 타입의 값 비교
var num1: Int

num1 = 3

print(num1)

var num2: Int?

//num2 = Optional(3) -> 원칙
num2 = 3 -> 허용

print(num2)

print("num1: \\(num1), num2: \\(num2)", num1 == num2) //true
  • 4.묵시적 해제(IUO = Implicitly Unwrapped Optional)
//형태
let num: Int!

print(type(of: num)) // Optional<Int>.Type -> IUO 역시 옵셔널 타입

let num100: Int! = 1

print(num100) // Optional(1) -> 옵셔널 추출이 안됨
  • 특정 조건에서 옵셔널 강제 추출
    • 옵셔널 타입을 논 옵셔널 타입에 대입할 때 별도의 추출과정이 없이 대입이 가능
//기존 옵셔널
var num1: Int? = 4
var num2: Int = num1 // Value of optional type 'Int?' must be unwrapped to a value of type 'Int' 에러

var num3: Int! = 4
var num4: Int = num3

print(num4) // 4 -> 옵셔널 해제

  • 타입뒤에! → 많이 익숙하다
@IBOutlet weak var tableView: UITableView! // 프로퍼티 지연 초기화

//위의 경우와 API에서 IUO를 리턴한 경우를 제외하고 옵셔널 바인딩 사용을 추천
  • 그냥 옵셔널 바인딩 활용
  • 강제추출의 일부이기 때문에 위험성이 존재
  • flatMap(), map()에서의 옵셔널
    • map()
    //map()은 기존 데이터를 변형하여 새로운 컨테이너를 만든다
    // 기존 데이터는 변형되지 않는다(데이터 유지)
    
    let intArray = [1,2,3,4,5]
    let mappedIntArray = intArray.map { String($0) }
    print(mappedIntArray)
    
    >>> ["1", "2", "3", "4", "5"]
    
    // 문자열이 정수로 바꿀 수 없는 문자열이라면?
    let stringArray = ["Jack", "Hue", "Bro", "1"]
    let mappedStringArray = stringArray.**map** { Int($0) }
    print(mappedStringArray)
    
    >>> [nil, nil, nil, Optional(1)]
    
    let stringArray = ["Jack", "Hue", "Bro", "1"]
    let mappedStringArray = stringArray.**compactMap** { Int($0) }
    print(mappedStringArray)
    
    >>> [1] // 모든 옵셔널 언래핑, nil값 포함 x
    
    • compactMap()
      • Map() 과 동일한 작업 수행
      • Optional이 있다면 언래핑, nil값 포함X
    • flatMap()
      • non-nil인 결과들을 가지는 배열을 리턴
      • 주어진 Sequence내의 요소들을 하나의 배열로써 리턴
      • 주어진 Sequence가 not-nil인지 판단 후 unwrapping하여 closure 파라미터로 전달
//1. not-nil인 결과들을 가지는 배열을 리턴

let array: [Int?] = [1, 2, 3, 4, nil]
let mentoArr: [String?] = ["jack", "hue", "bro", nil]
print(array)
print(mentoArr)

>>> [Optional(1), Optional(2), Optional(3), Optional(4), nil]
>>> [Optional("jack"), Optional("hue"), Optional("bro"), nil]
===============================================================

//Int?
let flatMappedArray = array.flatMap { $0 }
print(flatMappedArray)

>>> [1, 2, 3, 4] // 옵셔널 해제, nil값은 출력 안됨

//String?
let flatMappedMentoArray = mentoArr.flatMap { $0 }
print(flatMappedMentoArray)

>>> ["jack", "hue", "bro"] // 옵셔널 해제, nil값은 출력 안됨

//flatMap 대신 compactMap() 사용 -> 같은 결과
===============================================================
//주어진 시퀀스 내의 요소들을 하나의 배열로써 리턴

let arrayInArray = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let flatMappedArray2 = arrayInArray.flatMap { $0 }
print(flatMappedArray2)

>>> [1, 2, 3, 4, 5, 6, 7, 8, 9]
let intArray2: [[Int?]] = [[1, 2, 3], [nil, 4], [5, nil], [nil, 6]]
let flatMapIntArr = intArray2.flatMap { $0 }
let compactMapIntArr = intArray2.compactMap { $0 }

print(flatMapIntArr)
print(compactMapIntArr)

>>> [Optional(1), Optional(2), Optional(3), nil, Optional(4), Optional(5), nil, nil, Optional(6)]
>>> [[Optional(1), Optional(2), Optional(3)], [nil, Optional(4)], [Optional(5), nil], [nil, Optional(6)]]

=====================================================================================================

let intArray2: [[Int?]] = [[1, 2, 3], [nil, 5], [6, nil], [nil, nil]]
let flatMapArr = intArray2.flatMap { $0 }.compactMap{ $0 }

print(flatMapArr)

>>> [1, 2, 3, 5, 6]

3 - 2. 옵셔널 체이닝

  • 언제쓸까?
    • 옵셔널 타입으로 정의 된 값이 하위 프로퍼티메소드를 가지고 있을 때, if 구문 없는 간결한 코드를 작성하기 위해 도입됨
    • 프로퍼티
    struct Human {
        var name: String?
        var man: Bool = true
    }
    
    struct Privacy {
        var student: Human?
        var address: String?
    }
    
    var sesac2nd: Privacy? = Privacy(student: Human(name: "DY", man: true), address: "Seoul")
    
    sesac2nd?.student // 1. sesac2nd는 옵셔널 타입이기 때문에 sesac2nd.student로 호출 불가
    sesac2nd?.student?.name // 2. sesac2nd, student는 옵셔널 타입이므로 sesac2nd.student.name로 호출 불가
    sesac2nd?.student?.name = "Mime" // 3. 값 할당 -> 안정성 검사 불필요
    
    ##### sesac2nd 변수를 이용하여 student 프로퍼티의 내부 프로퍼티인 name을 참조하기 #####
    
    ######################## 옵셔널 체이닝 X ############################
    if let mimeMan = sesac2nd {
        if let mimeStudent = mimeMan.student {
            if let mimeName = mimeStudent.name {
                print("나는 \(mimeName) 입니다.") // 나는 DY 입니다.
            }
        }
    }
    
    -> 좀 더 간결하게?
    
    if let mimeMan = sesac2nd!.student!.name {
    	print("나는 \(mimeMan) 입니다.")
    }
    
    // 중간에 nil 값이 존재한다면? -> 런타임 오류
    
    ######################## 옵셔널 체이닝 O ############################
    
    if let mimeMan = sesac2nd?.student?.name {
    	print("나는 \(mimeMan) 입니다.")
    }

1.에서 sesac2nd가 nil이 아니면 student객체가 반환되고, nil이면 nil값을 반환한다. = 2.

print(sesac2nd?.student)
print(sesac2nd?.student?.name)

var sesac2nd2: Privacy? = nil

print(sesac2nd2?.student)
print(sesac2nd2?.student?.name)

##################################################################
>>> Optional(__lldb_expr_23.Human(name: Optional("DY"), man: true))
>>> Optional("DY")
>>> nil
>>> nil

 

1., 2. 에서 맨 마지막 값은 옵셔널 체이닝을 통해 참조하기 때문에 옵셔널 바인딩이 필요하다

if let name = sesac2nd?.student?.name {
    print(name)
}

let name2 = sesac2nd?.student?.name
print(name2)

##################################################################
>>> DY
>>> Optional("DY") // 일반 값일 지라도 옵셔널 체인을 통해 참조했다면 옵셔널 타입으로 변경
##################################################################
  • 메서드
class CustomObservable<T> {
    private var listener: ((T) -> Void)?
    
    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    init(_ value: T) {
        self.value = value
    }
    
    
    func bind(_ closure: @escaping (T) -> Void) {
        closure(value)
        listener = closure
    }
}
var value: T {
	didSet {
		listener?(value) // listener가 옵셔널 클로저 타입이기 때문에
	}
}

4. swift5.7 에서의 옵셔널 바인딩

  • 기존
var name: String? = "jack"
print(name)

if let mento = name {
  print("i'm \\(mento)")
} else {
  print("nil")
}

>>> Optional("jack")
>>> i'm jack
  • 변경
//할당연산자, 임시 상수 사용안해도 됨

name = "Hue"

if let name {
  print("my name is \\(name)")
} else {
  print("nil입니다.")
}

>>> my name is Hue
//단 객체 내부의 프로퍼티에는 적용되지 않음

struct Phone {
    var number: String
}

let phone: Phone? = Phone(number: "010-1111-2222")

if let phone.number {
    print("jack: \\(phone.number)")
}
728x90
반응형

관련글 더보기