Define struct that is treated like a class in Swif

2019-02-16 22:42发布

问题:

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")
    }
}

回答1:

This isn't currently possible. SE-0058 will address it, but is deferred out of Swift 3. A final implementation of SE-0058 would be hoped to handle more than just ObjC bridging; for example allowing C++ or .NET bridging as well in a more generic solution.



回答2:

Ultimately, the bridging between String and NSString is quite simple.

NSString only has 2 instance variables (The string pointer nxcsptr, and the length nxcslen). String uses _StringCore, which only has 3 properties (_baseAddress, _countAndFlags, and _owner). The conversion back and forth is hard coded, and called explicitly by the compiler. There's no automatic system implemented for generating classes out of structs, or vice versa.

You'll have to implement a struct/class pair (like with String and NSString), and implement initializers that construct one from the other.



回答3:

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)
    }

}