Wednesday, January 05, 2022

Boxing an Array in Swift

Swift Arrays

If you are reading this you probably have the need to work with an array returned from a function or a copy of an array. I have been porting a complex data transfer object (DTO) from Java to Swift. The Java implementation of this DTO has many violations the Law of Demeter.

The Swift implementation needs to resemble the Java implementation so that changes to one code base will be easier to make in the other code base. This requirement lead me to creating a Boxed Array which I named "List".  Before critiquing the name of the class or the method names, this is to make the port from Java as simple as possible FOR ME. You can make List implement the methods of a Swift Array if you want, or make Protocols for your own "collection classes".

This example of Boxing / Wrapping a Swift Array is as simple as possible. This includes the ability to serialize the List class to be the same as an Array when serialized to JSON.



//Geoffrey Slinker - Just keep my name here please.
import Foundation


public class List<T : Equatable & Codable> : Sequence, Codable
{
    var container : [T] = []
    
    public init() {
        
    }
    
    //------------------------------------------------------------------------
    //JSON Serialization
    required public init(from decoder:Decoder) throws {
        try container = [T](from: decoder)
    }
    
    public func encode(to encoder: Encoder) throws {
        try container.encode(to: encoder)
    }
    //------------------------------------------------------------------------
    
    public func makeIterator() -> ListIterator<T> {
        return ListIterator(self)
    }
    
    public func append(_ newElement:T) {
        container.append(newElement)
    }
    
    public func get(_ at : Int) -> T? {
        return container[at]
    }
    
    public func remove(at : Int) {
        container.remove(at: at)
    }
    
    public func remove(_ element:T) {
        if let index = container.firstIndex(where: {$0 == element}) {
            container.remove(at: index)
        }
    }
    
    public func clear() {
        container.removeAll()
    }
    
    public var count : Int {
        get {
            return container.count
        }
    }
}

public struct ListIterator<T : Equatable & Codable> : IteratorProtocol
{
    let list : List<T>
    var currentPosition = 0
    
    init(_ list: List<T>) {
        self.list = list
    }
    
    public mutating func next() -> T? {
        if(currentPosition < list.count) {
            let result = list.get(currentPosition)
            currentPosition += 1
            return result
        }
        return nil
    }
    
}

Test Code

import XCTest

class ListTests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }


    func testJson() throws {
        let myArray = [0, 1, 2, 3]
        
        let arrayData = try! JSONSerialization.data(withJSONObject: myArray, options: [])
        let arrayJson = String(data: arrayData, encoding: String.Encoding.utf8)
        
        
        let myList : List<Int> = List<Int>()
        myList.append(0)
        myList.append(1)
        myList.append(2)
        myList.append(3)
        

        let listData = try! JSONEncoder().encode(myList)
        let listJson = String(data: listData, encoding: String.Encoding.utf8)
        
        XCTAssertEqual(arrayJson, listJson)
        

        let anotherList : List<Int>? = try JSONDecoder().decode(List<Int>.self, from: listData)
        XCTAssert(anotherList!.count == 4)
        XCTAssertEqual(anotherList?.get(0), 0)
        XCTAssertEqual(anotherList?.get(1), 1)
        XCTAssertEqual(anotherList?.get(2), 2)
        XCTAssertEqual(anotherList?.get(3), 3)
        
        let yetAnotherList : List<Int>? = try JSONDecoder().decode(List<Int>.self, from: arrayData)
        XCTAssert(yetAnotherList!.count == 4)
        XCTAssertEqual(yetAnotherList?.get(0), 0)
        XCTAssertEqual(yetAnotherList?.get(1), 1)
        XCTAssertEqual(yetAnotherList?.get(2), 2)
        XCTAssertEqual(yetAnotherList?.get(3), 3)
    }

}

No comments: