iOS

assert / precondition (feat. preconditionFailure vs fatalError)

Zedd0202 2024. 2. 12. 14:36
반응형

 

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이 만족하지 못하는 상황) 

 

잘 정리된 이미지가 있어서 첨부!! 

https://medium.com/swift-india/fortify-code-with-runtime-checks-66619b43766f

 

# 번외 (안읽어도됨)  

위 이미지를 보고 일반적인 상황에서 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 - Debug/Relese상관없이 이렇게 나옴 / preconditionFailure - Debug 일때 이렇게 나옴
preconditionFailure - Relese일때 이렇게 나옴

 

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

반응형