본문 바로가기

Swift

[Swift] Foundation Framework 에서 NSString -> String 의 과정

지난 글 보기

 

[iOS] NSTaggedPointerString Crash

Foundation +[NSUserDefaults(NSUserDefaults_NSURLExtras) _web_defaultsDidChange] 현재 회사에서 서비스 중인 앱에서 오랜 기간 동안 리포트되고 있던 크래시가 있습니다. Fatal Exception: NSInvalidArgumentE..

darth-vader.tistory.com

 

지난 글에서 크래시를 해결하면서 작성했던 코드의 동작 원리 즉, NSString 에서 String 으로 변환될 때 왜 nil 이 아니라 빈 문자열로 대체되서 반환이 되는가에 대해 원리를 파악하고자 합니다.

 

인증 번호를 입력할 때 크래시가 발생하는 케이스

일단 우리가 전화번호를 통해 본인 인증을 하고자 할 때 iOS 13 에서는 OS 가 메시지에서 인증 번호를 가져와 사용자에게 자동 완성을 제공합니다. 이때 자동 완성 영역을 터치하면 OS 는 NSString.replacingCharacters(in range: NSRange, with replacement: String) 메서드를 호출합니다. 일단 호출하는 클래스가 NSString 이기 때문에 "Objective-C 코드 베이스에서 작동하는 게 아닐까?" 라고 추론했습니다.


NSString+Extension.swift

extension NSString {
    
    class func swizzleReplacingCharacters() {
        let originalMethod = class_getInstanceMethod(
            NSString.self, #selector(NSString.replacingCharacters(in:with:)))
        
        let swizzledMethod = class_getInstanceMethod(
            NSString.self, #selector(NSString.swizzledReplacingCharacters(in:with:)))
        
        guard let original = originalMethod, let swizzled = swizzledMethod else {
            return
        }
        
        method_exchangeImplementations(original, swizzled)
    }
    
    func swizzledReplacingCharacters(in range: NSRange, with replacement: String) -> String {
        return self.swizzledReplacingCharacters(in: range, with: replacement)
    }
}

그리고 해당 메서드는 Swift 에서 Swizzling 될 때 String 형태로 변환되는 것 같습니다.

 

해당 내용은 Foundation Framework 의 NSString.swift 파일에서 찾을 수 있었습니다.

 

Foundation/NSString.swift

extension NSString : _StructTypeBridgeable {
    public typealias _StructType = String
    
    public func _bridgeToSwift() -> _StructType {
        return _StructType._unconditionallyBridgeFromObjectiveC(self)
    }
}

Objective-C 의 NSString 에서 Swift 로 브릿징을 하면 unconditionallyBridgeFromObjectiveC 를 호출하는 것 같습니다.

 

 

Foundation/String.swift

static public func _unconditionallyBridgeFromObjectiveC(_ source: _ObjectType?) -> String {
    if let object = source {
        var value: String?
    _conditionallyBridgeFromObjectiveC(object, result: &value)
        return value!
    } else {
        return ""
    }
}

unconditionallyBridgeFromObjectiveC 메서드 내부를 보면 인자로 받은 source 라는 객체를 if let 을 통해 내부에서 옵셔널 바인딩을 하고 있습니다. 그래서 NSString 으로부터 전달받은 인자가 nil 이라면 내부의 옵셔널 바인딩을 통해 빈 문자열을 반환하고 nil 이 아니라면 String.swift 내부에 정의된 _contionallyBridgeFromObjectiveC 메서드에 의해서 String 타입으로 변환된 value 를 반환합니다.

 

만약 틀린 내용이 있으면 지적해주세요. 해당 내용을 포스팅에 반영하도록 하겠습니다.

 

오늘 포스팅을 작성하는 데 도움을 주신 두 분이 계십니다.

  • 야곰 님께서 NSString 이 Swift 의 String 으로 브릿징될 때 빈 문자열로 넘어갈 것이라고 힌트를 주셨습니다.
 

yagom's blog

야곰의 프로그래밍 블로그입니다. iOS, Swift, Objective-C, C에 대해 이야기합니다.

blog.yagom.net

  • 라이노 님께서 Foundation Framework 내부를 살펴보라 조언해 주셨습니다.
 

Rhyno's DevLife log

iOS,Swift,Problem Solving, Software Engineering...

jcsoohwancho.github.io

 

apple/swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence - apple/swift-corelibs-foundation

github.com