The data your application receives from a remote API may be base64 encoded. It is the task of your application to base64 decode it. In this episode, I show you how easy that is using Swift.

What Is the Purpose of Base64 Encoding?

I want to start off by emphasizing that base64 encoding is not form of encryption and it shouldn't be used to protect data. While you and I cannot read base64 encoded data, it is trivial for a computer to decipher base64 encoded data.

Base64 encoding converts binary data to ASCII (American Standard Code for Information Interchange) characters. Data is base64 encoded to ensure data integrity, making it more likely the data remains intact when it is sent from point A to point B, for example, from a server to your application.

Base64 Encoding

Fire up Xcode and create a playground by choosing the Blank template from the iOS > Playground section if you want to follow along.

Decoding a Base64 Encoded String in Swift

Add an import statement for Foundation. Let's start with a string and base64 encode it. That's the starting point.

import Foundation

let string = "Welcome to Cocoacasts"

We convert the value stored in the string constant to a base64 encoded string in two steps. First, we convert the string to a Data object by calling data(using:) on the String object. Second, we ask the Data object for its base64 encoded string representation by invoking its base64EncodedString(options:) method.

import Foundation

let string = "Welcome to Cocoacasts"
let stringData = string.data(using: .utf8)!
let base64EncodedString = stringData.base64EncodedString()

The value stored in the base64EncodedString constant is what you would receive from, for example, an API. Let's now decode it back to a string.

Base64 Decoding

To decode the base64 encoded string, we convert the string to a Data object by calling data(using:) on the String object.

let base64EncodedData = base64EncodedString.data(using: .utf8)!

We use the Data object to create another Data object by passing it to init(base64Encoded:) initializer. The initializer is failable, so we use optional binding to unwrap it. The initialization fails if the base64 encoded string is invalid.

let base64EncodedData = base64EncodedString.data(using: .utf8)!

if let data = Data(base64Encoded: base64EncodedData) {
	
}

In the last step, we create a string from the Data object by passing it to the init(data:encoding:) initializer of the String struct. The result is the string we started with.

let base64EncodedData = base64EncodedString.data(using: .utf8)!

if let data = Data(base64Encoded: base64EncodedData) {
    print(String(data: data, encoding: .utf8))
}

From String to Data and Back

In this episode, you learned how a string is base64 encoded, for example, on the server, and base64 decoded, for example, on a mobile application that received the base64 encoded string.

import Foundation

let string = "Welcome to Cocoacasts"
let stringData = string.data(using: .utf8)!
let base64EncodedString = stringData.base64EncodedString()

let base64EncodedData = base64EncodedString.data(using: .utf8)!

if let data = Data(base64Encoded: base64EncodedData) {
    print(String(data: data, encoding: .utf8))
}

Creating a Decoder

If you need to perform this operation often, create a type that encapsulates this task to reduce code duplication. Let me show you how that works. We declare a string with name Base64Decoder.

struct Base64Decoder {
	
}

We also declare an enum named DecodingError that conforms to the Error protocol. The DecodingError enum defines one case, invalidData.

struct Base64Decoder {

    // MARK: - Types

    enum DecodingError: Swift.Error {

        // MARK: - Cases

        case invalidData

    }

}

To decode the base64 encoded string, we declare a method named decode(_:). The method is throwing and returns a String object.

struct Base64Decoder {

    // MARK: - Types

    enum DecodingError: Swift.Error {

        // MARK: - Cases

        case invalidData

    }

    func decode(_ base64EncodedString: String) throws -> String {
		
	}

}

We repeat the steps we went through earlier in this episode. We convert the base64 encoded string to a base64, UTF-8 encoded Data object, and use the Data object to create a base64 decoded Data object. That base64 decoded object is used to create a string. Because these conversions can fail, we use a guard statement and throw a DecodingError.invalidData error if something goes wrong.

struct Base64Decoder {

    // MARK: - Types

    enum DecodingError: Swift.Error {

        // MARK: - Cases

        case invalidData

    }

    func decode(_ base64EncodedString: String) throws -> String {
        guard
            let base64EncodedData = base64EncodedString.data(using: .utf8),
            let data = Data(base64Encoded: base64EncodedData),
            let result = String(data: data, encoding: .utf8)
        else {
            throw DecodingError.invalidData
        }

        return result
    }

}

Because the Base64Decoder struct neatly encapsulates the logic to decode a base64 encoded string, the resulting API is simple and easy to use.

do {
    let string = try Base64Decoder().decode(base64EncodedString)
} catch {
    print("Unable to Decode Base64 Encoded String \(error)")
}

What's Next?

Swift makes base64 encoding and base64 decoding fairly straightforward. Gone are the days when you needed a third party library to handle such tasks. Remember that base64 encoding isn't a form of encryption. It won't help you protect the data you are sending or receiving.