assert / precondition (feat. preconditionFailure vs fatalError)
precondition을 얼마전에 처음 봤는데, 그 때 쓰기 시작한 글을 마무리를 못했네여
설날 기념으로 마무리해서 발행쓰
# assert
Debug configuration에서 오류가 생기면 치명적일 것인 곳에 심어 놓는 에러 검출용 코드이다.
즉 Release configuration에서는 아무 영향을 주지 않는다.
func assert(
_ condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = String(),
file: StaticString = #file,
line: UInt = #line
)
------
[사용법]
assert(false)
assert(1 + 1 != 2, "assert message")
이 condition이 evalute되는 빌드가 따로 있는데,
- 플레이그라운드 or -Onone(Debug Configuration의 기본값) & condition이 false이면 ➡️ 프로그램 중지 🔥
- -O빌드(Release Configuration의 기본값)에서는 condition이 evaluate 되지 않음 (== 효과가 없음)
Release configuration에서는 condition이 evalute 조차 되지 않으니까 assert가 무시?되는것이다.
# assertionFailure
func assertionFailure(
_ message: @autoclosure () -> String = String(),
file: StaticString = #file,
line: UInt = #line
)
assertionFailure은 assert와 동작하는 환경자체는 같은데, condition이 없는 것을 볼 수 있다.
condition없이 무조건 잘못된 상황일 때 assertionFailure를 사용하면 된다.
역시나 Debug configaration에서만 동작한다.
# precondition
Debug configaration말고 Release configation에서도 에러를 검출하고 프로그램을 중지시키고 싶다면 precondition을 쓰면된다.
덜덜;;;
precondition은 이름에서도 그 역할이 보이듯이, 코드가 수행되기 위해 필요한 조건을 앞서 확인하는 친구다.
func precondition(
_ condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = String(),
file: StaticString = #file,
line: UInt = #line
)
수행되는 환경은 assert와 살짝 다르다. (precondition은 Releas configuration에서도 프로그램을 중지시키니까)
- 플레이그라운드 or -Onone(Debug Configuration의 기본값) & condition이 false이면 ➡️ 프로그램 중지 🔥
- -O빌드(Release Configuration의 기본값) && condition이 false이면 ➡️ 프로그램 중지 🔥
프로그램 중지될때 전달한 message도 같이 나오게 되는데(전달안하면 empty string)
이건 Onone(Debug configuration의 기본값)에서만 나온다. (당연하게도?!)
# preconditionFailure
func preconditionFailure(
_ message: @autoclosure () -> String = String(),
file: StaticString = #file,
line: UInt = #line
) -> Never
precondition은 condition이 있었다면 요건 그냥 아묻따 프로그램 종료
- 플레이그라운드 or -Onone(Debug Configuration의 기본값) ➡️ 프로그램 중지 🔥
- -O빌드(Release Configuration의 기본값) ➡️ 프로그램 중지 🔥
precondition과 마찬가지로 프로그램 중지될때 전달한 message는 Onone(Debug configuration의 기본값)에서만 나온다.
위에 같이 적진 않았지만..
assert류, precondition류들은 -Ounchecked 빌드에서는 condition이 evalute되지는 않지만 optimizer가 condition이 항상 true로 간주할 수도 있다고 한다.
사실 -Ounchecked는 특수한? 빌드이기도 하고
딱히 큰 신경을 안써도 될 것 같긴하다 ㅎ;
사실 나는 assert도 잘 안쓰기도 하고해서 precondition이라는게 있는지도 몰랐다.
precondtion은 릴리스 빌드에서도 발생한다니까 무섭긴 한데 😥 ,,
진짜 일반 사용자에게 절대 노출되면 안되는?
(간헐적으로 일어나는) 모종의 이유로 꼬여서 특정 값 없이 그대로 실행하는게 더 위험한 곳(== 차라리 앱을 종료시켜버리는게 나은 상황..)
에서는 적절히 쓰면 좋을 것 같다! (말 그대로 precondition이니까 특정 condition이 만족하지 못하는 상황)
잘 정리된 이미지가 있어서 첨부!!
# 번외 (안읽어도됨)
위 이미지를 보고 일반적인 상황에서 preconditionFailure랑 fataError랑 무슨차이인거지...? 가 궁금했는데,
어딜 찾아봐도 차이점은 이거다!!!! 라고 딱 명쾌하게 나온곳이 없었다 ㅠ
차이점 찾는거 포기할때쯤 swift github의 Assert.swift를 보게 됐는데, 흥미로웠다 ㅎㅎㅎ
[assert]
정의 그대로 DebugConfiguration일때만 보고 있었다!! (당연하겠지만..)
[assertionFailure]
assertionFailure의 경우 Debug Configuration만 볼 줄 알았는데 Fast Configuration이란것도 보고있었다.
Fast Configuration은 뭔지 잘 모르겠다. 이것저것 건들여봐도 -Onone에서만 동작한다.
[precondition]
precondition은 정의와 똑같이 Debug, Release 둘 다 보고 있다.
역시 위에서 본 것 처럼 message는 Debug일때만 전달되고,
Release일때는 단순히 "precondition failure"로 전달되는 것을 볼 수 있다.
[preconditionFailure]
precondition과 거의 똑같은데, condition조건을 검사하는 로직이 사라졌다.
[fatalError vs precondition]
애플 쪽 코드를 보니까..그냥 preconditionFailure와 fatalError는 차이가 있긴 하지만
이 차이를 분석해야할만큼;;; 유의미하게 다른것 같진 않다.
굳이굳이 차이점을 꼽자면;;
fatalError는 항상 message를 전달하고, preconditionFailure는 Debug Configuration일때만 전달한다.
그리고 fatalError는 항상 _assertionFailure를 호출하는 반면, (_assertionFailure은 결국 Builtin.int_trap()을 호출한다)
preconditionFailure는 Release Configuration일때는 Builtin.condfail_message()를 호출한다.
fatalError : Builtin.int_trap()
preconditionFailure : Debug -> Builtin.int_trap() / Relase -> Builtin.condfail_message()
Builtin.int_trap() 와 Builtin.condfail_message() 의 안쪽이 정확히 어떻게 되어있는지는 못찾았는데,
크래시날 때 스택트레이스가 좀 다른것을 보니 내부 동작은 다른건 확실한데...굳이 더 안파봐도 될 것 같다.
ㅎㅓ허
[참고]
https://developer.apple.com/documentation/swift/debugging-and-reflection#testing
https://github.com/apple/swift/blob/main/stdlib/public/core/Assert.swift
https://github.com/apple/swift/blob/main/stdlib/public/core/AssertCommon.swift