Swift 3: Different kind of elements in an array are not acceptable - arrays

Up until Swift 2.2 I was able to do this:
for each in [myUIButton,myUILabel] {
each.hidden = true
}
but in Swift 3 this is not acceptable because label, button etc are not the same kind of element. I have already changed line 2 to each.isHidden = true
It throws "Heterogeneous collection literal..." error. When you fix it by adding [Any], it throws "Cast 'Any' to 'AnyObject.." error.
Is there an easy fix to this problem?

Find a common ancestor class having isHidden property, and explicitly cast to it:
for each in [myUIButton, myUILabel] as [UIView] {
each.isHidden = true
}

All items in your array must have a common subclass, like UIView in the case of myButton and myLabel (presumably) in order for type inference to take place.
let label = UILabel()
let button = UIButton()
let collectionView = UICollectionView()
let tableView = UITableView()
let array = [label, button, collectionView, tableView] // Type: [UIView]
for item in array {
item.isHidden = true
}
This code will work for your purposes.
Furthermore, if they all conform to the same protocol, you must explicitly name the protocol they conform to.
protocol Commonality {
func commonMethod() { ... }
}
class ThingA: Commonality { ... } // Correctly conform to Commonality
class ThingB: Commonality { ... } // Correctly conform to Commonality
class ThingC: Commonality { ... } // Correctly conform to Commonality
let array: [Commonality] = [ThingA(), ThingB(), ThingC()]
for item in array {
item.commonMethod()
}
This should work as well, but you must explicitly name the common protocol. Otherwise (at least in my tests), it downcasts everything down to Any.

Tell Swift it's an [Any] array:
for each in [myButton,myLabel,x,y,z] as [Any] {
each.hideen = true
}
But then you will get an error cause Any doesn't have a property called hideen (typo?).

Related

Array of generic classes and subclassing

I'm having trouble wrapping my head around generics. What I want is to have an array of generic classes, each with it's own associated type, and call a function accordingly. It would look something like this:
class SomeGenericClass<U> {
func addCallback(callback: (U)->() ) { ... }
}
var array: [SomeGenericClass] // compile error
The last line yields an error, so I found that I needed to have a superclass. I tried something like this:
class SuperClass {
func addCallback<V>(callback: (V)->() ) { ... }
}
class SomeGenericClass<U> {
func addCallback<V: U>(callback: (V)->() ) { ... } // compile error
}
var array: [SuperClass] // no compile error
This yields the error Type 'V' constrained to non-protocol, non-class type 'U'.
Basically I want to be able to do:
array.append(SomeGenericClass<UIImage>()) // array[0]
array.append(SomeGenericClass<Int>()) // array[1]
// Since array[0] is effectively of type SomeGenericClass<UIImage>, the compiler should understand that the close added if of type (UIImage)->(), and therefore that value is of type UIImage
array[0].addCallback { value in
someImageView.image = value
}
Is using a superclass the right approach in this case? How should it be implemented?
I worked around this problem by storing my array members in their own variable. That is, instead of defining my array like:
lazy var array: [SuperClass] = [
SomeGenericClass<UIImage>(),
SomeGenericClass<Int>(),
//etc...
]
I defined it this way:
lazy var genericFirst: SomeGenericClass<UIImage> = SomeGenericClass<UIImage>()
lazy var genericSecond: SomeGenericClass<Int> = SomeGenericClass<Int>()
// etc...
lazy var array: [SuperClass] = [
genericFirst,
genericSecond,
//etc...
]
This way, I can access the generics I want like this:
genericFirst.addCallback { value in
// value is indeed of type UIImage
someImageView.image = value
}

How to remove an object from an array, where the object conforms to a protocol?

I am keeping track of a number of delegate objects in an array delegates
To qualify as a valid delegate the objects need to conform to the BSBSystemDelegate protocol.
So here is the array declaration:
private var delegates: [BSBSystemDelegate] = []
When an object registers with the BSBSystem, it is appended to the array:
public func registerDelegateWith(_ viewController: BSBSystemDelegate)
{
self.delegates.append(viewController)
}
That's working fine.
The problem I'm running into with swift and it's awful and confusing syntax is when I need to 'deregister' a delegate i.e. remove it from the array, if it exists.
Here's what I've tried:
public function deregisterDelegate(_ viewController: BSBSystemDelegate)
{
for delegate in self.delegates
{
if delegate === viewController
{
self.delegates.removeAll(where: viewController)
}
}
}
That doesn't work.
I just want to remove the object in the array when it's the same object I'm asking to remove.
I've been fighting swift for over an hour. Can someone please explain where I'm going wrong?
Here's is Apple's example:
And here is my code and the crazy dumb error it keeps giving me:
self.delegates.removeAll(where: { $0 === viewController }) will work but your protocol needs to be declared as class-bound in order to use the === operator which only works with reference types.
You would have to declare your protocol as:
protocol BSBSystemDelegate: AnyObject {
...
}
The error message isn't useful because the compiler is confused but if you break out your closure declaration on to a separate line:
let shouldBeRemoved: (BSBSystemDelegate) -> Bool = { $0 === viewController }
self.delegates.removeAll(where: shouldBeRemoved)
You get a more useful binary operator '===' cannot be applied to two 'BSBSystemDelegate' operands message.
Assuming you change your protocol to make it class-bound, as described by Dan, you could also use code like this:
if let index = array.firstIndex(where: { $0 === aFoo }) {
array.remove(at: index)
}
That would probably be faster for a large array, since it would stop on the first occurence of a match. (removeAll(where:) will always check every element in the array for a match.)
However, the code above would only remove the first instance of the object from the array if the exact same object has been added more than once.

Swift complains of undeclared type, but that doesn't seem to be the problem

I am trying to fetch records in Core Data.
func fetchOrg() {
var **internalOrganization** = [InternalOrganizationMO]() //NSManagedClass
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "InternalOrganization")
fetchRequest.returnsObjectsAsFaults = false
do {
let result = try managedObjectContext.fetch(fetchRequest) as **internalOrganization** **////Compiler flags Error here**
} catch {
fatalError("Failed to fetch internal organization \(error)")
}
}
InternalOrganizationMO is a ManagedObject Class corresponding to the object model and it seems clear to me that internalOrganization is declared to be an array of those objects, so the flagged error seems to be off. My understanding is that this is the kind of object that is supposed to be the target of a fetch, but I am definitely on the learning curve here.
Is it that the fetch needs to be targeted at a Type instead of an array--thus, the complaint about my not providing a named type? If that is it, am I simply supposed to provide the ManagedObject? If that is so, how on earth do I determine how many records are returned?
Is this really better than just using the interface to SQLite?
Thanks, sorry for the rant.
You typecast objects as types, not objects as objects.
Example:
let a = b as! [String]
and not:
let a = [String]()
let c = b as! a
Solution #1:
Change NSFetchRequest<NSFetchRequestResult> to specify the type to be explicitly InternalOrganizationMO, like so:
NSFetchRequestResult<InternalOrganizationMO>
This fetchRequest has now the proper associated type InternalOrganizationMO and will be used accordingly to return objects of this type.
You then won't need to typecast result again and the following code should work just fine:
func fetchOrg() {
let fetchRequest = NSFetchRequest<InternalOrganizationMO>(entityName: "InternalOrganization")
do {
let internalOrganization = try managedContext.fetch(fetchRequest)
/*
internalOrganization will be of type [InternalOrganizationMO]
as that is the return type of the fetch now.
*/
//handle internalOrganization here (within the do block)
print(internalOrganization.count)
}
catch {
fatalError("Failed to fetch internal organization \(error)")
}
}
Solution #2:
If you want this method to work even if the fetchRequest try or typecasting fails then you can do this:
func fetchOrg() {
var internalOrganization = [InternalOrganizationMO]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "InternalOrganization")
do {
internalOrganization = try managedContext.fetch(fetchRequest) as? [InternalOrganizationMO]
/*
You ofcourse wouldn't want the above optional binding to fail but even if it
does, atleast your method can stay consistent and continue with an empty array
*/
}
catch {
fatalError("Failed to fetch internal organization \(error)")
}
//handle internalOrganization here
print(internalOrganization.count)
}
The choice of solution, depends on your design and requirements.

Swift Array holding elements weakly

