iOS 스터디 Part3 Force-Unwrap

강제로 unwrapping을 하는 Force-Unwrap에 대해 알아보고자 한다.

Advanced Swift (by Chris Eidhof) 책을 번역 후 참고하여 작성하였습니다.

iOS에서 unwrapping을 하는 3가지 방법이 있다.

  • Force-Unwrap: ! 연산자

  • Optional binding: ?? 연산자

  • Nil coalescing: ?. 연산자

이번에는 강제로 unwrapping을 하는 Force-Unwrap에 대해 알아보고자 한다.

언제 !를 사용해야할까?

Use "!" when you’re so certain that a value won’t be "nil" that you want your program to crash if it ever is.

→ nil이라면 프로그램이 충돌하기를 원할 때 쓰라고 권고하고 있다.

여기서도 이야기하는 바는 강제로 !를 쓰는 것보다 더 좋은 방법이 있다면 그 방법을 권고하고 있다. 하지만 때로는 Optional binding, Nil coalescing와 비교하여 하나의 문자로 의미를 표현할 수 있는 가독성 높은 연산자 역할을 하기도한다.

아래의 예시이다.

extension Sequence {
	func compactMap<B>(_ transform: (Element) -> B?) -> [B] {
		return lazy.map(transform).!lter { $0 != nil }.map { $0! } 
	}
}

$0이 nil이 아닌 것을 filter했기 때문에 당연히 $0은 nil이 아니라고 생각하고 $0!을 출력하는 간단한 예시이다.

let ages = [
	"Tim": 53,"Angela":54,"Craig":44, "Jony": 47, "Chris": 37, "Michael": 34,
]

// Force-unwrap
ages.keys
	.!lter { name in ages[name]! < 50 }
	.sorted()

// not force-unwrap(권장)
ages
	.!lter { (_, age) in age < 50 }
	.map { (name, _) in name } 
	.sorted()

모두 키 값이 존재하기 때문에 nil이 아니라고 생각하고 !를 출력하는 예시를 보여준다.

Force-Unwrap 에러 메세지

!! 연산자를 사용한 정의 함수로 메세지를 커스텀할 수 있다.

infix operator !!

func !! <T>(wrapped: T?, failureText: @autoclosure () -> String) -> T { ifletx=wrapped{returnx}
	fatalError(failureText())
}

let s = "foo"
let i = Int(s) !! "Expecting integer, got \\"\\(s)\\""

wrapped가 nil일 경우 fatalError를 발생시켜 메세지를 출력한다.

빌드 환경에 따라 assert 선택

릴리즈 빌드에서는 크래시를 선택하는 방법보다는 유효한 default값을 지정하는 방법을 권고하고 있다.

!?연산자를 사용한 정의 함수로 unwrap 실패시 assert하고 릴리즈 빌드에서는 기본값으로 대체하도록 할 수 있다. 아래를 예시로 설명하면, assert(wrapped != nil, failureText())는 디버그 에서만 동작하고 릴리즈에서는 return 문이 동작하여 nil일 경우 기본값이 출력되도록 처리한다.

infix operator !?
func !?<T: ExpressibleByIntegerLiteral>
	(wrapped: T?, failureText: @autoclosure () -> String) -> T
{
	assert(wrapped != nil, failureText()) 
	return wrapped ?? 0
}

lets="20"
let i = Int(s) !? "Expecting integer, got \\"\\(s)\\""

오버로딩하여 다양한 default값을 지정할 수 있다.

// 리스트
func !?<T: ExpressibleByArrayLiteral>
	(wrapped: T?, failureText: @autoclosure () -> String) -> T
{
	assert(wrapped != nil, failureText()) 
	return wrapped ?? []
}

// 문자열
func !?<T: ExpressibleByStringLiteral>
	(wrapped: T?, failureText: @autoclosure () -> String) -> T
{
	assert(wrapped != nil, failureText())
	return wrapped ?? ""
}

// 기본값과 오류 텍스트를 같이 사용하고 싶은 경우
func !?<T>(wrapped: T?,
	nilDefault: @autoclosure () -> (value: T, text: String)) -> T
{
	assert(wrapped != nil, nilDefault().text)
	return wrapped ?? nilDefault().value
}
Int(s) !? (5, "Expected integer")

// void를 반환하는 메서드의 경우
func !?(wrapped: ()?, failureText: @autoclosure () -> String)
{ 
	assert(wrapped != nil, failureText())
}
var output: String? = nil
output?.write("something") !? "Wasn't expecting chained nil here"

참조

https://developer.apple.com/documentation/swift/optional

Last updated