Afshin
Afshin

Reputation: 9173

Swifty method for providing 2 closures for a function

I have a function with following syntax:

func myfunc<V1,V2>(to: (V1)->V2, from: (V2)->V1)

and as you see, I'm providing 2 closures to this function. These 2 closures are used for converting V1 to V2 and vice versa. Remember that I cannot provide these conversions as extension to V1 or V2. I like to improve function usability and I want to know what is more common and better approach for providing these 2 as a Swift user.

I have though of 3 ways, each with its own pros and cons.

1st approach: Use current syntax.

func myfunc<V1,V2>(to: (V1)->V2, from: (V2)->V1)

Pros: User provides 2 closure, so he can organize code better. In addition, he can provide them in-place.

Cons: Function call will become long and can only use trailing closure for second closure.

2nd approach: Use a single closure for both and distinguish them through a paramether.

enum ConvertClosure {
   case .to(Any)
   case .from(Any)
}

func myfunc<V1,V2>(operation: (ConvertClosure)->Any)

Pros: Function call becomes simpler and we can use trailing closure too.

Cons: Responsibility of 2 closures are now in a single one, so it becomes more complex. Cannot add generic type checking and need to use Any for enum case argument and return type of function.

3rd approach: Use protocol rather than closure.

protocol ConvertOperation {
    associatedtype InVal
    associatedtype OutVal
    func to(_ val: InVal) -> OutVal
    func from(_ val: OutVal) -> InVal
}

func myfunc<V1,V2>(operation: ConvertOperation) 
        where ConvertOperation.InVal == V1, ConvertOperation.OutVal == V2

Pros: Function call becomes simpler. We have type checking from generics.

Cons: We need to conform to protocol which cannot be done in place. Approach is not using closure, so it may not be very Swifty.

Which method is more suitable for a Swift user? Can you suggest any better method?

Upvotes: 0

Views: 44

Answers (1)

Sulthan
Sulthan

Reputation: 130201

This might be a bit opinion based but the general rule is:

  1. Use the most specific type as possible. You want types to catch bugs for you during compilation.

  2. Use protocols when you want to reuse the functionality. A protocol will require you to use a struct/class that will implement it, therefore making it harder to use. Therefore it's better when you already have an object that can conform to that protocol or when the functionality is reused, therefore you can create an object and use it multiple times.

To comment more on your cases:

  1. You should not be using trailing closures if there is more than one closure parameter because that affects readability. However, this is not a bad solution and it's pretty common.

  2. This is generally a bad solution because it uses Any type which breaks rule 1 above. Always minimize usage of Any. Most applications should not use it at all.

  3. This is not a bad solution, however see rule 2. I will be optimal only in specific use cases.

Also consider NSObject is basically the same as Any. In general you should avoid its usage in Swift code because you are basically resigning on type checking. Also note that you should not aim for code to be as short as possible. Always aim for readability.

We cannot probably give a more specific advise without knowing the exact use case.

Upvotes: 2

Related Questions