I have an array of String? like so
var myArray: [String?] = ["2", "banana", nil, "31"];
I have another array of String like so:
var myStringArray: [String] = ["2", "3"]
I wrote an extension for Array to return me an Optional if index is not valid (which avoids the whole app crashing), but I really only want it to return Optional if the Element is NOT optional, and just return Element if it is Optional
func safeIndex(_ index: Int) -> Element? {
if self.indices.contains(index) {
return self[index]
}
return nil
}
If i run this against myArray the resulting type is String??
let item = myArray.safeIndex(1) // item is String??
let strItem = myStringArray.safeIndex(99) // strItem is String?
How do i change my extension such that it gives me String? for BOTH cases
Note: If i am explicit about my types, I can get what I want like so
extension Array where Element == String? {
func safeIndex(_ index: Int) -> String? {
if self.indices.contains(index) {
return self[index]
}
return nil
}
}
However, this approach requires me to write a bunch of extensions for a bunch of types i might have in an array which is a massive meh.
This is kind of tricky but you can create an AnyOptional protocol that requires an associatedtype (Wrapped) and a computed property to return the optional type. Then you can return the element unwrapped if the index is valid otherwise return nil.
protocol AnyOptional {
associatedtype Wrapped
var optional: Optional<Wrapped> { get }
}
extension Optional: AnyOptional {
var optional: Optional<Wrapped> { self }
}
extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
extension Collection {
subscript(safe index: Index) -> Element.Wrapped? where Element: AnyOptional {
indices.contains(index) ? self[index].optional ?? nil : nil
}
}
var myArray: [String?] = ["2", "banana", nil, "31"]
var myStringArray: [String] = ["2", "3"]
let item = myArray[safe: 1] // item is String?
let strItem = myStringArray[safe: 99] // strItem is String?
I think what you want could be achieved with two extensions and a dummy protocol, which would constrain the array's Element. Something like that:
protocol OptionalType {}
extension Optional: OptionalType {}
extension Array where Element: OptionalType {
func safeIndex(_ index: Int) -> Element {
return self[index]
}
}
extension Array {
func safeIndex(_ index: Int) -> Element? {
if self.indices.contains(index) {
return self[index]
}
return nil
}
}
Then this would work:
var myArray: [String?] = ["2", "banana", nil, "31"];
var myStringArray: [String] = ["2", "3"]
let item = myArray.safeIndex(1)
print(type(of: item)) // Optional<String>
let strItem = myStringArray.safeIndex(99)
print(type(of: strItem)) // Optional<String>
Related
How can you check if a type is Optional in Swift?
Say I have a variable of type PartialKeyPath where:
struct Foo {
let bar: String
let baz: String?
}
typealias Property<Root> = (key: PartialKeyPath<Root>, value: Any?)
typealias Properties<Root> = [Property<Root>]
Now say I iterate thru an instance of Properties:
properties.forEach { prop in
let valueType1 = type(of: prop.key).valueType
let valueType2 = type(of: value)
...
How can I check here whether valueType1 is Optional<valueType2>, or whether it is Optional of any other flavor for that matter?
So far the only way I’ve found is really ugly...
Using a similar approach to Optional field type doesn't conform protocol in Swift 3, you could define a 'dummy protocol' for Optional and use this to get the wrapped metatype:
protocol OptionalProtocol {
// the metatype value for the wrapped type.
static var wrappedType: Any.Type { get }
}
extension Optional : OptionalProtocol {
static var wrappedType: Any.Type { return Wrapped.self }
}
If you just want to know a type is an optional:
func isOptionalType(_ type: Any.Type) -> Bool {
return type is OptionalProtocol.Type
}
print(isOptionalType(String.self)) // false
print(isOptionalType(String?.self)) // true
If you want to check if one metatype is the 'optional version' of another metatype:
struct Foo {
let bar: String
let baz: String?
}
struct Property<Root> {
var key: PartialKeyPath<Root>
var value: Any
}
let properties = [Property(key: \Foo.baz, value: "hello")]
/// Attempt to get the `Wrapped` metatype from a metatype of an
/// `Optional<Wrapped>`. If not an `Optional`, will return `nil`.
func wrappedTypeFromOptionalType(_ type: Any.Type) -> Any.Type? {
return (type as? OptionalProtocol.Type)?.wrappedType
}
for property in properties {
let valueType1 = type(of: property.key).valueType
let valueType2 = type(of: property.value)
if wrappedTypeFromOptionalType(valueType1) == valueType2 {
print("\(valueType1) == Optional<\(valueType2)>")
}
}
// Optional<String> == Optional<String>
However there's almost certainly a better way to do whatever you're trying to do here with the key paths.
could you use a mirror reflecting Any and check displayStyle is optional?.
func isOptional(any:Any) -> Bool {
let mirror = Mirror(reflecting: any)
if mirror.displayStyle == .Optional {
return true
} else {
return false
}
}
More on mirror display style:
https://developer.apple.com/documentation/swift/mirror.displaystyle
This is a hacky but working solution:
func isOptional(_ type: Any.Type) -> Bool {
let typeName = String(describing: type)
return typeName.hasPrefix("Optional<")
}
Test:
let t1 = Int?.self
let t2 = Bool.self
print(isOptional(t1))
// true
print(isOptional(t2))
// false
A tweak of #kelin’s answer:
postfix operator ...?!
postfix func ...?!<T>(_ instance: T) -> Bool {
let subject = "\(Mirror(reflecting: instance).subjectType)"
return !subject.hasPrefix("Optional")
}
And in the vein of #Ercell0’s answer is this superior method:
func isOptional<T>(_ instance: T) -> Bool {
guard let displayStyle = Mirror(reflecting: instance).displayStyle
else { return false }
return displayStyle == .optional
}
I need help with solving the error:
Cannot invoke initializer for type 'QuotesViewController.RandomItems' with an argument list of type '(items: [String], seen: Int)'
This is my code:
struct RandomItems: Codable
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
func toPropertyList() -> [String: Any] {
return [
"items": items,
"seen": seen
]
}
}
}
extension QuotesViewController.RandomItems {
init?(propertyList: [String: Any]) {
return nil
}
}
let a = QuotesViewController.RandomItems(items: ["hello"], seen: 2) //<-- Error
let data: Data = try! JSONEncoder().encode(a)
let b = try! JSONDecoder().decode(QuotesViewController.RandomItems.self, from: data)
This line
let a = QuotesViewController.RandomItems(items: ["hello"], seen: 2) //<-- Error
invokes an initialiser that does not exist. Create one like this:
init(items:[String], seen: Int)
{
self.items = items
self.seen = seen
}
For good measure, change the existing initialiser to
init(_ items: [String])
{
self.init(items: items, seen: 0)
}
so that, if you need to add extra stuff to the initialisers, you only need to do it in one place.
Or just have one initialiser
init(items:[String], seen: Int = 0)
{
self.items = items
self.seen = seen
}
I'm working on a project in Swift 3 where I have a tuple array with duplicate values is there a way to save it to a NSSet or to avoid replicating the same value. The structure of my tuple array as follow.
var selectedSongList : [(title: String, healerName: String, trackUrl: String, trackID: String, imageUrl: String)] = []
Thus later I'm using this to poplate my UITableView
There are two ways to do it.
Solution 1
You can create a structure and it should confirm to Hashable and equatable some thing like this
struct Post: Hashable, Equatable {
let id: String
var hashValue: Int { get { return id.hashValue } }
}
func ==(left:Post, right:Post) -> Bool {
return left.id == right.id
}
and to remove your object you can do like this
let uniquePosts = Array(Set(posts))
Solution 2
Create a set out of your array and then make it back to array.
Edit func static func == in Song struct if you have different definition about the same song
struct Song: Equatable {
var title: String?
var healerName: String?
var trackUrl:String?
var trackID:String?
var imageUrl: String?
init(_ title: String, _ healerName:String, _ trackUrl:String, _ trackID:String,_ imageUrl: String) {
self.title = title
self.healerName = healerName
self.trackUrl = trackUrl
self.trackID = trackID
self.imageUrl = imageUrl
}
static func == (lhs: Song, rhs: Song) -> Bool {
return lhs.title == rhs.title &&
lhs.healerName == rhs.healerName &&
lhs.trackUrl == rhs.trackUrl &&
lhs.trackID == rhs.trackID &&
lhs.imageUrl == rhs.imageUrl
}
}
extension Array where Element:Equatable {
func removeDuplicates() -> [Element] {
var result = [Element]()
for value in self {
if result.contains(value) == false {
result.append(value)
}
}
return result
}
}
The below playground outlines my issue. The extension will remove nil values from my dictionary, but leave the other values as Optional(Value). What I need is a dictionary that has no nil values and make the optional value type non-optional.
Ex: I have a dictionary of [String:Int?]. I want the jsonSantize() of that called on that dictionary to return a [String:Int].
//: Playground - noun: a place where people can play
import UIKit
import Foundation
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value : OptionalType{
//Sanitizes the current dictionary for json serialization
//Removes nil values entirely. Makes optionals non-optional
func jsonSanitize() -> Dictionary<Key,Value> {
var newDict:[Key:Value] = [:]
for (key, value) in self {
if value.asOptional != nil {
newDict.updateValue(self[key]!, forKey: key)
}
}
return newDict
}
}
var youGood = false
var stringMan:String? = youGood ?
"WOHOO!" :
nil
var dict:[String:Any?] = [
"stuff":"THINGIES",
"things": stringMan
]
var dict2 = dict.jsonSanitize()
print(dict2)
var test = (stringMan != nil)
UPDATE: Suggestion made to use Value.Wrapped as new dictionary type
//: Playground - noun: a place where people can play
import UIKit
import Foundation
protocol OptionalType {
associatedtype Wrapped
var asOptional : Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional : Wrapped? {
return self
}
}
extension Dictionary where Value : OptionalType{
//Sanitizes the current dictionary for json serialization
//Removes nil values entirely. Makes optionals non-optional
func jsonSanitize() -> Dictionary<Key,Value.Wrapped> {
var newDict:[Key:Value.Wrapped] = [:]
for (key, value) in self {
if let v = value.asOptional {
newDict.updateValue(v, forKey: key)
}
}
return newDict
}
}
var youGood = false
var stringMan:String? = youGood ?
"WOHOO!" :
nil
var dict:[String:Any?] = [
"stuff":"THINGIES",
"things": stringMan
]
var dict2:[String:Any] = dict.jsonSanitize()
print(dict2)
var test = (stringMan != nil)
Your method produces a dictionary of the same type [Key: Value]
with Value being some optional type. What you probably want is
to produce a dictionary of type [Key: Value.Wrapped]:
extension Dictionary where Value: OptionalType {
func jsonSanitize() -> [Key: Value.Wrapped] {
var newDict: [Key: Value.Wrapped] = [:]
for (key, value) in self {
if let v = value.asOptional {
newDict.updateValue(v, forKey: key)
}
}
return newDict
}
}
Example:
let dict: [String: Int?] = [
"foo": 1234,
"bar": nil
]
var dict2 = dict.jsonSanitize()
print(dict2) // ["foo": 1234]
Note also that of Swift 3.0.1/Xcode 8.1 beta, optionals
are bridged to NSNull instances automatically, see
SE-0140 – Warn when Optional converts to Any, and bridge Optional As Its Payload Or NSNull
How can I check in Swift if a value is an Array. The problem is that an array of type Int can apparently not be casted to an array of type Any. Suppose I have an array myArray of type Int and execute the following:
if let array = myArray as? [Any] { return true }
it doesn't return true (which surprises me actually). The same thing appears with dictionaries. I want a dictionary of type String, Any (meaning that Any can be any type). How can I check if it is?
Thanks in advance.
Got it working like this, although it's not as beautiful as I would've hoped:
protocol ArrayType {}
extension Array : ArrayType {}
let intArray : Any = [1, 2, 3]
let stringArray : Any = ["hi", "hello", "world"]
intArray is ArrayType // true
stringArray is ArrayType // true
EDIT: I think I misunderstood your question before, now I got it though:
let intArray = [1, 2, 3]
let anyArray = intArray.map{ $0 as Any }
This is the only way to my knowledge.
You can simply
array is Array<Any>
Got it now, with the idea of #Kametrixom. It's extraordinary ugly but at least it works.
private protocol CJSONArrayType {
func toAny() -> [Any]
}
extension Array: CJSONArrayType {
private func toAny() -> [Any] {
return self.map { $0 as Any }
}
}
extension NSMutableArray: CJSONArrayType { }
extension NSArray: CJSONArrayType {
private func toAny() -> [Any] {
var result = [Any]()
for item in self {
result.append(item as Any)
}
return result
}
}
private protocol CJSONDictionaryType {
func toStringAny() -> [String: Any]
}
extension Dictionary: CJSONDictionaryType {
private func toStringAny() -> [String : Any] {
var result = [String: Any]()
for item in self {
result[item.0 as! String] = item.1 as Any
}
return result
}
}
extension NSMutableDictionary: CJSONDictionaryType { }
extension NSDictionary: CJSONDictionaryType {
private func toStringAny() -> [String : Any] {
var result = [String: Any]()
for item in self {
result[item.0 as! String] = item.1 as Any
}
return result
}
}
If you want to parse JSON, there are only a few supported types which are at least AnyObject rather than Any.
Then it's very easy to check for Array
func checkArray(item : AnyObject) -> Bool {
return item is Array<AnyObject>
}
let integer = 1
let string = "one"
let array = ["one", "two", "three"]
let dictionary = ["one" : 1, "two" : 2]
checkArray(integer) // false
checkArray(string) // false
checkArray(array) // true
checkArray(dictionary) // false
Apple highly recommends to constrain the types at compile time as much as possible