conversion of protocol array to any object in swift - arrays

I have a function storeInCache that accepts an AnyObject?
When I try to give it an optional array of protocol objects it fails with compilation error "cannot invoke 'storeInCache' with an argument of list of type '([HasImage]?, String)'". Basically it cannot convert the
[HasImage]? to AnyObject? I believe.
I do not understand this, because the array holds objects (that comply with the HasImage protocol yes), its an array so its an AnyObject no?
I tried different ways of casting but none of them worked.
How can this be solved in swift?
Sample code:
protocol HasImage {
var imageUrl : String? {get}
}
class Product : NSObject, HasImage {
var imageUrl : String?
init(imageUrl : String?) {
self.imageUrl = imageUrl
}
}
func storeInCache(obj : AnyObject?, key : String)
{
//if no object supplied nothing to store in cache!
//...
}
func testCaching(addDummyData : Bool) {
var objectsWithImages : [HasImage]?
if addDummyData {
objectsWithImages = [Product(imageUrl: "http://wwww.someserver.com/p1/image.jpg"),Product(imageUrl: "http://wwww.someserver.com/p2/image.jpg")]
}
//fails compilation with => cannot invoke 'storeInCache' with an argument of list of type '([HasImage]?, String)'
storeInCache(objectsWithImages,"somekey")
}

Add #objc to your protocol declaration and it should be fine,
#objc
protocol HasImage {
var imageUrl : String? {get}
}

Related

SwiftUI: How do I display multidimensional array as sections in List?

I have the following object as a var events = [[EventLog]] and I need to loop through each inner-array and display them in a section. For example, events[0] might have events[0][0].id events[0][1].id and events[0][2].id as values and events[1] may only have 1 element. This is the object that I've built for reference.
Model Object
class EventLog: Identifiable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
init(ipAddress: String, date: String, getMethod: String, statusCode: String, secondStatusCode: String, versionInfo: String ){
self.id = UUID()
self.ipAddress = ipAddress
self.date = date
self.getMethod = getMethod
self.statusCode = statusCode
self.secondStatusCode = secondStatusCode
self.versionInfo = versionInfo
}
}
Attempts At Using
Doing it this way results in this error: Referencing initializer 'init(_:content:)' on 'ForEach' requires that '[EventLog]' conform to 'Identifiable' It should be noted that the multidimensional array is stored on a ViewModel named landingPageVM which is not referenced here for the sake of brevity. It is however an #Published property of the view model.
ForEach(landingPageVM.eventLogs) { logs in
ForEach(logs)) { log in
Text(log.id.description)
}
}
If you can change your model to a struct instead of a class, you'll have an easier time with this (and a lot of other things in SwiftUI):
struct ContentView : View {
#State var eventLogs : [[EventLog]] = []
var body: some View {
List {
ForEach(eventLogs, id: \.self) { logs in
Section(header: Text("\(logs.count)")) {
ForEach(logs) { log in
Text(log.id.description)
}
}
}
}
}
}
struct EventLog: Identifiable, Hashable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
}
If you're stuck using a class for some reason, you can still get this behavior by doing something like:
extension EventLog : Hashable, Equatable {
func hash(into hasher: inout Hasher) {
hasher.combine(id) //etc
}
static func == (lhs: EventLog, rhs: EventLog) -> Bool {
lhs.id == rhs.id && lhs.ipAddress == rhs.ipAddress //etc
}
}
Details of the hash and == implementations may vary.

Cast to right generic from array in Swift

I have a Protocol called Composite.
This protocol has an array composites: [Composite]
I also have a generic subclass GenericSubclass<T>: Composite
When iterating over the array the best I can come up with looks like this:
for item in composites {
if let item = item as? GenericSubclass<A> {
let sc = SomeOtherClass<A>
} else if let item = item as? GenericSubclass<B> {
let sc = SomeOtherClass<B>
} //and so on...
}
Is there any way to get a hold of GenericSubclass without specifying the Generic? In my use case there is absolutely no need for me to know about the T. I just have to instantiate another class with the same generic type.
Any help is much appreciated.
It's not clear what you're trying to accomplish with the "generic" (pun intended) class names you've chosen. I don't think there's a way to directly accomplish what you want. I.e. you can't just leave it as a generic T because the compiler needs some way to determine what T will be in use at runtime.
However, one way to solve the issue is to hoist the API into the Composite protocol:
protocol Composite {
var composites: [Composite] { get set }
func otherClass() -> OtherProtocol
}
protocol OtherProtocol { }
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
func otherClass() -> OtherProtocol {
return SomeOtherClass<T>()
}
}
class SomeOtherClass<T>: OtherProtocol {}
So now when you implement your loop, you can rely on the fact that since each element is a Composite, you know it must provide an instance of OtherProtocol via the otherClass() method:
var c = GenericSubclass<Int>()
c.composites = [GenericSubclass<Double>(), GenericSubclass<Int>(), GenericSubclass<Character>()]
for item in c.composites {
let sc = item.otherClass()
print(sc)
}
Alternatively, if only GenericSubclass should vend an OtherProtocol, you can make the return type Optional and define an extension for all the other implementations of Composite:
protocol Composite {
var composites: [Composite] { get set }
func optionalClass() -> OtherProtocol?
}
extension Composite {
func optionalClass() -> OtherProtocol? {
return nil
}
}
I did some experiment on this in the playground and i came up with this
protocol Composite {
var composites: [Composite] { get set }
}
class GenericSubclass<T>: Composite {
var composites: [Composite] = []
}
let subclass = GenericSubclass<String>()
for item in subclass.composites {
let className = String(describing: type(of: item))
let aClassType = NSClassFromString(className) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(instance) //Output: GenericSubclass<String>
}
Hope this will help someone.
I think it's not possible to do that in array.
While you creat some different GenericSubclass<T> then put it in array , you will lose <T> no matter the composites is [Composite] or [Any].
// this line won't compile
let array = [GenericSubclass<Int>(),GenericSubclass<Double>()]
//error: heterogenous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
You want donging something like this func below, the param should be GenericSubclass<T> to compile success
func genericFunc<T>(param:GenericSubclass<T>) {
let sc = SomeOtherClass<T>()
print(sc)
}
Anyway you can implement it with member var for the instance like the code below:
class Subclass {
var type : Any
init(type : Any) {
self.type = type
}
}
class SomeOtherClass : CustomDebugStringConvertible{
var type : Any
init(type : Any) {
self.type = type
}
var debugDescription: String{
return String(describing: type.self)
}
}
let array : [Subclass] = [Subclass(type : Int.self),Subclass(type : Double.self),Subclass(type : String.self)]
let scArray = array.flatMap {SomeOtherClass(type:$0.type.self)}
print(scArray) // prints [Int, Double, String]
You need to add one method to protocol which creates new item of Type supported this protocol. So now you can use enums, structs and classes without any knowledge of creating object of specific type.
You can play in playground with the following code:
import UIKit
//This is your protocol
protocol MyAwesomeProtocol {
//this methods leaves implementaion detailes
//to concrete type
func createNewObject()->MyAwesomeProtocol
}
//Just create empty string
extension String: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return String()
}
}
//create Enum with default value
extension UIControlState: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return UIControlState.normal
}
}
//create viewController of any type
extension UIViewController: MyAwesomeProtocol {
func createNewObject() -> MyAwesomeProtocol {
return type(of:self).init()
}
}
//This is test function
//it creates array of newly created items and prints them out
//in terminal
func doSomeCoolStuffWith(items:[MyAwesomeProtocol]){
var newItems = [MyAwesomeProtocol]()
for anItem in items {
let newOne = anItem.createNewObject()
newItems.append(newOne)
}
print("created new ones:\n\(newItems)\nfrom old ones:\n\(items)\n")
}
doSomeCoolStuffWith(items: [UIControlState.focused,UIControlState.disabled])
doSomeCoolStuffWith(items: [UISplitViewController(),UINavigationController(),UICollectionViewController()])
doSomeCoolStuffWith(items: ["I","love","swift"])
This will produce the following result:
created new ones:
[__C.UIControlState(rawValue: 0), __C.UIControlState(rawValue: 0)]
from old ones:
[__C.UIControlState(rawValue: 8), __C.UIControlState(rawValue: 2)]
created new ones:
[<UISplitViewController: 0x7fa8ee7092d0>, <UINavigationController: 0x7fa8f0044a00>, <UICollectionViewController: 0x7fa8ee705f30>]
from old ones:
[<UISplitViewController: 0x7fa8ee7011e0>, <UINavigationController: 0x7fa8f004e600>, <UICollectionViewController: 0x7fa8ee708fb0>]
created new ones:
["", "", ""]
from old ones:
["I", "love", "swift"]

Store generic Arrays in NSUserDefaults

I'm trying to store a generic Array in NSUserDefaults but I get the following error: Cannot convert value of type 'Array<T>' to expected argument type 'AnyObject?'.
How can I solve this problem?
public class PropertyStore {
private let userDefaults = NSUserDefaults.standardUserDefaults()
public func loadSet<T>(key: String) -> Set<T>? {
guard let array = userDefaults.objectForKey(key) as? [T] else {
return nil
}
return Set<T>(array)
}
public func saveSet<T>(key: String, value: Set<T>) {
let array = Array(value)
userDefaults.setObject(array, forKey: key) // <- ERROR
}
}
Like #lucasD said, T needs to conform to NSCoding atleast. So the code looks like this.
public func saveSet<T: NSCoding>(key: String, value: Set<T>) {
let array = Array(value)
userDefaults.setObject(array, forKey: key)
}
However, this will not work for many reasons like:
public func loadSet<T: NSCoding>(key: String) -> Set<T>? {
guard let array = userDefaults.objectForKey(key) as? [T] else {
return nil
}
return Set<T>(array)
}
let defaults = PropertyStore()
defaults.saveSet("array", value: [1,2,3])
///defaults.loadSet<Int>("array") ===> Cannot explicitly specialize a function
///defaults.loadSet("array") ===> Cannot infer Type T
//let a: Set<Int>? = defaults.loadSet("array") ==> T cannot be inferred
In the case of loadSet type T cannot be inferred properly because we cannot specify it from outside, as far as i know. I would first try to return NSObject or Set<AnyObject> or Set<NSCoding> and then type cast it explicitly. Let me know if theres a better way though.
You can take a look at this SO post for more information on why a generics parameter cannot be specialised from outside. SO Generics specialization
You should define T as NSCoding conforming class. So when you're going to store the array, you are going to store an array of NSKeyedArchiver.archiveDataWithRootObject() results. Then, to return a Set<T> in the loadSet method, you should unarchive all the objects:
let storedData = NSUserDefaults.standardUserDefaults. ...
return storedData.flatMap { return NSKeyedUnarchiver.unarchiveObjectWithData($0) }

Trying to decode arrays with Argo

I am trying to decode data from JSON into a very generic struct using Argo (https://github.com/thoughtbot/Argo):
struct ValueBox<T: Decodable where T == T.DecodedType> {
let value: T
}
extension ValueBox: Decodable {
static func decode(json: JSON) -> Decoded<ValueBox> {
let r = curry(ValueBox.init)
<^> json <| "value"
return r
}
}
extension Array: Decodable {
public typealias DecodedType = Array<Element>
}
extension Array {
public static func decode(json: JSON) -> Decoded<Array<Element>> {
return Decoded<Array>.customError("not implemented")
}
}
This compiles. I know it would not be able to decode ValueBox in case of T being an array. But that is a second problem.
If I now try to use Argo for decoding:
func testExample() {
let jsonDict_Int: [String : AnyObject] = [
"value" : 5
]
let jsonDict_IntArray: [String : AnyObject] = [
"value" : [5]
]
let intBox: Decoded<ValueBox<Int>> = decode(jsonDict_Int)
let intArrayBox: Decoded<ValueBox<Array<Int>>> = decode(jsonDict_IntArray)
}
I get a compiler error that "Array<Int> does not conform to protocol 'Decodable'". But why? I provided the extension to make it conform, or am I missing something obvious?
For the second example, I think you must use <^> json <|| "value" instead of <^> json <| "value"
When you use an Array of value you must use the operator <||
a great link to the detailed Argo's documentation

Cannot convert value of type 'Meme!' to expected argument type '#noescape (Meme) throws -> Bool'

Here is the code:
#IBAction func deleteMeme(sender: UIBarButtonItem) {
if let foundIndex = MemeRepository.sharedInstance.memes.indexOf(selectedMeme) {
//remove the item at the found index
MemeRepository.sharedInstance.memes.removeAtIndex(foundIndex)
navigationController?.popViewControllerAnimated(true)
The error happens at the .indexOf method at (selectedMeme).
Cannot convert value of type Meme! to expected argument type #noescape (Meme) throws -> Bool
Meme! is a struct for my app. How do I work through this?
struct Meme {
var topText : String!
var bottomText: String!
var image: UIImage!
var memedImage: UIImage!
init(topText: String, bottomText: String, image: UIImage, memedImage: UIImage) {
self.topText = topText
self.bottomText = bottomText
self.image = image
self.memedImage = memedImage
The error message is misleading. What you actually need is to provide the compiler a way to compare two Meme instances and decide upon which criteria those instances are equal.
Let's say you want two instances having the same name property to be treated as equal.
We make the struct conform to Equatable and we also create an == operator that compares two structs by their name property:
struct Meme:Equatable {
var name:String!
}
func ==(lhs: Meme, rhs: Meme) -> Bool {
return lhs.name == rhs.name
}
Now we can use indexOf with a Meme instance:
let doge = Meme(name: "doge")
let lolcat = Meme(name: "lolcat")
let memes = [lolcat, doge]
if let dogeIndex = memes.indexOf(doge) {
print(dogeIndex) // 1
}
If you wanted to compare two instances not by their name property but by their uniqueness, then you would have to make the struct conform to Hashable and use a unique hashValue property returning an Int:
struct Meme:Hashable {
var name:String!
var hashValue: Int {
return self.name.hashValue
}
init(name: String) {
self.name = name
}
}
func ==(lhs: Meme, rhs: Meme) -> Bool {
return lhs.hashValue == rhs.hashValue
}
let doge = Meme(name: "doge")
let lolcat = Meme(name: "lolcat")
let memes = [lolcat, doge]
if let dogeIndex = memes.indexOf(doge) {
print(dogeIndex) // 1
}
In this example the hashValue is made from self.name, so two different instances of Meme with a same name property will be considered equal. If you don't want that, use another source for the hash value.
Note: in Swift 3, indexOf has become index(of:), so for this example we would change memes.indexOf(doge) to memes.index(of: doge).
If you want to put the comparison inside the indexOf method itself, do it like this:
if let foundIndex = MemeRepository.sharedInstance.memes.indexOf({
UIImagePNGRepresentation($0.memedImage) == UIImagePNGRepresentation(selectedMeme.memedImage)})
Probably not the best way to compare images. If you know the images are the same object, you can use:
.indexOf({$0.memedImage == selectedMeme.memedImage})
but if you want to compare them pixel by pixel or compare the same image scaled to different sizes, that is a little more complicated.

Resources