Swift · · 13 min read

Design Patterns in Swift #3: Facade and Adapter

Design Patterns in Swift #3: Facade and Adapter

This tutorial is the third installment in our series on design patterns. I started
this series with a tutorial examining two examples of patterns in the “creational” category: factory method and singleton. I then discussed two examples of patterns in the “behavioral” category: observer and memento.

In this tutorial, I’ll explain two examples of patterns in the “structural” category: facade and adapter. I urge you to review my first two posts mentioned above so you can familiarize yourself with the concept of software design patterns. Beyond a brief reminder today of what constitutes a design pattern, I’m not going to regurgitate all the definitions again. All the information you need to get up to speed is in my first two tutorials, here and here.

Let’s briefly review some general definitions of design patterns here and in the next few sections. There are 23 classic software development design patterns probably first identified, collected, and explained all in one place by the “Gang of Four” (“GoF”), Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in their seminal book, “Design Patterns: Elements of Reusable Object-Oriented Software.”. Remember that today we’ll focus on two of these patterns, facade and adapter, which fall into what the GoF calls the “structural” category.

Protocol-oriented code with value semantics

You’ll still find that many tutorials about design patterns contain sample code based on object-oriented programming (OOP) principles, reference semantics, and reference types (classes). I am endeavoring to create a series of tutorials on design patterns that are mostly based in protocol-oriented programming principles (POP), value semantics, and value types (structs). If you’ve reviewed the first two articles in this series, I hope you followed my advice and familiarized yourselves with POP versus OOP and reference semantics versus value semantics. If not, I strongly encourage you to get up to speed on these topics. This tutorial is solely based in POP and value semantics.

Design Patterns

Design patterns are an extremely important tool with which developers can manage complexity. It’s best to conceptualize them as generally templated techniques, each tailored to solving a corresponding, recurring, and readily identifiable problem. Look at them as a list of best practices you would use for coding scenarios that you see over and over again. To make this definition tangible, think of how many times you’ve either used code or written code that conforms to the observer design pattern.

In observer, the subject instance, usually a single critical resource, broadcasts notifications about a change in its state to many observer instances that depend on that resource. Interested observers must tell the subject instance that they’re interested in receiving notifications, in other words, they must subscribe to get notifications. Encoding iOS push notifications, where users must opt into receiving messages, is a great example of observer.

Design pattern categories

The GoF organized their 23 design patterns into three categories, “creational,” “behavioral,” and “structural.” This tutorial discusses two patterns in the structural category. Consider the word “structure,”

“something arranged in a definite pattern of organization” and “the aggregate of elements of an entity in their relationships to each other.”

– https://www.merriam-webster.com/dictionary/structure

Structural design patterns are meant to help you to clearly define the purpose of a segment of code and clearly specify how other code interacts with that segment. Most patterns in this category enable you to simplify the use of your code. We can usually simplify use of code by creating an easily readable interface to that code. Since pieces/segments of code don’t exist in a vacuum, providing a good interface to a code segment should obviously and cleanly define the possible relationships that can be built between that segment and other segments.

The facade design pattern

The word “facade” is defined as “any face of a building given special architectural treatment” and “a false, superficial, or artificial appearance or effect.”

– https://www.merriam-webster.com/dictionary/facade

In most cases, we use the facade pattern to create one simple interface to a group of other, possibly many, and usually complex, interfaces. You have probably already created what are commonly called “wrappers” where you built a simple interface to a complex codebase with the purpose of simplifying the use of that codebase.

Use case for facade design pattern app

My facade example playground, available on GitHub, showcases how this pattern can create one simple interface to the sandboxed file system available to each iOS app. The iOS file system is a huge OS subsystem, allowing you to create, read, delete, move, rename, and copy files and directories; allowing you to get (and sometimes set) meta data about files and directories, e.g., list the files in a directory; allowing you to check the status of files/directories, e.g., determine if a file is writable; and, providing you with the names of predefined directories in which Apple prefers that you work. Note that you can do much more than what I just listed.

Since the iOS file system is such a grand topic with many different features and functions, it is an ideal candidate for using the facade design pattern to simplify its usage. A facade interface allows you to leave out functionality you don’t need and that may clutter your code. Conversely, a facade interface allows you to specify only the functionality you need for a particular app, or in my case, to limit functionality to what I have found to be only the features I have used over time, and thus make my facade reusable, extensible, and maintainable for many of my apps.

I used protocol-oriented programming and value semantics for dividing and conquering major features of the iOS file system into reusable and extensible units: protocols and protocol extensions.

I then composed four protocols into one struct that represents a sandboxed iOS directory available to all iOS apps (see also here). Since you’re likely to run across the topics of POP and value semantics more and more, note that the terms composed and composition are synonymous herein.

Note that I left Swift error handling and more generic error checking out of the code shown below solely for didactic purposes, i.e., so you can more easily concentrate only on understanding use of the facade pattern.

Sample code for the facade design pattern

Let’s walk through my code. Make sure you follow along with my code in the playground hosted on GitHub. Here’s a list of predefined directories in which Apple prefers you do much of your iOS app’s work:

enum AppDirectories : String {
    case Documents = "Documents"
    case Inbox = "Inbox"
    case Library = "Library"
    case Temp = "tmp"
}

By constraining my file manipulation code to these known directories, I control complexity, simplify, and stay within the bounds of the Human Interface Guidelines.

Before looking at my core code for file manipulation, let’s first look at my facade design pattern-based interface as that’s the topic of this tutorial. I created the iOSAppFileSystemDirectory struct as a simple and readable interface to common file system features available for each of the directories specified in my AppDirectories enum. Yes, I could get involved with things like the creation of symbolic links or the use of fine grained manipulation of individual files using the FileHandle class, but I almost never use these features, and most importantly, I’m deliberately keeping things simple.

I’ve created a facade composed of four protocols (I know you see three immediately below, but one of the protocols inherits from another):

struct iOSAppFileSystemDirectory : AppFileManipulation, AppFileStatusChecking, AppFileSystemMetaData {
    
    let workingDirectory: AppDirectories

    init(using directory: AppDirectories) {
        self.workingDirectory = directory
    }

    func writeFile(containing text: String, withName name: String) -> Bool {
        return writeFile(containing: text, to: workingDirectory, withName: name)
    }
    
    func readFile(withName name: String) -> String {
        return readFile(at: workingDirectory, withName: name)
    }
    
    func deleteFile(withName name: String) -> Bool {
        return deleteFile(at: workingDirectory, withName: name)
    }
    
    func showAttributes(forFile named: String) -> Void {
        let fullPath = buildFullPath(forFileName: named, inDirectory: workingDirectory)
        let fileAttributes = attributes(ofFile: fullPath)
        for attribute in fileAttributes {
            print(attribute)
        }
    }
    
    func list() {
        list(directory: getURL(for: workingDirectory))
    }
    
} // end struct iOSAppFileSystemDirectory

Here’s some code that tests my iOSAppFileSystemDirectory struct:

var iOSDocumentsDirectory = iOSAppFileSystemDirectory(using: .Documents)

iOSDocumentsDirectory.writeFile(containing: "New file created.", withName: "myFile3.txt")
iOSDocumentsDirectory.list()
iOSDocumentsDirectory.readFile(withName: "myFile3.txt")
iOSDocumentsDirectory.showAttributes(forFile: "myFile3.txt")
iOSDocumentsDirectory.deleteFile(withName: "myFile3.txt")

Here’s the output to console from executing the previous code snippet in my playground:

----------------------------
LISTING: /var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Facade-Design-Pattern-1C4BD3E3-E23C-4991-A344-775D5585D1D7/Documents

File: "myFile3.txt"
File: "Shared Playground Data"

----------------------------

File created with contents: New file created.

(key: __C.FileAttributeKey(_rawValue: NSFileType), value: NSFileTypeRegular)
(key: __C.FileAttributeKey(_rawValue: NSFilePosixPermissions), value: 420)
(key: __C.FileAttributeKey(_rawValue: NSFileSystemNumber), value: 16777223)
(key: __C.FileAttributeKey(_rawValue: NSFileExtendedAttributes), value: {
    "com.apple.quarantine" = <30303836 3b356238 36656364 373b5377 69667420 46616361 64652044 65736967 6e205061 74746572 6e3b>;
})
(key: __C.FileAttributeKey(_rawValue: NSFileReferenceCount), value: 1)
(key: __C.FileAttributeKey(_rawValue: NSFileSystemFileNumber), value: 24946094)
(key: __C.FileAttributeKey(_rawValue: NSFileGroupOwnerAccountID), value: 20)
(key: __C.FileAttributeKey(_rawValue: NSFileModificationDate), value: 2018-08-29 18:58:31 +0000)
(key: __C.FileAttributeKey(_rawValue: NSFileCreationDate), value: 2018-08-29 18:58:31 +0000)
(key: __C.FileAttributeKey(_rawValue: NSFileSize), value: 17)
(key: __C.FileAttributeKey(_rawValue: NSFileExtensionHidden), value: 0)
(key: __C.FileAttributeKey(_rawValue: NSFileOwnerAccountID), value: 502)

File deleted.

