The black magic in 3.5 Protocol-oriented programming: Getting fancy with extensions

I agree with the author, Marin Bencevic, that this is some Swift Extension black magic going on.

Can some help me understand the magic that connects the Array extension:

extension Array: URLSessionDecodable where Element == Article {
  init(from output: Data) throws {
    let decoder = JSONDecoder()
    let articlesCollection = try decoder.decode(Articles.self, from: output)
    let articles = articlesCollection.data.map { $0.article }
    self.init(articles)
  }
}

To the [Article].init in the .tryMap() below?:

networker.fetch(request)
  .tryMap([Article].init)
  .replaceError(with: [])
   ...

Thanks!

@maczealot This is quite a tricky one to get right because of the way in which the Combine framework behaves error wise.

You use the tryMap(_:) instance method over here to throw any errors that might occur during the decoding process of the articles.

Please check out how the method actually works under the hood over here:

https://developer.apple.com/documentation/combine/fail/trymap(_:)

tryMap(_:) always takes a throwable closure as its only parameter, so you go ahead and plug in the custom throwable initializer from the Array extension as the method’s only argument in this case.

This is actually the only throwable Array initializer out there that only works with Article elements from the compiler’s point of view, so it definitely does not get confused when choosing the right initializer for the tryMap(_:) method’s argument because of that.

You can read even more about all of the other standard initializers for the Array structure over here:

https://developer.apple.com/documentation/swift/array

Please let me know if you have any more questions or other issues about the whole thing when you get a chance.

Thank you!

1 Like