-1
extension String {

        /// Create NSData from hexadecimal string representation
        ///
        /// This takes a hexadecimal representation and creates a NSData object. Note, if the string has any spaces, those are removed. Also if the string started with a '<' or ended with a '>', those are removed, too. This does no validation of the string to ensure it's a valid hexadecimal string
        ///
        /// The use of `strtoul` inspired by Martin R at http://stackoverflow.com/a/26284562/1271826
        ///
        /// - returns: NSData represented by this hexadecimal string. Returns nil if string contains characters outside the 0-9 and a-f range.

        func dataFromHexadecimalString() -> NSData? {
            let trimmedString = self.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "<> ")).stringByReplacingOccurrencesOfString(" ", withString: "")

            // make sure the cleaned up string consists solely of hex digits, and that we have even number of them

            var error: NSError?
            let regex: NSRegularExpression?
            do {
                regex = try NSRegularExpression(pattern: "^[0-9a-f]*$", options: .CaseInsensitive)
            } catch let error1 as NSError {
                error = error1
                regex = nil
            }
            let found = regex?.firstMatchInString(trimmedString, options: [], range: NSMakeRange(0, trimmedString.characters.count))
            if found == nil || found?.range.location == NSNotFound || trimmedString.characters.count % 2 != 0 {
                return nil
            }

            // everything ok, so now let's build NSData

            let data = NSMutableData(capacity: trimmedString.characters.count / 2)

            for var index = trimmedString.startIndex; index < trimmedString.endIndex; index = index.successor().successor() {
                let byteString = trimmedString.substringWithRange(Range<String.Index>(start: index, end: index.successor().successor()))
                let num = UInt8(byteString.withCString { strtoul($0, nil, 16) })
                data?.appendBytes([num] as [UInt8], length: 1)
            }

            return data
        }
    }

Trying to convert the for loop in the above code snippit to swift 3, currently written in swift 2.3 and having trouble. The error is : "C-style for statement has been removed in Swift 3"

The below for loop is what happened after I pressed to "convert to swift 3" button on xcode.

   for var index = trimmedString.startIndex; index < trimmedString.endIndex; index = <#T##Collection corresponding to your index##Collection#>.index(after: <#T##Collection corresponding to `index`##Collection#>.index(after: index)) {
            let byteString = trimmedString.substring(with: (index ..< <#T##Collection corresponding to your index##Collection#>.index(after: <#T##Collection corresponding to `index`##Collection#>.index(after: index))))
            let num = UInt8(byteString.withCString { strtoul($0, nil, 16) })
            data?.append([num] as [UInt8], length: 1)
        }

    extension NSData {

        /// Create hexadecimal string representation of NSData object.
        ///
        /// - returns: String representation of this NSData object.

        func hexadecimalString() -> String {
            let string = NSMutableString(capacity: length * 2)
            var byte: UInt8 = 0

            for i in 0 ..< length {
                getBytes(&byte, range: NSMakeRange(i, 1))
                string.appendFormat("%02x", byte)
            }

            return string as String
        }
    }

Also the for loop in the above code snippet isn't working in swift 3. How to re-write this for swift 3? The error for this one is: "Cannot invoke 'copyBytes' with an argument list of type '(to: inout UInt8, from: NSRange)'"

Any help appreciated. I use these functions to build a special url for a third party service I am using but struggling to update this complex syntax to swift 3.

Screenshot of the errors here

  • A search for `[swift] dataFromHexadecimalString` quickly shows this answer http://stackoverflow.com/a/26502285/1187415, which has been updated for Swift 3 some time ago. – Martin R Dec 25 '16 at 12:38

1 Answers1

2

There exists many threads explaining how to convert C-style for-loops, or how to work with Data in Swift 3. (In Swift 3, you'd better work with Data rather than NSData.) You just have need to find and combine them.

extension String {

    func dataFromHexadecimalString() -> Data? {
        let trimmedString = self.trimmingCharacters(in: CharacterSet(charactersIn: "<> ")).replacingOccurrences(of: " ", with: "")

        //`NSRegularExpression(pattern:options:)` will not throw error for a valid pattern & options.
        //And you need to use `utf16.count` when working with `NSRegularExpression`.
        let regex = try! NSRegularExpression(pattern: "^[0-9a-f]*$", options: .caseInsensitive)
        let found = regex.firstMatch(in: trimmedString, range: NSMakeRange(0, trimmedString.utf16.count))
        if found == nil || found!.range.location == NSNotFound || trimmedString.characters.count % 2 != 0 {
            return nil
        }

        //In Swift 3, working with `Data` is easier than `NSData` in most cases.
        var data = Data(capacity: trimmedString.characters.count / 2)

        //Generally, `for INIT; COND; UPDATE {...}` can be re-written with `INIT; while COND {...; UPDATE}`
        var index = trimmedString.startIndex
        while index < trimmedString.endIndex {
            let nextIndex = trimmedString.characters.index(index, offsetBy: 2)
            let byteString = trimmedString.substring(with: index..<nextIndex)
            let num = UInt8(byteString, radix: 16)!
            data.append(num)

            index = nextIndex
        }

        return data
    }
}

extension Data {

    func hexadecimalString() -> String {
        var string = ""
        string.reserveCapacity(count * 2)

        //You have no need to use `getBytes(_:range:)` when you use each byte of Data one by one.
        for byte in self {
            string.append(String(format: "%02X", byte))
        }

        return string
    }
}

Some parts can be re-written in more Swifty way, but I have kept some basic structure of your code to make it easy to compare two codes.

OOPer
  • 47,149
  • 6
  • 107
  • 142