Search code examples
swiftdecoratorcomposition

Decorators in Swift


I'm new to Swift, and I'd like to know if the language has some equivalent to Python's decorator pattern.
For example:

import functools


def announce(func):
    """Print a function's arguments and return value as it's called."""
    @functools.wraps(func)
    def announced_func(*args, **kwargs):
        rv = func(*args, **kwargs)
        print('In: {0}, {1}'.format(args, kwargs))
        print('Out: {}'.format(rv))
        return rv
    return announced_func


@announce  # add = announce(add)
def add(a, b):
    return a + b

add(2, 5)
# In: (2, 5), {}
# Out: 7
# 7

Perhaps I just haven't found it yet, but Swift doesn't seem to have a way to forward arbitrary arguments to functions or to preserve a wrapped function's information (as functools.wraps does).

Is there an equivalent, or is the pattern not meant to be used in Swift?


Solution

  • You can use this:

    func decorate<T, U>(_ function: @escaping (T) -> U, decoration: @escaping (T, U) -> U) -> (T) -> U {
        return { args in
            decoration(args, function(args))
        }
    }
    
    let add: (Int, Int) -> Int = decorate(+) { args, rv in
        print("In: \(args)")
        print("Out: \(rv)")
        return rv
    }
    
    add(2, 5) // In: (2, 5)\nOut: 7
    

    Or announce as function instead of closure, allowing reuse:

    func announce<T, U>(input args: T, output rv: U) -> U {
        print("In: \(args)")
        print("Out: \(rv)")
        return rv
    }
    
    let add: (Int, Int) -> Int = decorate(+, decoration: announce)
    
    add(2, 5) // In: (2, 5)\nOut: 7
    
    let length = decorate({(str: String) in str.characters.count }, decoration: announce)
    length("Hello world!") // In: Hello world!\nOut: 12