12 Replies
      Latest reply on Dec 2, 2019 5:21 AM by cristib
      flarosa Level 1 Level 1 (0 points)

        Hi,

         

        My code contains the following function:

         

          func md5(string: String) -> String {
          let messageData = string.data(using:.utf8)!
          var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
                    
          _ = digestData.withUnsafeMutableBytes {digestBytes in
               messageData.withUnsafeBytes {messageBytes in
               CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
               }
          }
                    
               let md5Hex = digestData.map { String(format: "%02hhx", $0) }.joined()
               return md5Hex
          }
        
        

         

        This worked fine and compiled with no errors or warnings in prior versions of Swift. After upgrading to Swift 5, I get a deprecation warning on both "withUnsafeMutableBytes" and "withUnsafeBytes".

         

        I've stared at the warning, read the documentation, and even tried re-keying the code allowing Xcode to fill in what it thinks is the correct syntax, but I cannot resolve the problem. I do not understand what the complaint is, nor what I specifically need to type here to avoid the warnings.

         

        Can someone please explain this, or at least, tell me what I need to write to get it correct?

         

        Thanks.

        • Re: withUnsafeMutableBytes deprecated in Swift 5?
          flarosa Level 1 Level 1 (0 points)

          The root of the problem seems to be that I need a value of type "unsigned char *" to pass as the thrird parameter to the CC_MD5 function, but what I end up with is of type UnsafeMutableRawPointer? instead.

           

          I tried using as! to cast it, but the compiler says the cast will always fail.

          • Re: withUnsafeMutableBytes deprecated in Swift 5?
            flarosa Level 1 Level 1 (0 points)

            After much trial and error, I arrived at this expression:

             

                        _ = digestData.withUnsafeMutableBytes { (digestBytes) -> Bool in
                            messageData.withUnsafeBytes({ (messageBytes) -> Bool in
                                _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
                                return true
                            })
                        }
            
            

            It compiles without warning and seems to work, but I'm not confident that I understand why, or if it is correct.

             

            In particular I'm not sure why I had to return a value even though I need no return value. Without that, the compiler seems to think I want the old deprecated function.

              • Re: withUnsafeMutableBytes deprecated in Swift 5?
                OOPer Level 8 Level 8 (5,445 points)

                In particular I'm not sure why I had to return a value even though I need no return value.

                 

                As far as I tried, this code compiles without errors or warnings:

                        _ = digestData.withUnsafeMutableBytes { digestBytes in
                            messageData.withUnsafeBytes { messageBytes in
                                CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
                            }
                        }
                
                

                A slight change to this code caused some error such as the compiler seems to think I want the old deprecated function, but returning a value does not seem to be mandatory.

                  • Re: withUnsafeMutableBytes deprecated in Swift 5?
                    flarosa Level 1 Level 1 (0 points)

                    You're right. This does compile without the warning. The only difference between what you wrote and my original code is the single line in the body of the inner block.

                     

                    I don't really understand what's going on here, but I'd like to. CC_MD5 is an old C function, so it doesn't have multiple definitions. It always accepts the same parameters and returns the same kind of value.

                     

                    It appears that the data types of digestBytes and messageBytes are different depending on which expression is used. If I put both the new and old expressions inside the block, in either order, the old expression generates a compiler error: "Cannot convert value of type UnsafeRawBufferPointer to expected UnsafeRawPointer?. But if I put just the old expression, it works, although with warnings.

                     

                    What is Swift doing here? How is it deciding what types to make each variable?

                      • Re: withUnsafeMutableBytes deprecated in Swift 5?
                        OOPer Level 8 Level 8 (5,445 points)

                        What is Swift doing here? How is it deciding what types to make each variable?

                         

                        The details of type inference in Swift is not clearly documented and hard to predict, especially when closure is involved.

                         

                        One thiing sure is that you can explicity annotate types if you want to avoid such unpredictable behaviours...

                                digestData.withUnsafeMutableBytes { (digestBytes: UnsafeMutableRawBufferPointer)->Void in
                                    messageData.withUnsafeBytes { (messageBytes: UnsafeRawBufferPointer)->Void in
                                        CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes.bindMemory(to: UInt8.self).baseAddress)
                                    }
                                }
                        
                        
                    • Re: withUnsafeMutableBytes deprecated in Swift 5?
                      cristib Level 1 Level 1 (0 points)

                      This actually helped me! Adding `bindMemory(to: UInt8.self).baseAddress` to the `digestBytes` saved my day! Thanks!

                    • Re: withUnsafeMutableBytes deprecated in Swift 5?
                      eskimo Apple Staff Apple Staff (12,455 points)

                      This came up recently on Swift Forums.  Check out this thread, which has a bunch of good advice.

                      In this case I’m going to specifically recommend the code that I posted there.  Using an array for the digest makes sense because your final goal is to create a hex representation (md5Hex) and thus there’s no reason to use any of the alternative techniques.  These alternatives have benefits (like avoiding an extra allocation), but they won’t benefit you.


                      OOPer wrote:

                      The details of type inference in Swift is not clearly documented and hard to predict, especially when closure is involved.

                      Indeed.  In this specific case, however, the difference in behaviour is tied to a well-known Swift oddity.  To understand this, you must first realise that Swift 5 introduced a new API on Data that uses the Unsafe[Mutable]RawBufferPointer.  So now you have two methods in play:

                      func withUnsafe[Mutable]Bytes<ResultType, ContentType>(_ body: (Unsafe[Mutable]Pointer<ContentType>) throws -> ResultType) rethrows -> ResultType
                      func withUnsafe[Mutable]Bytes<ResultType>             (_ body: (Unsafe[Mutable]RawBufferPointer    ) throws -> ResultType) rethrows -> ResultType

                      with the first one being deprecated.

                      Now consider this code snippet:

                      digestData.withUnsafeMutableBytes { digestBytes -> Void in
                      //        ^
                      // 'withUnsafeMutableBytes' is deprecated: use `withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R` instead
                          messageData.withUnsafeBytes { messageBytes in
                              _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes)
                          }
                      }

                      Both closures are a single statement, and Swift will do type inferencing ‘through’ a single-statement closure.  Thus, it can use the type of the third parameter of CC_MD5 to infer the type of digestBytes as an UnsafeMutablePointer<UInt8>.  That does not match Unsafe[Mutable]RawBufferPointer, so you end up using the original deprecated API, and hence the deprecation warning.

                      Contrast this with this snippet:

                      digestData.withUnsafeMutableBytes { digestBytes -> Void in
                          messageData.withUnsafeBytes { messageBytes in
                              _ = CC_MD5(messageBytes.baseAddress, CC_LONG(messageData.count), digestBytes)
                              //                                                               ^
                              // Cannot convert value of type 'UnsafeMutableRawBufferPointer' to expected argument type 'UnsafeMutablePointer<UInt8>?'
                              print("Hello Cruel World")
                          }
                      }

                      All I’ve done is add a call to print in the inner closure.  This makes the closure a multi-statement closure, which disables type inferencing.  Swift then chooses the Unsafe[Mutable]RawBufferPointer method, resulting in the type mismatch error.

                      Share and Enjoy

                      Quinn “The Eskimo!”
                      Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                      let myEmail = "eskimo" + "1" + "@apple.com"

                      • Re: withUnsafeMutableBytes deprecated in Swift 5?
                        sofapps Level 1 Level 1 (0 points)

                        Depending on what you're doing, you could easily convert your code in something like that:

                         

                          _ = d.withUnsafeBytes({ (body: UnsafeRawBufferPointer) -> Void in
                          CC_MD5(body.baseAddress, CC_LONG(self.count), &digest)
                          })

                         

                        • Re: withUnsafeMutableBytes deprecated in Swift 5?
                          aiden.zhang Level 1 Level 1 (0 points)

                          Excuse me.

                          This is my code:

                           

                          // 'withUnsafeMutableBytes' is deprecated: use 'withUnsafeMytableBytes(_:(UnsafeMutableRawBufferPointer) throws -> R) rethrows -> 
                          // R'  instead
                          var data = Data(count: length)
                          let result data.withUnsafeMutableBytes {
                               return SecRandomCopyBytes(kSecRandomDefault, length, $0)
                          }
                          
                          

                          What should I do to fix this warning?

                          I have no idear, Please.