nacross
nacross

Reputation: 2043

How can I write a function that will unwrap a generic property in swift assuming it is an optional type?

So far I have only been able to achieve this using a global function. I am not sure if it is possible but I was hoping to write an extension to a generic class that would hopefully achieve the same thing.

Below is the working global function it is using SignalProducer class from ReactiveCocoa but the principle should be the same for any generic class.

func ignoreNilValues <Value,Error> (producer: SignalProducer<Value?,Error>) -> SignalProducer<Value, Error> {
   return producer.filter { return $0 != nil }.map { $0! }
}

Update:

I have made progress but have still fallen short of a complete solution

Given any class with some generic property

class GenericClass<SomeType> {
    var someProperty: [SomeType] = []
}

How can I write an extension that will filter any optional values and return the value using the Wrapped type?

The following will filter any nil values but still return it as the Optional type.

protocol AnOptional {
    var isNil: Bool {get}
}

extension Optional : AnOptional {
    var isNil: Bool {
        get {
            guard let hasValue = self.map({ (value: Wrapped) -> Bool in
                return true
            }) else {
                return true
            }

            return !hasValue
        }
    }
}

extension GenericClass where SomeType : AnOptional {
    func filterNilValuesOfSomeProperty() -> [SomeType] {
        return someProperty.filter({ (anOptional: AnOptional) -> Bool in
            return !anOptional.isNil
        })
    }
}

As can be seen

let aClass = GenericClass<Int?>()

aClass.someProperty = [3,5,6,nil,4,3,6, nil]

let x = aClass.someProperty 
//x = [Some(3),Some(5),Some(6),nil,Some(4),Some(3),Some(6), nil]

let y = aClass.filterNilValuesOfSomeProperty()
//y = [Some(3),Some(5),Some(6),Some(4),Some(3),Some(6)]

Is it possible to write a class extension that would return the wrapped type? In the example above it would be [Int] instead of [Int?].

I rewrote the global function solution for this example.

func ignoreNilValues <Value> (aClass: GenericClass<Value?>) -> GenericClass<Value> {
    let aNewClass = GenericClass<Value>()

    aNewClass.someProperty = aClass.someProperty.filter({ (v: Value?) -> Bool in
        v != nil
    }).map { (oldValue: Value?) -> Value in
        return oldValue!
    }

    return aNewClass
}

let z = ignoreNilValues(aClass).someProperty
//z = [3, 5, 6, 4, 3, 6]

Upvotes: 2

Views: 2086

Answers (2)

Keshav
Keshav

Reputation: 3273

I have this solution using in my app, create a protocol, and added an extension to Optional.

protocol OptionalUnwrap {
  associatedtype Wrapped
    func unwrap(default defaultValue: @autoclosure () -> Wrapped) -> Wrapped
}

extension Optional: OptionalUnwrap {
    func unwrap(default defaultValue: @autoclosure () -> Wrapped) -> Wrapped {
      if let value = self {
        return value
      }
      return defaultValue()
    }
}

You can use it like this, you have to provide a default value, so if optional is nil it will return the default value. It works with all types.

struct StructName {
  var name: String
  var age: Int
}
var structName3: StructName?

let unwrapped = structName3.unwrap(default: StructName(name: "", age: 2345))
print(unwrapped.age)


var version: Int?
version.unwrap(default: 5)

var subject: String? = "iOS"
subject.unwrap(default: "")

Upvotes: 0

Martin R
Martin R

Reputation: 539685

The "trick" is to define a protocol to which all optionals conform (this is from Creating an extension to filter nils from an Array in Swift with a minor simplification; the idea goes back to this Apple Forum Thread):

protocol OptionalType {  
    typealias Wrapped 
    func intoOptional() -> Wrapped?  
}  

extension Optional : OptionalType {  
    func intoOptional() -> Wrapped? {  
        return self 
    }  
}  

You can use that in your case as:

class GenericClass<SomeType> {
    var someProperty: [SomeType] = []
}

extension GenericClass where SomeType : OptionalType {
    func filterNilValuesOfSomeProperty() -> [SomeType.Wrapped] {
        return someProperty.flatMap { $0.intoOptional() }
    }
}

which uses the flatMap() method from SequenceType:

extension SequenceType {
    /// Return an `Array` containing the non-nil results of mapping
    /// `transform` over `self`.
    ///
    /// - Complexity: O(*M* + *N*), where *M* is the length of `self`
    ///   and *N* is the length of the result.
    @warn_unused_result
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

Example:

let aClass = GenericClass<Int?>()
aClass.someProperty = [3,5,6,nil,4,3,6, nil]

let x = aClass.someProperty 
print(x) // [Optional(3), Optional(5), Optional(6), nil, Optional(4), Optional(3), Optional(6), nil]

let y = aClass.filterNilValuesOfSomeProperty()
print(y) // [3, 5, 6, 4, 3, 6]

In Swift 3 and later the protocol has to be defined as

protocol OptionalType {
    associatedtype Wrapped
    func intoOptional() -> Wrapped?
}

Upvotes: 4

Related Questions