I am taking some inspiration from
https://marcosantadev.com/swift-arrays-holding-elements-weak-references/
and I want to be able to maintain an array holding weak references to its elements, so that in case those elements get released elsewhere in my code base, I don't retain them in my array.
I would like the implementation to be as type safe as possible, however should be reusable.
The strategy that I am using is declaring a Weak Reference container as so.
class WeakRefContainer<T> where T: AnyObject {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
Then I want to maintain an array of these WeakRefContainers, so I create an array extension:
extension Array where Element: WeakRefContainer<AnyObject> {
func compact() -> [WeakRefContainer<AnyObject>] {
return filter { $0.value != nil }
}
}
When calling the compact method, I am now able to clear up the array in case stuff needs to be cleaned up.
I am now having some compilation issues which am having trouble understanding.
Lets suppose I have a sample class
class SampleClass {
}
And I try to use everything as follows:
var weakReferencesArray = [WeakRefContainer<SampleClass>]()
let obj1 = WeakRefContainer.init(value: SampleClass())
let obj2 = WeakRefContainer.init(value: SampleClass())
weakReferencesArray.append(obj1)
weakReferencesArray.append(obj2)
weakReferencesArray.compact()
When I try to call compact I get the following error message:
MyPlayground.playground:29:21: 'WeakRefContainer<SampleClass>' is not a subtype of 'WeakRefContainer<AnyObject>'
Can anyone unblock me please? Thanks
Your code doesn't work because WeakRefContainer<SampleClass> is not a subclass of WeakRefContainer<AnyObject> because generics are invariant in Swift. Thus weakReferencesArray can't use the compact method added from the extension.
There is a workaround for this, via a protocol:
protocol WeakHolder {
var hasRef: Bool { get }
}
extension WeakRefContainer: WeakHolder {
var hasRef: Bool { return value != nil }
}
extension Array where Element: WeakHolder {
func compacted() -> [Element] {
return filter { $0.hasRef }
}
mutating func compact() {
self = compacted()
}
}
I also renamed compact to compacted, for better Swift semantics, and replaced the original compact by a mutating version.
You probably want the extension to apply to all [WeakRefContainer<T>] where T can be any type extending AnyObject.
extension Array where Element: WeakRefContainer<T> {
However, currently, parameterised extensions are not possible. See this proposal.
You can kind of work around this by making compact generic:
extension Array{
func compact<T>() -> [Element] where Element == WeakRefContainer<T> {
return filter { $0.value != nil }
}
}

Address of Array & Remove(at :) IOS

I'm passing an array of a specific model by reference between ViewControllers.
If I change any value of a specific element in the array it reflects well in all ViewControllers but when I remove an element from that array it doesn't reflect to the other controllers.
Does the remove(at: ) function create new array and refer to another address?
And if so how to delete an element without changing the address of array so it can reflect this change on the other view controllers?
Swift Arrays are value types (specifically, an array is a struct), not reference types, so you are mistaken when you say that you are "passing an array of a specific model by reference between view controllers". You can only ever pass a Swift array as a value.
Arrays, like other structs, have copy-on-modify semantics. As soon as you change the array itself a copy is made and the change is made to the copy.
Now, in your case the array contains references to model objects; When you update the model object you change the object itself, not the reference held in the array, so you see the change reflected in all of your view controllers.
An analogy might be the difference between adding a house to a street (which changes the street itself) versus changing the occupants of an existing house on the street.
I would suggest you implement a model object that provides abstraction from the underlying array so that you have better code and avoid the issue with array references.
One approach could be something like:
struct MyModel {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func removeModel(at: Int) {
guard at >= 0 && at < _models.count else {
return
}
self._models.remove(at: at)
}
}
Although this isn't ideal as it still requires model consumers to know indices in the underlying array. I would prefer something like this:
struct MyModel: Hashable {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func remove(model: MyModel) -> Bool {
if let index = self._models.index(of: model) {
_models.remove(at: index)
return true
} else {
return false
}
}
}
Now I don't need to know what internal collection MyData uses to store the models.
If you need to pass an array (or any other value type) by reference, you could go through an intermediate structure that manages the indirection for you.
[EDIT] changed to use KeyPaths available in Swift 4.
// Generic class to hold a "weak" reference to a property from an object
// including properties that are valued types such as arrays, structs, etc.
// This is merely an encapsulation of Swift's native KeyPath feature
// to make the code a bit more readable and simpler to use
//
class ReferenceTo<ValueType> { var value:ValueType! { get { return nil} set {} } }
class Reference<OwnerType:AnyObject,ValueType>:ReferenceTo<ValueType>
{
internal weak var owner:OwnerType!
internal var property:ReferenceWritableKeyPath<OwnerType,ValueType>! = nil
internal var valueRef:KeyPath<OwnerType,ValueType>! = nil
init(_ owner:OwnerType, _ property:ReferenceWritableKeyPath<OwnerType,ValueType>)
{ (self.owner,self.property) = (owner,property) }
init(_ owner:OwnerType, get valueRef:KeyPath<OwnerType,ValueType>)
{ (self.owner,self.valueRef) = (owner,valueRef) }
override var value:ValueType!
{
get { return valueRef != nil ? owner?[keyPath:valueRef] : owner?[keyPath:property] }
set { owner?[keyPath:property] = newValue }
}
}
With this generic class you can create references to valued type properties of object instances and manipulate them anywhere in your code as if the valued type property was a reference type.
// Example class with a read/write and a read-only property:
class MyObject
{
var myArray = [1,2,3,4]
var total:Int { return myArray.reduce(0,+) }
}
var instance:MyObject! = MyObject()
// create a reference to the array (valued type)
// that can be used anywhere and passed around as a parameter
let arrayRef = Reference(instance, \.myArray)
// the value is accessed and manipulated using the
// "value" property of the reference
arrayRef.value.remove(at:2)
arrayRef.value.append(5)
print(instance.myArray) // [1,2,4,5]
// Read-only properties can also be manipulated as
// references
let valueRef = Reference(instance, get:\.total)
print(valueRef.value) // 12
The Reference class allows passing the value as a reference to function parameters
// a function that expects a reference to an array
// would be declared as follows
func changeArray(_ array:ReferenceTo<[Int]>)
{ array.value.insert(9, at: 1) }
// the reference can also be used as an inout parameter
func shift(_ array:inout [Int])
{ array = Array(array.dropFirst()) + Array(array.prefix(1)) }
changeArray(arrayRef)
shift(&arrayRef.value!)
print(instance.myArray) // [9,2,4,5,1]
...
// the reference uses a weak link to the owner
// of the referenced property or value
// so there will be no strong reference cycle issues even
// if the reference is used in an object held strongly
// by the owner itself
instance = nil
print(arrayRef.value) // none ... no more value after the owner is gone

Categories

Resources