How to access implict closure parameters from higher closure level - swift

I want to iterate an enum and then use $0 in a switch/case statement down one level in a closure that is called in a fetch operation inside the enum iteration loop, as follows:
enum GenericType: CaseIterable {
case purchase
case sale
// etc....
}
Then the code to use is as follows:
GenericType.allCases.forEach {
// let type = $0
Manager.fetchItems(ofType: $0, onSuccess: { (data) in
switch $0 {
case purchase:
// Do something
case sale:
// Do something
}
}
Xcode 10 assumes that $0 refers to data (the parameter in the closure) and gives this error message:
Anonymous closure arguments cannot be used inside a closure that has explicit arguments; did you mean 'data'?
I am able to make it work with before the fetch:
let type = $0
And then using type in the switch/case statement.
Is there a way to access the $0 shorthand argument from the higher level context inside a closure? Is the workaround a feasible solution?
Thx

$0 can only ever refer to the first closure context "up the chain". To access the parameters of outter closures, you need to name them:
GenericType.allCases.forEach { genericType in
Manager.fetchItems(ofType: genericType, onSuccess: { data in
switch genericType {
case .purchase: return
// Do something
case .sale: return
// Do something
}
})
}

Building on #Alexander and #Daniel's answer/inputs, this is the way I implemented the for loop without the switch-case statement.
enum GenericType: CaseIterable {
case purchase
case sale
var manager: GenericManager {
switch self {
case .purchase:
return PurchaseManager.shared
case .sale:
return SalesManager.shared
}
}
}
PurchaseManager and SaleManager are subclasses of GenericManager and they override the processFetchData(_:) method.
And the code is as follows:
GenericType.allCases.forEach { genericType in
FetchManager.fetchItems(ofType: genericType, onSuccess: { data in
genericType.manager.processFetchData(data)
})
}
Strictly speaking I only moved the switch statement from the biz logic to the enum declaration, but it makes it a bit more elegant.

Related

Swift: use inOut parameter in escaping closure

I have the following method:
private func retrieveFromAPIAndMapToCD<T: Decodable & CoreDataMappable, R: NSManagedObject>(endpoint: Endpoint, object: T.Type, cdObjectType: R.Type, storedObjectList: inout [R]) {
API.client.call(endpoint, expecting: [T].self) { (result, objects) in
switch result {
case .failure:
print("Unable to retrieve objects")
case .success:
guard let objectResponse = objects else { return }
DispatchQueue.main.async {
for object in objectResponse {
object.mapToCoreData()
}
self.appDelegate.saveContext()
self.updateLastSavedDate(object: T.self)
self.fetchObjectsFromCD(object: cdObjectType.self, objectList: &storedObjectList)
}
}
}
}
Without pasting too much unnecessary code, my issue is that within this API call (which comes from a library helper method we use to handle API responses) I need to set storedObjectList which is a variable defined within the scope of the current class.
However, when I do this I receive an error stating:
Escaping closure captures 'inout' parameter 'storedObjectList'
I'm trying to find a way around this so that I can still pass in storedObjectList here. I hit this method for 3 different objects, hence why I am trying to make it generic to avoid code repetition. This is the last part that I cannot get to work. I would like to avoid having some kind of a switch statement based on the object type passed in as R because this will limit the reusability of this method in the future.

Remove enum from array of enums regardless of its argument

I have an enum BulletinOptions:
enum BulletinOption {
case notificationPermissions
case enableNotifications(eventId: String)
case join(hostName: String, eventId: String)
case share(type: SocialBulletinItem.BulletinType.Social, event: EventJSONModel, view: UIView)
case completedShare(type: SocialBulletinPageItem.SocialButtonType)
}
I have an array of these enums like this:
let array = [
.join(hostName: hostName, eventId: event.id),
.notificationPermissions,
.enableNotifications(eventId: event.id),
.share(type: .queue(position: 0, hostName: ""), event: event, view: view)
]
I want to create a function that can remove a specific enum from this array. I have this code:
func remove(
item: BulletinOption,
from options: [BulletinOption]) -> [BulletinOption] {
var options = options
if let index = options.firstIndex(where: {
if case item = $0 {
return true
}
return false
}) {
options.remove(at: index)
}
return options
}
What I want to do is this:
let options = remove(item: .enableNotifications, from: options)
However, this gives me two errors. The remove function says:
Expression pattern of type 'BulletinOption' cannot match values of type 'BulletinOption'
for the line:
if case item = $0
The second error is when calling that function:
Member 'enableNotifications' expects argument of type '(eventId: String)'
I just want to delete that enum regardless of its argument. How can I do that?
This is currently impossible.
What you are trying to do is essentially passing an enumeration case pattern as an argument to a method, so that that method can match each value in the array against that pattern. However, the swift guide says that:
An enumeration case pattern matches a case of an existing enumeration type. Enumeration case patterns appear in switch statement case labels and in the case conditions of if, while, guard, and for-in statements.
This means that enumeration case patterns are not allowed as arguments to functions. :(
So the best you can do is this:
array.filter {
if case .enableNotifications = $0 { return false } else { return true }
}

Sub enumeration rawValue

Consider the following code, where I declared an enum with sub enums inside of it.
enum LocalizeKey {
case message(Messages)
case buttons(Buttons)
enum Buttons: String {
case remove = "Remove"
case add = "Add"
}
enum Messages: String {
case success = "Success"
case failure = "Failure"
}
}
In a normal enum with no sub enums we can easily access .rawValue property and get the raw value of whatever case we picked.
For this case, i created a function like this just to check out what am i getting .
func keyString(for type: LocalizeKey) {
print(type)
}
keyString(for: .message(.failure)) // usage
Problem : there are no other properties than .self to access for this LocalizeKey enum .
What I am trying to achieve: perhaps you can relate by the naming, i am trying to wrap my localized keys, so i can access them easily based on the key type etc, and the rawValue that is refering to the actual key will go into the getLocalizedValue function .
Playground Output : using the function above the playground output was
message(__lldb_expr_21.LocalizeKey.Messages.failure)
Edit: without having to create a variable that switches self on every case, imagine if we had +400 key that would be a huge mess probably.
You need to switch on the type parameter and do pattern matching:
switch type {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
You can also make this an extension of LocalizeKey:
extension LocalizeKey {
var keyString: String {
switch self {
case .message(let messages): return messages.rawValue
case .buttons(let buttons): return buttons.rawValue
}
}
}
You are going to have to switch somewhere. If there are only a handful of "sub-enums", it is probably the easiest to just write a switch manually:
func keyString(for type: LocalizeKey) {
switch type {
case .message(let message):
print(message.rawValue)
case .buttons(let button):
print(button.rawValue)
}
}
If you don't want to write this manually, you either have to change your data structure so it is not needed, or use a code generation tool that generates the boilerplate for you.
Although The mentioned answers do provide the solution, I'd mention the issue of the approach itself:
At this point, each new case (key) has to be added in your switch statement with an associated value, which seems to be undesired boilerplate coding; I assume that you could imagine how it will look like when having many cases in the enums.
Therefore, I'd recommend to follow an approach to be more dynamic instead of adding the value of each case manually in a switch statement. Example:
protocol Localizable {
var value: String { get }
}
extension RawRepresentable where Self: Localizable, Self.RawValue == String {
var value: String { return rawValue }
}
extension CustomStringConvertible where Self: RawRepresentable, Self.RawValue == String {
var description: String { return rawValue }
}
struct LocalizeKey {
enum Buttons: String, Localizable, CustomStringConvertible {
case remove = "Remove"
case add = "Add"
}
enum Messages: String, Localizable, CustomStringConvertible {
case success = "Success"
case failure = "Failure"
}
}
We are applying the same logic for your code, with some improvements to make it easier to maintain.
Based on that, you still able to implement your function as:
func keyString(for type: Localizable) {
print(type)
}
Usage:
keyString(for: LocalizeKey.Buttons.add) // Add
keyString(for: LocalizeKey.Messages.success) // Success
IMO, I find calling it this way seems to be more readable, straightforward rather than the proposed approach (keyString(for: .message(.failure))).

Swift: Less code with nested enum cases conforming to equal protocols

I try to write less code in following scenario:
I have this Queryable protocol and a Parameter enum:
protocol Queryable {
var urlQuery: URLQueryItem { get }
}
enum PaginationParameter: Queryable {
case page(Int)
case pageSize(Int)
var queryItem: URLQueryItem {
switch self {
case .page(let page):
return URLQueryItem(name: "page", value: page.description)
case .pageSize(let pageSize):
return URLQueryItem(name: "page_size", value: pageSize.description)
}
}
}
And an enum that provides some default cases and some specific cases defined by a generic type:
enum Parameter<P: Queryable> {
case pagination(PaginationParameter)
case specific(P)
}
Example Usage
enum BookParameters: Queryable {
case search(String)
case id(Int)
var urlQuery: URLQueryItem {
switch self {
case .search(let string):
return URLQueryItem(name: "search", value: string)
case .id(let id):
return URLQueryItem(name: "id", value: id.description)
}
}
}
let parameters: [Parameter<BookParameters>] = [
.pagination(.pageSize(10)),
.specific(.id(123))
]
Now I need to get the url query item through both .pagination and .specific cases.
let queryItems = parameters.map({
switch $0 {
case .pagination(let param):
return param.queryItem
case .specific(let param):
return param.queryItem
}
})
It would be nice to have a way to handle the nested cases combined since they conform to the same protocol. That doesn't work since I have to go to the nested cases through the parent cases:
A small improvement would be to bury the switch statement in an extension for the Parameters enum and let it conform to the Queryable protocol as well:
extension Parameters: Queryable {
let queryItem: URLQueryItem {
switch self {
case .pagination(let param):
return param.queryItem
case .specific(let param):
return param.queryItem
}
}
}
That results in a one liner but I have only shifted my problem to a different place.
let queryItems = parameters.map({ $0.queryItem })
Since you are using nested enums with associated values, I don't really see a way to avoid having this extra switch on the top level Parameter enum. As far as I am concerned, Swift doesn't provide us with a tool to work with cases in such a way where we could cast all cases with the "same" associated value types to a single case. What you could do is to rethink the existence of Parameter type, since it doesn't seem to be really useful due to the fact you still need to refer to it as Parameter<BookParameters> or Parameter<SomeOtherTypeThatConformsToQueryable>.
Personally I would skip the top level enum, and refer to the parameters property type as [Queryable] directly.
var parameters: [Queryable] = [
PaginationParameter.pageSize(10),
BookParameters.id(123)
]
Makes things much more simpler and easier to reason about. Also there is now a way to add other cases of other types, where it would not be possible with your initial solution.
enum SomeOtherTypeThatConformsToQueryable: Queryable {
case aVeryNiceCase(Int)
}
parameters.append(SomeOtherTypeThatConformsToQueryable.aVeryNiceCase(0))
// Appending this to array of type `[Parameter<BookParameters>]`, would not be
// possible without explicitly adding new case to the `Parameter` enumeration
Also if you find yourself calling the map { $0.queryItem } often, you could provide an extension to the Array where Element is type of Queryable
extension Array where Element == Queryable {
var queryItems: [URLQueryItem] { return map { $0.queryItem } }
}
// And now you can simply call
let queryItems = parameters.queryItems
Without conforming Parameters to Queryable, you can just introduce a variable in Parameters to get the queryItem because both cases accept a type that already conform to Queryable,
enum Parameter<P: Queryable> {
case pagination(PaginationParameter)
case specific(P)
var urlQuery: URLQueryItem {
switch self {
case .pagination(let param):
return param.urlQuery
case .specific(let param):
return param.urlQuery
}
}
}

Swift closure omission example?

When I read the Swift documentation provided by Apple, I found some concept not clear for me...
When a closure’s type is already known, such as the callback for a delegate, you can omit the type of its parameters, its return type, or both. Single statement closures implicitly return the value of their only statement.
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
What does it means the callback for a delegate? Could you give me an example? If both are omitted, should we need the in keyword?
There is nothing simpler. In this case the meaning of delegate is that the closure is used as a variable. Consider the following example:
class Downloader {
var onDownloaded: ((Data) -> Void)?
func startDownloading() {
...
}
}
Used as:
let downloader = Downloader()
downloader.onDownloaded = { data in
print("Downloaded: \(data.count) B")
}
downloader.startDownloading()
As you can see, I did not specify neither the type or the return value in the closure, because the left side of the expression (.onDownloaded =) provides them.
The same applies for method parameters:
func startDownloading(onDownloaded: ((Data) -> Void)?) {
...
}
However, we still need in in the closure. The keyword separates the parameter names from the closure body. Of course, we could make the parameters anonymous:
downloader.onDownloaded = {
print("Downloaded: \($0.count) B")
}
It states that the parameter type can be inferred from the delegate. A delegate is a protocol, which is where you define the types of the method parameters. This means that when implementing the delegate method, the compiler already knows about the method types through the declared protocol.
An example:
let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in
return one < two
}
The first simplification is related to the parameters. The type inference system can calculate the type of the parameters in the closure:
let sortedAnimals = animals.sort { (one, two) -> Bool in return one < two }
The return type can also be inferred:
let sortedAnimals = animals.sort { (one, two) in return one < two }
The $i notation can substitute the parameter names:
let sortedAnimals = animals.sort { return $0 < $1 }
In single statement closures, the return keyword can be omitted:
let sortedAnimals = animals.sort { $0 < $1 }
For strings, there's a comparison function which makes string comparison even shorter:
let sortedAnimals = animals.sort(<)
Each step outputs the same result and it is for you to decide what is concise, but readable at the same time.