Search code examples
swiftclassstructbridge

Define struct that is treated like a class in Swift


In Swift a String structure is also treated as a class object like when using the NSCoder encodeObject(_:forKey:) method. I do know that String is directly bridged with the objective-c class, NSString, but is there a way to make a custom struct that behaves similarly? Perhaps bridge it to a custom class? I want to be able to do something like this:

struct SortedArray <Value: Comparable> {}

// Would I need to create a bridge between 
// SortedArray and NSSortedArray? Can I even do that?
class NSSortedArray <Value: Comparable> : NSObject, NSCoding {
    required init?(coder aDecoder: NSCoder) {}
    func encodeWithCoder(aCoder: NSCoder) {}
}

class MyClass : NSObject, NSCoding {
    private var objects: SortedArray<String> = SortedArray<String>()
    required init?(coder aDecoder: NSCoder) {
        guard let objects = aDecoder.decodeObjectForKey("objects") as? SortedArray<String> else { return nil }
        self.objects = objects
    }
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(objects, forKey: "objects")
    }
}

Solution

  • I found a working, elegant solution that works with an _ObjectiveCBridgeable struct that can be encoded by NSCoder; thanks to the reference that Martin R provided. Here is the library code I wrote for anyone interested. I can now do something like this:

    func init?(coder aDecoder: NSCoder) {
        guard let numbers = aDecoder.decodeObjectForKey("Numbers") as? SortedArray<Int> else { return nil }
        print(numbers) // Outputs "[1,3,5]"
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(SortedArray<Int>([1,5,3]), forKey: "Numbers")
    }
    

    SortedArray.swift

    //
    //  SortedArray.swift
    //  NoodleKit
    //
    //  Created by Thom Morgan on 8/15/16.
    //  Copyright © 2016 CocoaPods. All rights reserved.
    //
    
    // MARK: - ** Import Modules **
    
    import Foundation
    
    // MARK: - ** SortOrder Enumeration **
    
    /// Ascending or descending sort order enumerations
    public enum SortOrder : Int {
        case Ascending, Descending
    }
    
    // MARK: - ** SortedArray Structure **
    
    /// An array data structure that automatically places elements in order as
    /// they added to the collection.
    public struct SortedArray <Value: Comparable> : CollectionType, _ObjectiveCBridgeable, CustomStringConvertible {
    
        // MARK: - _ObjectiveCBridgeable
    
        /// Required typealias from the `_ObjectiveCBridgeable` private protocol
        public typealias _ObjectiveCType = NSSortedArray<Value>
    
        // MARK: - CollectionType
    
        public typealias Index = Int
        public typealias Generator = IndexingGenerator<SortedArray<Value>>
    
        public var startIndex: Index { return 0 }
        public var endIndex: Index { return values.count }
        public var range: Range<Index> { return 0 ..< values.count }
        public var count: Int { return values.count }
    
        // MARK: - CustomStringConvertible
    
        public var description: String { return "\(values)" }
    
        // MARK: - SortedArray
    
        /// The order in which to sort array elements.
        public var sortOrder: SortOrder {
            willSet { if sortOrder != newValue { values = values.reverse() } }
        }
    
        /// The elements of this array.
        public private (set) var values = [Value]()
    
        /// Whether or not to allow duplicate elements to be added to this array.
        public var uniqueElements: Bool = true
    
        // MARK: - ** Constructor Methods **
    
        // MARK: - SortedArray
    
        /// Verbose constructor in which the sort order can be established at
        /// initialization.
        /// - parameter sortOrder: The order in which to sort array elements.
        /// - parameter values: The initial elements to populate this array. 
        /// - note: The initial values parameter need not be sorted, as it will
        /// automatically be sorted upon initialization.
        /// - returns: An array structure instance with sorted values.
        public init(sortOrder: SortOrder = .Ascending, values: [Value] = [Value]()) {
            self.sortOrder = sortOrder
            self.values = values.sort({ (a: Value, b: Value) -> Bool in
                return sortOrder == .Ascending ? (a < b) : (b < a)
            })
        }
    
        /// Convenience constructor that sets the inital array elements.
        /// - parameter values: The initial elements to populate this array.
        /// - returns: An array structure instance with sorted values in 
        /// ascending order.
        public init(_ values: [Value]) {
            sortOrder = .Ascending
            self.values = values.sort({ (a: Value, b: Value) -> Bool in
                return a < b
            })
        }
    
        /// Duplicating constructor.
        /// - parameter sortedArray: Another array to initialize from.
        /// - returns: An array structure instance with sorted values
        /// identical to `sortedArray`.
        public init(_ sortedArray: SortedArray<Value>) {
            sortOrder = sortedArray.sortOrder
            values = sortedArray.values
        }
    
        /// Bridging constructor from an `NSSortedArray` class instance.
        /// - parameter sortedArray: Another array to initialize from.
        /// - returns: An array structure instance with sorted values
        /// identical to `sortedArray`.
        public init(_ sortedArray: NSSortedArray<Value>) {
            sortOrder = sortedArray.sortOrder
            values = sortedArray.values
        }
    
        // MARK: - ** Public Methods **
    
        // MARK: - _ObjectiveCBridgeable
    
        /// Required static method from the `_ObjectiveCBridgeable` private 
        /// protocol.
        /// - returns: `true`, indicating that this structure is bridgeable to
        /// an Objective-C class, namely `NSSortedArray`.
        public static func _isBridgedToObjectiveC() -> Bool {
            return true
        }
    
        /// Required static method from the `_ObjectiveCBridgeable` private
        /// protocol.
        /// - returns: `NSSortedArray<Value>.self`
        public static func _getObjectiveCType() -> Any.Type {
            return _ObjectiveCType.self
        }
    
        /// Required static method from the `_ObjectiveCBridgeable` private
        /// protocol.
        /// - parameter source: An `NSSortedArray<Value>` instance to force bridge
        /// to `SortedArray<Value>`.
        /// - parameter result: The `SortedArray<Value>` instance created from
        /// the forced bridging.
        public static func _forceBridgeFromObjectiveC(source: _ObjectiveCType, inout result: SortedArray<Value>?) {
            result = SortedArray<Value>(source)
        }
    
        /// Required static method from the `_ObjectiveCBridgeable` private
        /// protocol.
        /// - parameter source: An `NSSortedArray<Value>` instance to conditionally
        /// bridge to `SortedArray<Value>`.
        /// - parameter result: The `SortedArray<Value>` instance created from
        /// the conditional bridging.
        public static func _conditionallyBridgeFromObjectiveC(source: _ObjectiveCType, inout result: SortedArray<Value>?) -> Bool {
            _forceBridgeFromObjectiveC(source, result: &result)
            return true
        }
    
        /// Required method from the `_ObjectiveCBridgeable` private protocol
        /// - returns: An `NSStortedArray<Value>` instance identical to `self`.
        public func _bridgeToObjectiveC() -> _ObjectiveCType {
            return NSSortedArray<Value>(self)
        }
    
        // MARK: - CollectionType
    
        public subscript (index: Index) -> Value {
            get { return values[index] }
            set { values[index] = newValue }
        }
    
        public func generate() -> Generator {
            return Generator(SortedArray(values: values))
        }
    
        /// Insert `newElement` at index `i`.
        ///
        /// - requires: `i <= count`.
        ///
        /// - complexity: O(`self.count`).
        public mutating func insert(value: Value, atIndex index: Index) {
            values.insert(value, atIndex: index)
        }
    
        /// Remove and return the element at index `i`.
        ///
        /// Invalidates all indices with respect to `self`.
        ///
        /// - complexity: O(`self.count`).
        public mutating func removeAtIndex(index: Index) -> Value {
            return values.removeAtIndex(index)
        }
    
        /// Remove all elements.
        ///
        /// - postcondition: `capacity == 0` iff `keepCapacity` is `false`.
        ///
        /// - complexity: O(`self.count`).    
        public mutating func removeAll() {
            values.removeAll()
        }
    
        // MARK: - SortedArray
    
        /// Returns the first index where `value` appears in `self` or `nil` if
        /// `value` is not found.
        ///
        /// - note: This is a significantly less costly implementation of the
        /// default system method `indexOf(element: Element)`.
        ///
        /// - complexity: O(`log(self.count)`)
        ///
        /// - parameter value: The value to search for
        /// - parameter range: The range to search within. If `nil` the entire
        /// range of elements are searched.
        /// - returns: The first index where `value` appears in `self` or `nil` if
        /// `value` is not found.
        @warn_unused_result
        public func indexOf(value: Value, searchRange range: Range<Index>? = nil) -> Index? {
    
            if values.count == 0 { return nil }
    
            let range = range ?? 0 ..< values.count
            let index = (range.startIndex + range.length / 2)
            let val = values[index]
    
            if range.length == 1 {
                return val == value ? index : nil
            } else if (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) {
                return indexOf(value, searchRange: range.startIndex ..< index)
            }
    
            return indexOf(value, searchRange: index ..< range.endIndex)
    
        }
    
        /// Returns the first index where `value` would be placed in sorted order
        /// in `self`.
        ///
        /// - complexity: O(`log(self.count)`)
        ///
        /// - parameter value: The value to search for.
        /// - parameter range: The range to search within. If `nil` the entire
        /// range of elements are searched.
        /// - returns: Returns the first index where `value` would be placed
        /// in sorted order.
        @warn_unused_result
        public func ordinalIndexForValue(value: Value, searchRange range: Range<Index>? = nil) -> Index {
    
            if values.count == 0 { return 0 }
    
            let range = range ?? 0 ..< values.count
            let index = (range.startIndex + range.length / 2)
            let val = values[index]
    
            if range.length == 1 {
                return (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) ? index : index + 1
            } else if (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) {
                return ordinalIndexForValue(value, searchRange: range.startIndex ..< index)
            }
    
            return ordinalIndexForValue(value, searchRange: index ..< range.endIndex)
    
        }
    
        /// Adds a value to `self` in sorted order.
        /// - parameter value: The value to add.
        /// - returns: The index where `value` was inserted, or `nil` if
        /// `uniqueElements` is set to `true` and `value` already exists in
        /// `self.
        ///
        /// - complexity: O(`log(self.count)`)
        public mutating func add(value: Value) -> Index? {
            var index = 0
            if values.count == 0 { values.append(value) }
            else {
                if uniqueElements && indexOf(value) != nil { return nil }
                index = ordinalIndexForValue(value)
                values.insert(value, atIndex: index)
            }
            return index
        }
    
        /// Removes all instances of `value` from `self`
        /// - parameter value: The `value` to remove from `self`.
        ///
        /// - complexity: O(`log(self.count) * n`) where `n` is the number of
        /// times `value` occurs in `self`.
        public mutating func remove(value: Value){
            var index = indexOf(value)
            while index != nil {
                values.removeAtIndex(index!)
                index = indexOf(value)
            }
        }
    
    }
    

    NSSortedArray.swift

    //
    //  NSSortedArray.swift
    //  NoodleKit
    //
    //  Created by Thom Morgan on 6/29/16.
    //  Copyright © 2016 NoodleOfDeath. All rights reserved.
    //
    
    // MARK: - ** Import Modules **
    
    import Foundation
    
    private struct CodingKeys {
        static let SortOrder = "SortOrder"
        static let Values = "Values"
    }
    
    // MARK: - ** NSSortedArray Class **
    
    /// An array class that automatically places elements in order as
    /// they added to the collection.
    public class NSSortedArray <Value: Comparable> : NSObject, CollectionType, NSCoding {
    
        // MARK: - CollectionType
    
        public typealias Index = Int
        public typealias Generator = IndexingGenerator<NSSortedArray<Value>>
    
        public var startIndex: Index { return 0 }
        public var endIndex: Index { return values.count }
        public var range: Range<Index> { return 0 ..< values.count }
        public var count: Int { return values.count }
    
        // MARK: - CustomStringConvertible
    
        public override var description: String { return "\(values)" }
    
        // MARK: - NSSortedArray
    
        /// The order in which to sort array elements.
        public var sortOrder: SortOrder {
            willSet { if sortOrder != newValue { values = values.reverse() } }
        }
    
        /// The elements of this array.
        public private (set) var values = [Value]()
    
        /// Whether or not to allow duplicate elements to be added to this array.
        public var uniqueElements: Bool = true
    
        // MARK: - ** Constructor Methods **
    
        // MARK: - NSSortedArray
    
        /// Verbose constructor in which the sort order can be established at
        /// initialization.
        /// - parameter sortOrder: The order in which to sort array elements.
        /// - parameter values: The initial elements to populate this array.
        /// - note: The initial values parameter need not be sorted, as it will
        /// automatically be sorted upon initialization.
        /// - returns: An array structure instance with sorted values.
        public init(sortOrder: SortOrder = .Ascending, values: [Value] = [Value]()) {
            self.sortOrder = sortOrder
            self.values = values.sort({ (a: Value, b: Value) -> Bool in
                return sortOrder == .Ascending ? (a < b) : (b < a)
            })
        }
    
        /// Convenience constructor that sets the inital array elements.
        /// - parameter values: The initial elements to populate this array.
        /// - returns: An array structure instance with sorted values in
        /// ascending order.
        public init(_ values: [Value]) {
            sortOrder = .Ascending
            self.values = values.sort({ (a: Value, b: Value) -> Bool in
                return a < b
            })
        }
    
        /// Duplicating constructor.
        /// - parameter sortedArray: Another array to initialize from.
        /// - returns: An array structure instance with sorted values
        /// identical to `sortedArray`.
        public init(_ sortedArray: NSSortedArray<Value>) {
            sortOrder = sortedArray.sortOrder
            values = sortedArray.values
        }
    
        /// Bridging constructor from a `SortedArray` structure instance.
        /// - parameter sortedArray: Another array to initialize from.
        /// - returns: An array class instance with sorted values
        /// identical to `sortedArray`.
        public init(_ sortedArray: SortedArray<Value>) {
            sortOrder = sortedArray.sortOrder
            values = sortedArray.values
        }
    
        // MARK: - NSCoding
    
        public convenience required init?(coder aDecoder: NSCoder) {
            guard let sortOrder = SortOrder(rawValue: aDecoder.decodeIntegerForKey(CodingKeys.SortOrder)) else { return nil }
            guard let values = aDecoder.decodeObjectForKey(CodingKeys.Values) as? [Value] else { return nil }
            self.init(sortOrder: sortOrder, values: values)
        }
    
        public func encodeWithCoder(aCoder: NSCoder) {
            aCoder.encodeInteger(sortOrder.rawValue, forKey: CodingKeys.SortOrder)
            aCoder.encodeObject(values, forKey: CodingKeys.Values)
        }
    
        // MARK: - CollectionType
    
        public subscript (index: Index) -> Value {
            get { return values[index] }
            set { values[index] = newValue }
        }
    
        public func generate() -> Generator {
            return Generator(NSSortedArray(values: values))
        }
    
        /// Insert `newElement` at index `i`.
        ///
        /// - requires: `i <= count`.
        ///
        /// - complexity: O(`self.count`).
        public func insert(value: Value, atIndex index: Index) {
            values.insert(value, atIndex: index)
        }
    
        /// Remove and return the element at index `i`.
        ///
        /// Invalidates all indices with respect to `self`.
        ///
        /// - complexity: O(`self.count`).
        public func removeAtIndex(index: Index) -> Value {
            return values.removeAtIndex(index)
        }
    
        /// Remove all elements.
        ///
        /// - postcondition: `capacity == 0` iff `keepCapacity` is `false`.
        ///
        /// - complexity: O(`self.count`).
        public func removeAll(keepCapacity keepCapacity: Bool = false) {
            values.removeAll(keepCapacity: keepCapacity)
        }
    
        // MARK: - NSSortedArray
    
        /// Returns the first index where `value` appears in `self` or `nil` if
        /// `value` is not found.
        ///
        /// - note: This is a significantly less costly implementation of the
        /// default system method `indexOf(element: Element)`.
        ///
        /// - complexity: O(`log(self.count)`)
        ///
        /// - parameter value: The value to search for.
        /// - parameter range: The range to search within. If `nil` the entire
        /// range of elements are searched.
        /// - returns: The first index where `value` appears in `self` or `nil` if
        /// `value` is not found.
        @warn_unused_result
        public func indexOf(value: Value, searchRange range: Range<Index>? = nil) -> Index? {
    
            if values.count == 0 { return nil }
    
            let range = range ?? 0 ..< values.count
            let index = (range.startIndex + range.length / 2)
            let val = values[index]
    
            if range.length == 1 {
                return val == value ? index : nil
            } else if (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) {
                return indexOf(value, searchRange: range.startIndex ..< index)
            }
    
            return indexOf(value, searchRange: index ..< range.endIndex)
    
        }
    
        /// Returns the first index where `value` would be placed in sorted order
        /// in `self`.
        ///
        /// - complexity: O(`log(self.count)`)
        ///
        /// - parameter value: The value to search for.
        /// - parameter range: The range to search within. If `nil` the entire
        /// range of elements are searched.
        /// - returns: The first index where `value` would be placed in sorted
        /// order in `self`.
        @warn_unused_result
        public func ordinalIndexForValue(value: Value, searchRange range: Range<Index>? = nil) -> Index {
    
            if values.count == 0 { return 0 }
    
            let range = range ?? 0 ..< values.count
            let index = (range.startIndex + range.length / 2)
            let val = values[index]
    
            if range.length == 1 {
                return (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) ? index : index + 1
            } else if (val > value && sortOrder == .Ascending) || (val < value && sortOrder == .Descending) {
                return ordinalIndexForValue(value, searchRange: range.startIndex ..< index)
            }
    
            return ordinalIndexForValue(value, searchRange: index ..< range.endIndex)
    
        }
    
        /// Adds a value to `self` in sorted order.
        /// - parameter value: The value to add.
        /// - returns: The index where `value` was inserted, or `nil` if
        /// `uniqueElements` is set to `true` and `value` already exists in
        /// `self.
        ///
        /// - complexity: O(`log(self.count)`)
        public func add(value: Value) -> Index? {
            var index = 0
            if values.count == 0 { values.append(value) }
            else {
                if uniqueElements && indexOf(value) != nil { return nil }
                index = ordinalIndexForValue(value)
                values.insert(value, atIndex: index)
            }
            return index
        }
    
        /// Removes all instances of `value` from `self`
        /// - parameter value: The `value` to remove from `self`.
        ///
        /// - complexity: O(`log(self.count) * n`) where `n` is the number of
        /// times `value` occurs in `self`.
        public func remove(value: Value){
            var index = indexOf(value)
            while index != nil {
                values.removeAtIndex(index!)
                index = indexOf(value)
            }
        }
    
    }
    

    NSCoder.swift

    //
    //  NSCoder.swift
    //  NoodleKit
    //
    //  Created by Thom Morgan on 8/15/16.
    //  Copyright © 2016 CocoaPods. All rights reserved.
    //
    
    // MARK: - ** Import Modules **
    
    import Foundation
    
    // MARK: - ** NSCoder - _ObjectiveCBridgeable Encoding Compatibility **
    
    extension NSCoder {
    
        /// Encodes an `_ObjectiveCBridgeable` data structure.
        /// - important: The objective-c class being bridged to must conform to
        /// `NSCoding`.
        /// - parameter object: The object to encode.
        public func encodeObject<T: _ObjectiveCBridgeable>(object: T?) {
            encodeObject(object?._bridgeToObjectiveC())
        }
    
        /// Encodes an `_ObjectiveCBridgeable` data structure as a root object.
        /// - important: The objective-c class being bridged to must conform to
        /// `NSCoding`.
        /// - parameter object: The object to encode.
        public func encodeRootObject<T: _ObjectiveCBridgeable>(object: T) {
            encodeRootObject(object._bridgeToObjectiveC())
        }
    
        /// Encodes an `_ObjectiveCBridgeable` conditional data structure.
        /// - important: The objective-c class being bridged to must conform to
        /// `NSCoding`.
        /// - parameter object: The object to encode.
        public func encodeConditionalObject<T: _ObjectiveCBridgeable>(object: T?) {
            encodeConditionalObject(object?._bridgeToObjectiveC())
        }
    
        /// Encodes an `_ObjectiveCBridgeable` data structure and maps it to a 
        /// specific `key`.
        /// - important: The objective-c class being bridged to must conform to
        /// `NSCoding`.
        /// - parameter object: The object to encode.
        /// - parameter key: The key to associate with this object.
        public func encodeObject<T: _ObjectiveCBridgeable>(object: T?, forKey key: String) {
            encodeObject(object?._bridgeToObjectiveC(), forKey: key)
        }
    
        /// Encodes an `_ObjectiveCBridgeable` conditional data structure and maps
        /// it to a specific `key`.
        /// - important: The objective-c class being bridged to must conform to
        /// `NSCoding`.
        /// - parameter object: The object to encode.
        /// - parameter key: The key to associate with this object.
        public func encodeConditionalObject<T: _ObjectiveCBridgeable>(object: T?, forKey key: String) {
            encodeConditionalObject(object?._bridgeToObjectiveC(), forKey: key)
        }
    
    }