Let’s briefly discuss the protocols that were used to compose iOSAppFileSystemDirectory. Here we have the AppDirectoryNames protocol and protocol extension which compartmentalize the retrieval of full paths of type URL to the Apple-predefined directories as specified in my AppDirectories enum:

protocol AppDirectoryNames {
    
    func documentsDirectoryURL() -> URL
    
    func inboxDirectoryURL() -> URL
    
    func libraryDirectoryURL() -> URL
    
    func tempDirectoryURL() -> URL
    
    func getURL(for directory: AppDirectories) -> URL
    
    func buildFullPath(forFileName name: String, inDirectory directory: AppDirectories) -> URL
    
} // end protocol AppDirectoryNames

extension AppDirectoryNames {
    
    func documentsDirectoryURL() -> URL {
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    }
    
    func inboxDirectoryURL() -> URL {
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(AppDirectories.Inbox.rawValue) // "Inbox")
    }
    
    func libraryDirectoryURL() -> URL {
        return FileManager.default.urls(for: FileManager.SearchPathDirectory.libraryDirectory, in: .userDomainMask).first!
    }
    
    func tempDirectoryURL() -> URL {
        return FileManager.default.temporaryDirectory
        //urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(AppDirectories.Temp.rawValue) //"tmp")
    }
    
    func getURL(for directory: AppDirectories) -> URL {
        switch directory {
        case .Documents:
            return documentsDirectoryURL()
        case .Inbox:
            return inboxDirectoryURL()
        case .Library:
            return libraryDirectoryURL()
        case .Temp:
            return tempDirectoryURL()
        }
    }
    
    func buildFullPath(forFileName name: String, inDirectory directory: AppDirectories) -> URL {
        return getURL(for: directory).appendingPathComponent(name)
    }
} // end extension AppDirectoryNames

AppFileStatusChecking is my protocol and protocol extension that encapsulate getting state data about files stored in directories as specified in my AppDirectories enum. By “state,” I mean determining if a file exists, if it’s writable, etc.

protocol AppFileStatusChecking {
    func isWritable(file at: URL) -> Bool
    
    func isReadable(file at: URL) -> Bool
    
    func exists(file at: URL) -> Bool
}

extension AppFileStatusChecking {
    func isWritable(file at: URL) -> Bool {
        if FileManager.default.isWritableFile(atPath: at.path) {
            print(at.path)
            return true
        }
        else {
            print(at.path)
            return false
        }
    }
    
    func isReadable(file at: URL) -> Bool {
        if FileManager.default.isReadableFile(atPath: at.path) {
            print(at.path)
            return true
        }
        else {
            print(at.path)
            return false
        }
    }
    
    func exists(file at: URL) -> Bool {
        if FileManager.default.fileExists(atPath: at.path) {
            return true
        }
        else {
            return false
        }
    }
} // end extension AppFileStatusChecking

AppFileSystemMetaData is my protocol and protocol extension that compartmentalize listing directory contents and getting extended file attributes, both from directories as specified in my AppDirectories enum:

protocol AppFileSystemMetaData {
    func list(directory at: URL) -> Bool
    
    func attributes(ofFile atFullPath: URL) -> [FileAttributeKey : Any]
}

extension AppFileSystemMetaData
{
    func list(directory at: URL) -> Bool {
        let listing = try! FileManager.default.contentsOfDirectory(atPath: at.path)
        
        if listing.count > 0 {
            print("\n----------------------------")
            print("LISTING: \(at.path)")
            print("")
            for file in listing {
                print("File: \(file.debugDescription)")
            }
            print("")
            print("----------------------------\n")
            
            return true
        }
        else {
            return false
        }
    }
    
    func attributes(ofFile atFullPath: URL) -> [FileAttributeKey : Any] {
        return try! FileManager.default.attributesOfItem(atPath: atFullPath.path)
    }
} // end extension AppFileSystemMetaData

Finally, the AppFileManipulation protocol and protocol extension encapsulate the reading, writing, deleting, renaming, moving, copying, and changing the file extension of files located in directories as specified in my AppDirectories enum:

protocol AppFileManipulation : AppDirectoryNames {
    func writeFile(containing: String, to path: AppDirectories, withName name: String) -> Bool
    
    func readFile(at path: AppDirectories, withName name: String) -> String
    
    func deleteFile(at path: AppDirectories, withName name: String) -> Bool
    
    func renameFile(at path: AppDirectories, with oldName: String, to newName: String) -> Bool
    
    func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool
    
    func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool
    
    func changeFileExtension(withName name: String, inDirectory: AppDirectories, toNewExtension newExtension: String) -> Bool
}

extension AppFileManipulation {
    func writeFile(containing: String, to path: AppDirectories, withName name: String) -> Bool {
        let filePath = getURL(for: path).path + "/" + name
        let rawData: Data? = containing.data(using: .utf8)
        return FileManager.default.createFile(atPath: filePath, contents: rawData, attributes: nil)
    }
    
    func readFile(at path: AppDirectories, withName name: String) -> String {
        let filePath = getURL(for: path).path + "/" + name
        let fileContents = FileManager.default.contents(atPath: filePath)
        let fileContentsAsString = String(bytes: fileContents!, encoding: .utf8)
        print("File created with contents: \(fileContentsAsString!)\n")
        return fileContentsAsString!
    }
    
    func deleteFile(at path: AppDirectories, withName name: String) -> Bool {
        let filePath = buildFullPath(forFileName: name, inDirectory: path)
        try! FileManager.default.removeItem(at: filePath)
        print("\nFile deleted.\n")
        return true
    }
    
    func renameFile(at path: AppDirectories, with oldName: String, to newName: String) -> Bool {
        let oldPath = getURL(for: path).appendingPathComponent(oldName)
        let newPath = getURL(for: path).appendingPathComponent(newName)
        try! FileManager.default.moveItem(at: oldPath, to: newPath)
        
        // highlights the limitations of using return values
        return true
    }
    
    func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool {
        let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory)
        let destinationURL = buildFullPath(forFileName: name, inDirectory: directory)
        // warning: constant 'success' inferred to have type '()', which may be unexpected
        // let success =
        try! FileManager.default.moveItem(at: originURL, to: destinationURL)
        return true
    }
    
    func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) -> Bool {
        let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory)
        let destinationURL = buildFullPath(forFileName: name, inDirectory: directory)
        try! FileManager.default.copyItem(at: originURL, to: destinationURL)
        return true
    }
    
    func changeFileExtension(withName name: String, inDirectory: AppDirectories, toNewExtension newExtension: String) -> Bool {
        var newFileName = NSString(string:name)
        newFileName = newFileName.deletingPathExtension as NSString
        newFileName = (newFileName.appendingPathExtension(newExtension) as NSString?)!
        let finalFileName:String =  String(newFileName)
        
        let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory)
        let destinationURL = buildFullPath(forFileName: finalFileName, inDirectory: inDirectory)
        
        try! FileManager.default.moveItem(at: originURL, to: destinationURL)
        
        return true
    }
} // end extension AppFileManipulation

The adapter design pattern

The word “adapt” is defined as “to make fit (as for a new use) often by modification.”

– https://www.merriam-webster.com/dictionary/adapts

The word “adapter” is defined as “an attachment for adapting apparatus for uses not originally intended.”

– https://www.merriam-webster.com/dictionary/adapter

The adapter pattern is used so that an existing codebase (call it “A”) can work, without modifying the original “A” code, with other code (call it “B”) that may not be completely compatible with the existing, original codebase “A.” We can create some kind of adapter that allows “A” and “B” to work together despite their differences. Remember that the existing, original codebase “A” cannot be modified (either because it would break the code or because we don’t have the source).

Use case for adapter design pattern app

My adapter example playground, available on GitHub, showcases how we’ll use the iOS file system as a substrate on which to discuss and design an example of the adapter pattern. Let’s say we’ve got my iOS file system code from the preceding sections up above where all paths to directories and files are expressed as URL instances. Consider a scenario in which we’ve been given a huge chunk of code that also manipulates the iOS file system, but where all paths to directories and files are expressed as String path instances, and the URL-based code must be made to work with the String path-based code.

Sample code for the adapter design pattern

Let’s walk through my code. Make sure you follow along with my code in the playground hosted on GitHub. In order to concentrate solely on the adapter pattern, we’ll use slimmed-down versions of my AppDirectories enum and AppDirectoryNames protocol and protocol extension:

enum AppDirectories : String {
    case Documents = "Documents"
    case Temp = "tmp"
}

protocol AppDirectoryNames {
    func documentsDirectoryURL() -> URL
    
    func tempDirectoryURL() -> URL
}

extension AppDirectoryNames {
    func documentsDirectoryURL() -> URL {
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    }
    
    func tempDirectoryURL() -> URL {
        return FileManager.default.temporaryDirectory
    }
}

One technique we could use is to create a “dedicated” adapter, one which gives us string-based paths to directories as specified in AppDirectories and also gives us string-based paths to files stored in directories as specified in AppDirectories:

// A dedicated adapter
struct iOSFile : AppDirectoryNames {
    let fileName: URL
    var fullPathInDocuments: String {
        return documentsDirectoryURL().appendingPathComponent(fileName.absoluteString).path
    }
    var fullPathInTemporary: String {
        return tempDirectoryURL().appendingPathComponent(fileName.absoluteString).path
    }
    var documentsStringPath: String {
        return documentsDirectoryURL().path
    }
    var temporaryStringPath: String {
        return tempDirectoryURL().path
    }

    init(fileName: String) {
        self.fileName = URL(string: fileName)!
    }
}

Here’s some code that tests the iOSFile “dedicated” adapter — and note my one inline comment:

let iOSfile = iOSFile(fileName: "myFile.txt")
iOSfile.fullPathInDocuments
iOSfile.documentsStringPath

iOSfile.fullPathInTemporary
iOSfile.temporaryStringPath

// We STILL have access to URLs
// through protocol AppDirectoryNames.
iOSfile.documentsDirectoryURL()
iOSfile.tempDirectoryURL()

Here are my playground’s line-by-line annotations which appear to the right of each line of code, on the same line, representing runtime code values, corresponding to the previous code snippet — the annotations below correspond one-to-one to the lines of code above:

iOSFile
"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents/myFile.txt"
"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents"

"/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp/myFile.txt"
"/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp"

file:///var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents/
file:///Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp/

The technique I prefer is to design an adapter protocol that my string path-based code could adopt so that it could use String paths instead of URL paths.

// Protocol-oriented approach
protocol AppDirectoryAndFileStringPathNamesAdpater : AppDirectoryNames {
    
    var fileName: String { get }
    var workingDirectory: AppDirectories { get }

    func documentsDirectoryStringPath() -> String
    
    func tempDirectoryStringPath() -> String
    
    func fullPath() -> String
    
} // end protocol AppDirectoryAndFileStringPathAdpaterNames

extension AppDirectoryAndFileStringPathNamesAdpater {
   
    func documentsDirectoryStringPath() -> String {
        return documentsDirectoryURL().path
    }
    
    func tempDirectoryStringPath() -> String {
        return tempDirectoryURL().path
    }
    
    func fullPath() -> String {
        switch workingDirectory {
        case .Documents:
            return documentsDirectoryStringPath() + "/" + fileName
        case .Temp:
            return tempDirectoryStringPath() + "/" + fileName
        }
    }

} // end extension AppDirectoryAndFileStringPathNamesAdpater

struct AppDirectoryAndFileStringPathNames : AppDirectoryAndFileStringPathNamesAdpater {
    
    let fileName: String
    let workingDirectory: AppDirectories
    
    init(fileName: String, workingDirectory: AppDirectories) {
        self.fileName = fileName
        self.workingDirectory = workingDirectory
    }
    
} // end struct AppDirectoryAndFileStringPathNames

Here’s some code that tests my AppDirectoryAndFileStringPathNames struct, which adopts the AppDirectoryAndFileStringPathNamesAdpater adapter protocol (which is a descendent of the AppDirectoryNames protocol) — and note my two inline comments:

let appFileDocumentsDirectoryPaths = AppDirectoryAndFileStringPathNames(fileName: "myFile.txt", workingDirectory: .Documents)
appFileDocumentsDirectoryPaths.fullPath()
appFileDocumentsDirectoryPaths.documentsDirectoryStringPath()

// We STILL have access to URLs
// through protocol AppDirectoryNames.
appFileDocumentsDirectoryPaths.documentsDirectoryURL()

let appFileTemporaryDirectoryPaths = AppDirectoryAndFileStringPathNames(fileName: "tempFile.txt", workingDirectory: .Temp)
appFileTemporaryDirectoryPaths.fullPath()
appFileTemporaryDirectoryPaths.tempDirectoryStringPath()

// We STILL have access to URLs
// through protocol AppDirectoryNames.
appFileTemporaryDirectoryPaths.tempDirectoryURL()

Here are my playground’s line-by-line annotations which appear to the right of each line of code, on the same line, representing runtime code values, corresponding to the previous code snippet — the annotations below correspond one-to-one to the lines of code above:

AppDirectoryAndFileStringPathNames
"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents/myFile.txt"
"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents"



file:///var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents/

AppDirectoryAndFileStringPathNames
"/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp/tempFile.txt"
"/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp"



file:///Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp/

Conclusion

Not only do design patterns encourage code reuse, but can help you make your code consistent, readable, loosely coupled, and thus maintainable and extensible. When you identify recurring and generalized features in your apps, I encourage you to take your design pattern-based code and put it into frameworks so you write it once and use it many more times.

Thanks for joining me again here on AppCoda. Enjoy your work, keep on learning, and I’ll see you soon!

Read next