marmelroy / PhoneNumberKit

A Swift framework for parsing, formatting and validating international phone numbers. Inspired by Google's libphonenumber.
MIT License
5.1k stars 810 forks source link

XCode 16: Mutable capture of 'inout' parameter 'buffer' is not allowed in concurrently-executing code #797

Closed gijoecodes closed 3 weeks ago

gijoecodes commented 3 weeks ago

In Xcode 16, Swift introduced stricter concurrency checks. Specifically, mutable captures of inout parameters, like the buffer in your code, are not allowed in concurrently executing code to prevent potential data races.

The key issue is that the buffer parameter, which is an inout parameter to the closure, is being mutated inside a concurrent loop. Swift's new concurrency model is more strict about this to ensure thread safety.

Steps to reproduce

PhoneNumberKit > PhoneNumberKitCore > ParseManager lines 112 and 114

        var multiParseArray = [PhoneNumber](unsafeUninitializedCapacity: numberStrings.count) { buffer, initializedCount in
            DispatchQueue.concurrentPerform(iterations: numberStrings.count) { [buffer] index in
                let numberString = numberStrings[index]
                do {
                    let phoneNumber = try self.parse(numberString, withRegion: region, ignoreType: ignoreType)
                    **buffer.baseAddress!.advanced(by: index).initialize(to: phoneNumber)**
                } catch {
                    **buffer.baseAddress!.advanced(by: index).initialize(to: PhoneNumber.notPhoneNumber())**
                    hasError = true
                }
            }
            initializedCount = numberStrings.count
        }
Quick Fix

Replace lines 107 - 119 with

        // Create a temporary array to hold the parsed phone numbers.
        var tempArray = Array<PhoneNumber?>(repeating: nil, count: numberStrings.count)

        DispatchQueue.concurrentPerform(iterations: numberStrings.count) { index in
            let numberString = numberStrings[index]
            do {
                let phoneNumber = try self.parse(numberString, withRegion: region, ignoreType: ignoreType)
                tempArray[index] = phoneNumber
            } catch {
                tempArray[index] = PhoneNumber.notPhoneNumber()
                hasError = true
            }
        }

        // After the concurrent operation, populate the buffer.
        var multiParseArray = [PhoneNumber](unsafeUninitializedCapacity: numberStrings.count) { buffer, initializedCount in
            for (index, phoneNumber) in tempArray.enumerated() {
                buffer.baseAddress!.advanced(by: index).initialize(to: phoneNumber!)
            }
            initializedCount = numberStrings.count
        }

Environment

iOS Application. Installed with Swift Package Manager

bguidolim commented 3 weeks ago

Please update PhoneNumberKit