I'm trying to subclass NSMutableArray in Swift to provide bridge functionality for a Swift array to some old Objective-C codebase. However, I can't find a functional example and all my attempts have failed so far. This is the closest I got to a functional example:
import UIKit
class MyArray: NSMutableArray {
var array: [Int]?
// Adding an initializer triggers the error: 'required' initializer 'init(arrayLiteral:)' must be provided by subclass of 'NSArray'
// override init() {
// self.array = []
// super.init()
// }
static func makeArray() -> MyArray {
let array = MyArray()
array.array = []
return array
}
override var count: Int {
get {
if let count = array?.count {
return count
}
return 0
}
}
override func insert(_ anObject: Any, at index: Int) {
print("insert called")
}
override func removeObject(at index: Int) {
print("removeObject called")
}
override func add(_ anObject: Any) {
print("Trying to add \(anObject)")
if let newInt = anObject as? Int {
array?.append(newInt)
}
}
override func removeLastObject() {
print("removeLastObject called")
}
override func replaceObject(at index: Int, with anObject: Any) {
print("replaceObject called")
}
}
func append42(array: NSMutableArray) {
array.add(42)
}
let myArray = MyArray.makeArray()
print("Before: \(myArray.array)")
myArray.array?.append(42)
// The following two lines generate a SIGABRT
//myArray.add(42)
//append42(array: myArray)
print("After: \(myArray.array)")
However, using the add method always triggers a SIGABRT. I'm using XCode 12.4 on Catalina.
EDIT (for clarifications): I know that Swift bridges Arrays to NSArrays. However, I need to pass a reference to a Swift array to some Objective-C code accepting an NSMutableArray and possibly making updates to the array.
Essentially I'm adding some Swift code to an existing project, and I'd like to use Swift typed arrays in the new code, but I still to pass these arrays to the old code. I know that I can make an NSMutableArray copy of the Swift array, pass this copy to the old code, then copy it back to the Swift array but it's rather convoluted and hardly maintainable. What I'm tried to do is encapsulating a reference to a Swift array into an 'NSMutableArray' subclass so I can transparently use this subclass with the legacy code.
Are you aware that:
When importing the Foundation framework, the Swift overlay provides value types for many bridged reference types. Many other types are renamed or nested to clarify relationships.
And specifically that :
Class clusters that include immutable and mutable subclasses, like NSArray and NSMutableArray, are bridged to a single value type.
So you can use Array in place of NSArray and if you want a NSMutableArray is also a Swift Array (but a var).
Same thing applies for Dictionary and NSDictionary.
Disclaimer
I would probably not use that in production code and although sublassing NSArray and NSMutableArray is not forbidden, documentation says :
There is typically little reason to subclass NSMutableArray.
And this should be reason enough to consider another solution IMHO
Edit
After reading this answer
I decided to check NSMutableArray documentation:
Methods to Override
NSMutableArray defines five primitive methods:
insert(_:at:)
removeObject(at:)
add(_:)
removeLastObject()
replaceObject(at:with:)
In a subclass, you must override all these methods. You must also override the primitive methods of the NSArray class.
So I checked NSArray documentation too:
Any subclass of NSArray must override the primitive instance methods count and object(at:). These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
and now, with:
#objc class MyArray: NSMutableArray {
var array: [Int] = []
static func makeArray() -> MyArray {
let array = MyArray()
array.array = []
return array
}
override var count: Int {
array.count
}
override func object(at index: Int) -> Any {
array[index]
}
override func insert(_ anObject: Any, at index: Int) {
print("insert called")
if let newInt = anObject as? Int {
array.insert(newInt, at: index)
}
}
override func removeObject(at index: Int) {
array.remove(at: index)
print("removeObject called")
}
override func add(_ anObject: Any) {
print("Trying to add \(anObject)")
if let newInt = anObject as? Int {
array.append(newInt)
}
}
}
func append42(array: NSMutableArray) {
array.add(42)
}
I have :
let myArray = MyArray.makeArray()
print("Before: \(myArray.array)")
myArray.array.append(42) // [42]
// The following two lines generate a SIGABRT
myArray.add(42) // [42, 42]
append42(array: myArray)
print("After: \(myArray.array)")
// Before: []
// Trying to add 42
// Trying to add 42
// After: [42, 42, 42]
Fun facts
I tried your code in a playground and reproduced the crash. I tried in a XCTestCase and it did not crash
calling super.add(_:) in add(_:) will trigger insert(_:at:)
NSMutableArray conformance to ExpressibleByArrayLiteral is probably written in an extension (which makes perfect sense since NSMutableArray is an Objective-C class) and so overriding init() forces you to override init(arrayLitteral:) and the compiler says Overriding declarations in extensions is not supported. :
Automatic Initializer Inheritance
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
We should generally follow the Open-Closed Principle, OCP, namely that classes are open for extension and closed for modification. This attempt to subclass NSMutableArray, and replace a few key methods, violates OCP. It is likely to manifest all sorts of weird behaviors.
If you really want to subclass, you will want to override all of the primitive methods of both NSArray and NSMutableArray. Theoretically you could examine your Objective-C code and see what methods it is calling, but this is a very fragile approach. I would not advise this pattern.
Personally, if I was trying to integrate with some Objective-C code that required NSMutableArray, I would stick with Swift arrays and create a NSMutableArray before I called my Objective-C library, and then copy it back to an array (consistent with the value semantics of Swift arrays).
Consider:
#implementation Foo
- (void)bar:(NSMutableArray *)array {
[array addObject:#42];
}
#end
I would shim this for a Swift Array:
extension Foo {
func bar(_ values: inout [Int]) {
let array = NSMutableArray(array: values)
bar(array)
values = array as! [Int]
}
}
Then I could do:
var values = [1, 2]
values.append(3)
let foo = Foo()
foo.bar(&values)
print(values) // 1, 2, 3, 42
But I would avoid avoid subclassing NSMutableArray simply because I happened to be calling some Objective-C routine that used NSMutableArray. I would only do that if benchmarking (or other considerations) deemed that absolutely necessary.
Let us assume for a second that you absolutely needed to stick with NSMutableArray (e.g., you had millions of records and are calling this Objective-C routine constantly). In that case, I still would not advise subclassing NSMutableArray, but rather just use NSMutableArray directly in my Swift code. If I wanted to retrieve Int values from my NSMutableArray and didn't want to cast all over the place, I'd add an extension to NSMutableArray:
extension NSMutableArray {
func append(integer value: Int) {
add(value)
}
func intValue(at index: Int) -> Int {
return self[index] as! Int
}
}
Then you could do:
let values: NSMutableArray = [1, 2]
values.append(integer: 3)
let foo = Foo()
foo.bar(values)
print(values) // 1, 2, 3, 42
let value = values.intValue(at: 3)
print(value) // This is `Int` value of 42
Now, I know this is not the answer you were hoping for, namely that you did not want to “lose the benefits of Swift typed Arrays” while still using this Objective-C legacy code. But at least these two methods give you type-safe setting and retrieval of integer values.
Related
I have faced an issue.
Consider I have one protocol and two classes implementing it:
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
then at some point I'm creating an array and passing it to function:
var array = [A]()
func update(object: Any){
}
update(object: array)
from that function update I would like to pass object to another function if it mets another function's conditions:
func process<T: Initiatable>(array: T){
/* ... */
}
So how can I check that object of type Any is an array of concrete class implementing Initiatable protocol? I would like to write something like
func update(object: Any){
if let array = object as Array<T: Initiatable>{
process(array: array)
}
}
But that's doesn't work. Code like:
func update(object: Any){
if let array = object as [Initiatable]{
process(array: array)
}
}
func process(array: [Initiatable]){ }
Compiles fine, but that's not what I want – process function should receive an array of concrete implementation of Initiatable so at some point it could use:
func process<T: Initiatable>(array: [T]){
/* other code */
T.init()
}
So is there any way to do this? Thank you very much in advance!
There's a few parts to this question:
Generating the array of types
Your array declaration is expecting an array of A objects rather than A types. To generate an array with A types, you could pass in a Postfix self expression: (link)
var array = [ A.self ]
This would define array as an array of A.Type, called the Metatype Type (same link).
You could also generate an empty array with this metatype type:
var array:[A.Type] = []
If you wanted an array with both A.self and B.self you could either specify it as [Any]...
var array:[Any] = [A.self,B.self]
...or, make use of the Initiatable protocol you created:
var array:[Initiatable.Type] = [A.self,B.self]
Downcasting an array to an array of types in your update method
You were having trouble downcasting an Any object to an array of types.
Here's my updated update method:
func update(object: Any){
if let array = object as? [Initiatable.Type] { //1
process(array: array)
}
}
You can now perform an optional downcast on the array that is being past into your update method. Downcast it to an array of metadata types of Initiatable: (This is the only line I modified from your method)
Receiving a type as a parameter in the process method
I'm assuming that you just want your process method to receive an array of types, and instantiate a variable based on one of those types. You didn't mention which element in the array, so I've just gone with the first.
func process(array: [Initiatable.Type]){ //1
if let firstType = array.first { //2
let firstObject = firstType.init() //3
}
}
The process method can receive an array of types that adopt the Initiatable protocol.
I'm using optional binding to get at the value of the first element in the array. This should be a type.
Instantiate an object based on the first element in the array.
If your array is nonempty, then you can solve this by grabbing the runtime type of one element from the array:
func update(object: Any){
if let array = object as? [Initiatable]{
process(array: array)
} else {
// Not initiatable
}
}
func process(array: [Initiatable]) { // no need for generics here
guard let first = array.first else {
// Unable to determine array type
// throw or return or whatever
}
// type(of: first) gives A or B
type(of: first).init()
}
If the array is empty, then I don’t know of a way to do this in Swift (as of Swift 3, anyway).
The static type of object is Any, so you need to use the runtime type of object in order to get anywhere.
However, Swift (and most languages with generics) only resolve type parameters like T based on the static types of the values you pass. Therefore your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only take on a type the compiler already knew when you called process().
Swift does give you access to runtime types: type(of: object) will return (for example) Array<A>. So close! But:
AFAIK, there is currently no way to extract type parameters from a metatype, i.e. to get A from Array<A>, unless the compiler already knows the type parameter statically.
You can get the string "Array<A>" and extract the string A from it, but AFAIK there is no way to map strings back to types in Swift unless the type is an Objective-C class.
In short, I think you’ve just hit a limit of Swift’s metatype / reflection capabilities.
Unfortunately I can't think of a solution in a given constraints. It seems like you are trying to achieve compile-time polymorphism at runtime.
Using generics implies that compiler understands what class will be called. But using Any implies that at compile time it could be anything and it would be known only at runtime.
The closest solutions that I can propose are:
// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
process(array: array)
}
// or explicitly convert `Any` to the class,
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {
if let array = object as? [A] {
process(array: array)
}
if let array = object as? [B] {
process(array: array)
}
}
Is this help you,
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
class Test<T : Initiatable>
{
func update(object: Any){
if let array = object as? [T]{
process(array: array)
}
}
func process(array: [T]){
}
}
I found a workaround to solve this problem:
func classTypeFrom(_ className: String) -> AnyClass!{
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
return NSClassFromString(classStringName)
}
return nil;
}
func update(object: Any){
if let array = object as? [Initiatable]{
let arrayTypeName = "\(type(of: ar))"
let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
process(array: array, type: arrayType)
}
}
}
func process(array: [Initiatable], type: Initiatable.Type){
var ar = array
let newObj = type.init()
ar.append(newObj)
}
Is there a way to extend an Array when the element is a type of tuple ?
public extension Array where Element: (timeZoneName: String, formattedName: String){
}
This declaration returns 4 errors:
Statement cannot begin with a closure expression
Braced block statements is an unused closure
Expected '{' in extension
Expected identifier for type name
I can't tell if the errors shown are accurate.
Any ideas?
Swift 3 version of appzYourLife's answer:
extension Sequence where Iterator.Element == Tuple2 {
func foo() {}
}
It's now possible in Swift 3.1.
extension Array where Element == (String, String) {
...
}
Since (AFAIK) the Tuple type does not conform to a Protocol (and does not even have a name) it's very hard to do what you need.
This is the closest I could get (maybe others can provide more elegant solutions).
Typealias
First of all lets define a couple of typealiases
typealias Tuple2 = (Any, Any)
typealias Tuple3 = (Any, Any, Any)
Yes, some readers now understand where I am going and probably don't like it...
I don't like it neither
SequenceType
Now let's extend the protocol SequenceType adding the foo method when the Element of the sequence is Tuple2...
extension SequenceType where Generator.Element == Tuple2 {
func foo() {}
}
or Tuple3
extension SequenceType where Generator.Element == Tuple3 {
func foo() {}
}
Array
Next lets define and populate an array of Tuple2
let list: [Tuple2] = [(1,2)]
Now the extension is applied and we can write
list.foo()
Disclaimer :D
This does work only if the array is explicitly declared as [Tuple2] (or [Tuple3]).
Something like this does not work
let list = [(1,2)]
list.foo() // compile error
You can't add specific typing like extension Array where Element == Int because this would transform the generic Array into a non-generic version.
You will see an error something like same-type requirement makes generic parameter 'Element' non-generic
Edit
It does actually seem legit (at least in Swift 2.2) to do:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array where Element: tzTuple {
}
You will have to see if this works in runtime though.
I was checking this in a Playground and at present, Playgrounds are not yet fully functional with Swift 2.2-dev
I would suggest this instead:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array {
func formattedName(index: Int) -> String? {
if self[index] is tzTuple {
return (self[index] as! tzTuple).formattedName
}
return nil
}
}
will allow you to do
let foo = [(timezoneName: "PST", formattedName: "Pacific Standard Time"),(timezoneName: "AEST", formattedName: "Australian Eastern Time")]
print(foo.formattedName(0))
This should be pretty simple. I have a data source that always gives me UInt16s. I derive different data sets from this raw data and plot the results. Some of the derived data sets are Floats, some are UInt8s, and some are UInt16s.
I queue the derived data where it is later retrieved by my graphing classes.
Queues are arrays of arrays and look like this: [[UInt16]], [[Float]], or [[UInt8]].
I'm trying to make use of generics, but I get a compiler error when I try to append a generic-typed array to an array that is declared to be [[AnyObject]].
As I'm learning Swift, I keep bumping into this AnyObject / generic problem quite a bit. Any help/insight is appreciated.
class Base: NSObject {
var queue : [[AnyObject]] = Array()
func addtoQueue<T>(dataSet: [T]) {
queue.append(dataSet)
}
func removeFromQueue() -> [AnyObject]? {
return queue.removeAtIndex(0)
}
}
class DataSet1 : Base {
func getSomeData(rawData: [UInt16]) {
var result : [Float] = processRawData(rawData)
addToQueue(result)
}
}
It may be that you don't understand what AnyObject is. It is the protocol type automatically adopted by all classes. But Float, UInt16, and UInt8 are not classes; they are structs.
It may be that you meant [[Any]] as the type of your array. In that case, you don't need a generic. This works:
var queue : [[Any]] = Array()
func addToQueue(dataSet:[Any]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([i1])
addToQueue([i2])
If you insist on [[AnyObject]], you have two problems:
Your generic is too generic. Not everything in the universe is an AnyObject, so how can the compiler know that this thing will be an AnyObject? Write your generic like this:
func addToQueue<T:AnyObject>(dataSet:[T]) {
queue.append(dataSet)
}
Now the compiler knows that only something conforming to AnyObject will be used when calling this method. At that point, however, it is a little hard to see what your generic is for; you are not using T elsewhere, so just write a normal function:
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
The second problem is that you will still have to convert (as Drew's answer tells you), because there is no automagic bridging between, say, a UIInt16 and an AnyObject. Float is _ObjectiveCBridgeable, but UInt16 and UInt8 are not.
var queue : [[AnyObject]] = Array()
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([NSNumber(unsignedChar: i1)])
addToQueue([NSNumber(unsignedShort: i2)])
What you'll need to do is 'box' up the underlying value. You could accomplish this using a protocol which exposed a setter/getter and an enum property of the underlying type that you can 'switch' on. Alternatively, you could see if you can make Foundation's built in NSNumber work for you. It's doing exactly that: boxes up any number of numerical types for you to store and retrieve later on:
var queue : Array<NSNumber> = []
queue.append(NSNumber(int: 1))
queue.append(NSNumber(double: 2.5))
queue.append(NSNumber(float: 3.5))
var types : Array<String> = []
for item in queue{
println("number type: \(item)")
}
While find(["a", "b"], "c") works with no problems, I get an error when trying to find the index of a structure inside an array of structures:
struct Score
{
//...
}
var scores: [Score] = //...
var score: Score = //...
find(self.scores, score) // Error: Cannot invoke 'find' with an argument list of type '([Score], Score)'
I though it could be a problem with structures that can't be compared to each other by default. But changing Scores definition to class
gives me the same error.
EDIT: as of Swift 2.0, there is now a built-in version of find that takes a closure so you don’t have to write your own – but also, find has been renamed indexOf, and is now a protocol extension of CollectionType, so you call it like a method:
// if you make `Score` conform to `Equatable:
if let idx = self.scores.indexOf(score) {
}
// or if you don't make it Equatable, you can just use a closure:
// (see original answer below for why you might prefer to do this)
if let idx = scores.indexOf({$0.scoreval == 3}) {
}
Original pre-2.0 answer below
While the answers suggesting making your class Equatable may work nicely, I'd recommend a bit of caution before choosing to do this. The reason being that, as the docs state, equatability implies substitutability, and your == operator must be reflexive, symmetric and transitive. If you don't adhere to this, you might get some very weird behavior when using algorithms like equals, sort etc. Be especially cautious if implementing Equatable on non-final classes. If you're sure you can meet requirements, go for it, and find will work.
If not, an alternative you could consider is writing a function that ought to be in the standard library but isn't, which is a find that takes a closure:
func find<C: CollectionType>(source: C, match: C.Generator.Element -> Bool) -> C.Index {
for idx in indices(source) {
if match(source[idx]) { return idx }
}
return nil
}
Once you have this, you can supply any matching criteria you prefer. For example, if your objects are classes you could use reference equality:
let idx = find(scores) { $0 === $1 }
The interface for the function find is/was:
func find<C : CollectionType where C.Generator.Element : Equatable>(domain: C,
value: C.Generator.Element) -> C.Index?
This says that the CollectionType of C must have elements that are Equatable and, furthermore, that the value must also be Equatable.
[Note Swift 3.0: As of Swift 3.0, you'll need to use the index function which comes in two variations. In the first, you'll supply your own predicate:
func index(where: (Self.Generator.Element) -> Bool) -> Self.Index?
In the second, your elements need to be equatable:
// Where Generator.Element : Equatable
func index(of: Self.Generator.Element) -> Self.Index?
If you decide to go the equatable route, then the following applies.
Note End]
Your Score struct is not Equatable and hence the error. You'll need to figure out what it means for scores to be equal to one another. Maybe it is some numerical 'score'; maybe it is a 'score' and a 'user id'. It depends on your Score abstraction. Once you know, you implement == using:
func == (lhs:Score, rhs:Score) -> Bool {
return // condition for what it means to be equal
}
Note: if you use class and thus scores have 'identity' then you can implement this as:
func == (lhs:Score, rhs:Score) -> Bool { return lhs === rhs }
Your example with strings works because String is Equatable. If you look in the Swift library code you'll see:
extension String : Equatable {}
func ==(lhs: String, rhs: String) -> Bool
As the others have said, the objects you search for must conform to the Equatable protocol.
So you need to add an extension to your Score struct that tells the compiler that it conforms to that protocol:
extension Score: Equatable {}
And then you need to implement the == function for that class:
public func ==(lhs: Score, rhs: Score) -> Bool
{
return lhs.whatever == rhs.whatever //replace with code for your struct.
}
In Swift I declared a function that differs from Array.count only in that if array == nil the function returns 0. This is related to my UITableViewDataSource, but that's not important here. The problem is, if I declare the function as:
class func countOfItemsInArray(array: [AnyObject]?) -> Int
and then try to pass it an array of structs, it declares that the structs in the array do not conform to AnyObject. I understand why that is (I think), but is there a way to make this work with classes and structs, or should I just give in to copy and paste?
Generics are probably better suited to this problem than relying on covariance of [AnyObject]. A version of countElements that worked on an optional array and returned 0 in case of nil could go like this:
func countElements<T>(array: [T]?) -> Int {
return array?.count ?? 0
}
When you call countElements with any kind of array, the placeholder T is replaced with the type of the element contained in the array.
Note, this version overloads the existing countElements with a version that takes an optional. If you call it with a non-optional or any other kind of collection, the Swift version would be called, if you pass in an optional array, this one will be called. It’s debatable whether this is a good practice (I think it’s fine) or a bad one (some may disapprove :).
A version that works on any collection type would be:
func countElements<C: CollectionType>(col: C?) -> C.Index.Distance {
return col.map { countElements($0) } ?? 0
}
If you use Any instead of AnyObject you can pass any type, so also structs:
class func countOfItemsInArray(array: [Any]?) -> Int
This is kind of weird.
I used this function:
func countOfItemsInArray(array: [Any]?) -> Int {
return array != nil ? array!.count : 0
}
Declared two of your Assignment structs and put them in an array:
let structOne = Assignment(name: "1", dueDate: NSDate(), subject: "1")
let structTwo = Assignment(name: "2", dueDate: NSDate(), subject: "2")
let myArray: [Assignment] = [structOne, structTwo]
But here's the interesting part.
When calling println(countOfItemsInArray(myArray)) it gives the error:
<stdin>:27:33: error: 'Assignment' is not identical to 'Any'
println(countOfItemsInArray(myArray))
^
<stdin>:17:26: note: in initialization of parameter 'array'
func countOfItemsInArray(array: [Any]?) -> Int {
^
So I tested if myArray is of type [Any]:
println(myArray is [Any])
to which swift says:
<stdin>:25:17: error: 'Any' is not a subtype of 'Assignment'
println(myArray is [Any])
^
But when I change the type annotation of myArray to [Any] it works:
let myArray: [Any] = [structOne, structTwo]
And when simply handing the literal to the function it works, too:
countOfItemsInArray([structOne, structTwo])
The whole code example can be seen here.