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