Определите структуру, которая обрабатывается как класс в Swift

В Swift структура String также рассматривается как объект класса, как при использовании метода NSCoder encodeObject(_:forKey:). Я знаю, что String напрямую связан с классом target-c, NSString, но есть ли способ создать собственный struct, который ведет себя аналогично? Возможно, связать его с пользовательским классом? Я хочу иметь возможность сделать что-то вроде этого:

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

person NoodleOfDeath    schedule 08.08.2016    source источник
comment
В настоящее время нет способа добавить неявное связывание ObjC с пользовательскими типами. Текущая реализация требует некоторой магии компилятора.   -  person Rob Napier    schedule 08.08.2016
comment
@RobNapier +1 за уточнение. Я еще не на том уровне, чтобы внести свой вклад, но, возможно, это может стать потенциальной темой для быстрой эволюции? Я был бы очень признателен любому, кто сделает это с предложением, если они увидят выгоду.   -  person NoodleOfDeath    schedule 08.08.2016
comment
github.com/apple/swift-evolution/blob/ мастер/предложения/   -  person Rob Napier    schedule 08.08.2016
comment
(Я перенесу это в ответ для будущих искателей)   -  person Rob Napier    schedule 08.08.2016
comment
@RobNapier Я понимаю причину отсрочки решения, поскольку предлагаемый протокол не кажется достаточно общим. Я надеюсь, что они добавят эту функцию в будущем :(   -  person NoodleOfDeath    schedule 08.08.2016
comment
Вы можете посмотреть, как это делает Apple. Однако использует много недокументированных вызовов   -  person Code Different    schedule 08.08.2016
comment
Взгляните на stackoverflow.com/questions/35893517/ (он использует недокументированные функции, но, похоже, работает).   -  person Martin R    schedule 09.08.2016
comment
@MartinR Интересно, похоже, это именно тот протокол, который был предложен и отложен в приведенной выше ссылке RobNapier. Будет ли продолжена поддержка этого решения в Swift 3?   -  person NoodleOfDeath    schedule 10.08.2016


Ответы (3)


В настоящее время это невозможно. SE-0058 решит эту проблему, но отложено из Swift 3. Можно надеяться, что окончательная реализация SE-0058 будет обрабатывать больше, чем просто мост ObjC; например, разрешая мост C++ или .NET, а также в более общем решении.

person Rob Napier    schedule 08.08.2016
comment
Как насчет этого stackoverflow.com/questions/35893517/? - person Martin R; 09.08.2016
comment
_ObjectiveCBridgeable является частным. Хотя он может работать, нет никаких тестов, что он работает с чем-либо, кроме типов stdlib. Конечно, для хобби-кода, но я бы не стал выпускать что-либо, основываясь на недокументированных деталях реализации. - person Rob Napier; 09.08.2016

В конечном счете, связь между String и NSString довольно проста.

NSString имеет только 2 переменные экземпляра (указатель строки nxcsptr и длину nxcslen). String использует _StringCore, который имеет только 3 свойства (_baseAddress , _countAndFlags, и _owner). Преобразование туда и обратно жестко запрограммировано и явно вызывается компилятором. Не существует автоматической системы для создания классов из структур или наоборот.

Вам придется реализовать пару struct/class (например, с String и NSString) и реализовать инициализаторы, которые строят одно из другого.

person Alexander    schedule 08.08.2016

Я нашел работающее элегантное решение, которое работает с _ObjectiveCBridgeable struct, которое можно закодировать с помощью NSCoder; благодаря ссылке предоставленный Martin R. Вот код библиотеки, который я написал для всех, кто заинтересован. Теперь я могу сделать что-то вроде этого:

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

}
person NoodleOfDeath    schedule 24.08.2016