Define Struct That Is Treated Like a Class in Swift

Define struct that is treated like a class in Swift

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

}

Sometimes Classes treat like structures in swift?

  class DishActivationDateTimeModel {

var dayDate: Date? = nil
var dayDateStr: String = ""
var servingsLeft: String = ""
var firstSlotPostedDishId: String = ""
var secondSlotPostedDishId: String = ""

var startTimeDateForFirstSlot: Date? = nil
var startTimeStringForFirstSlot: String = ""
var endTimeDateForFirstSlot: Date? = nil
var endTimeStringForFirstSlot: String = ""

var startTimeDateForSecondSlot: Date? = nil
var startTimeStringForSecondSlot: String = ""
var endTimeDateForSecondSlot: Date? = nil
var endTimeStringForSecondSlot: String = ""

init(_ object : DishActivationDateTimeModel) {
self.dayDate = object.dayDate
self.dayDateStr = object.dayDateStr
// ....
// ....
self.endTimeStringForSecondSlot = object.endTimeStringForSecondSlot
}
}
extension Array where Element : DishActivationDateTimeModel {

func copyModelArray() -> [DishActivationDateTimeModel] {
var array : [DishActivationDateTimeModel] = []
for object in self {
array.append(DishActivationDateTimeModel(object))
}
return array
}
}

If you don't want to make changes in your main data then copy your
model array using copyModelArray method. And use your operational
model array. So, its not affect to your main model data array.

structure vs class in swift language

Here's an example with a class. Note how when the name is changed, the instance referenced by both variables is updated. Bob is now Sue, everywhere that Bob was ever referenced.

class SomeClass {
var name: String
init(name: String) {
self.name = name
}
}

var aClass = SomeClass(name: "Bob")
var bClass = aClass // aClass and bClass now reference the same instance!
bClass.name = "Sue"

println(aClass.name) // "Sue"
println(bClass.name) // "Sue"

And now with a struct we see that the values are copied and each variable keeps it's own set of values. When we set the name to Sue, the Bob struct in aStruct does not get changed.

struct SomeStruct {
var name: String
init(name: String) {
self.name = name
}
}

var aStruct = SomeStruct(name: "Bob")
var bStruct = aStruct // aStruct and bStruct are two structs with the same value!
bStruct.name = "Sue"

println(aStruct.name) // "Bob"
println(bStruct.name) // "Sue"

So for representing a stateful complex entity, a class is awesome. But for values that are simply a measurement or bits of related data, a struct makes more sense so that you can easily copy them around and calculate with them or modify the values without fear of side effects.

Is there a way to use Swift structs in Objective-C without making them Classes?

As of now, NO.
You will need Classes to store model objects.

@objcMembers public class Car: NSObject {
public var mileage: NSNumber?
}

@objcMember virtually converts them to Objective-c readable format and can be accessed from Objective-C classes.
This will make your code interoperable.

Note: Make sure the class type is NSObject.

Why Swift class need init but not Swift struct

Structs still have an initializers. The only differences is that in some cases the compiler will synthesize a "default member-wise initalizer" for you.

In this case, it created one with the signititure private init(value: Int) (private because your struct has a private field)

Why does a public class/struct in Swift require an explicit public initializer?

Marking a class public does not necessarily imply that the developer wants the class to be initialized publicly. For example, I often write base classes that exist solely for me to be able to subclass them. I give these superclasses internal initializers so that their subclasses can access them, but those in the outside world shouldn't be using them directly. For example, Operation in Foundation has no accessible initializers, yet the class is public. It is simply meant to be subclassed. This is considered an abstract class in Objective-C.

Since Swift doesn't contain explicit support for abstract classes, the act of making a class public but without public initializers basically serves as an abstract class (except each function must still have a default definition, either in the class itself or some protocol extension).

With this in mind, here are some Swift rules:

  • If your class is marked private, all variables, inits, and functions will default to private.
  • If your class is marked internal (which is default), public, or open, all variables, inits, and functions will default to internal.
  • A subclass's superclass must be at least as accessible.
  • classes and class members declared public in Objective-C are imported into Swift as open, due to there being no such distinction in Objective-C.

That second one is the one you are running into. The default init is picking up the default internal because the last thing Swift wants to do is expose your init as public API unless it is explicitly instructed to do so.

Note: In my tests (at least in the playground), it seems that with the introduction of fileprivate:

  • If a class is declared private or fileprivate, it seems that class members default to fileprivate unless explicitly annotated private.

What is the correct way to create preset Structs?

The equivalent of class func for struct types is static func:

static func testStruct() -> SomeStruct {
return SomeStruct(value: "foo")
}

and a static property (the "singleton-pattern") works identically
with both class and struct types:

static let singleStruct = SomeStruct(value: "foo")

testStruct() creates a value on each call, whereas singleStruct
creates the value once (on the first call).

In most cases that would make no difference because structures are
value types. The static property has advantages if creating the
value is "expensive". Also, as @Lance noticed in a comment,
this pattern is used by Apple frequently, such as CGRect.zero.

However, it makes a difference if the struct has properties which
are reference types (or pointers to unmanaged memory). Here is an example:

class MyClass {
var attribute : String
init(value : String) {
attribute = value
}
}

struct SomeStruct {

var ptr : MyClass

init(value : String) {
ptr = MyClass(value: value)
}
static func testStruct() -> SomeStruct {
return SomeStruct(value: "foo")
}

static let singleStruct = SomeStruct(value: "foo")
}

Using the static function:

let test1 = SomeStruct.testStruct()
print(test1.ptr.attribute) // foo

let test2 = SomeStruct.testStruct()
test2.ptr.attribute = "bar"

print(test1.ptr.attribute) // foo

Here test1 and test2 are separate values and we get the expected
output.

Using the static property:

let test1 = SomeStruct.singleStruct
print(test1.ptr.attribute) // foo

let test2 = SomeStruct.singleStruct
test2.ptr.attribute = "bar"

print(test1.ptr.attribute) // bar <--- What?

Here, test1 and test2 are set to the same value returned from
the static property. Changing test2.ptr does not mutate test2,
resulting in the somewhat unexpected output for test1.ptr.attribute



Related Topics



Leave a reply



Submit