Dynamic

@dynamicMemberLookup
public struct Dynamic

A wrapper enabling ergonomic dynamic Objective-C lookup.

This type can be used as syntactic sugar to access ObjC classes and protocols by name, and also to call undeclared methods on them in a relatively type-safe manner.

This type is inspired by mhdhejazi/Dynamic.

Obtaining an Instance

Use Dynamic.TypeName to get an instance of Dynamic. Accessing classes/protocols with dots in their name can be done in the same way: e.g. Dynamic.Foo.Bar.

You can also get an instance of Dynamic by passing a string, e.g. Dynamic("Foo.Bar"), or even by passing an existing class: Dynamic(NSString.self).

Getting the Underlying Type

If the type name corresponds to an Objective-C class, it is possible to retrieve an AnyClass from the type using the class property. For example, Dynamic.TypeName.class will return the class named “TypeName”. To cast to a more specific superclass type, use as(type:), for example Dynamic.PrivateViewType.as(type: UIView.self).

If the type name corresponds to an Objective-C protocol, the protocol property returns the type as a Protocol.

Calling Undeclared Instance Methods

The first step to calling an undeclared method on the type (one that is not known to the Swift compiler) is to declare an “interface” with the method. An interface is simply an Objective-C protocol.

Say you wanted to call -[MyClass someInstanceMethod], where someInstanceMethod is not exposed to Swift. First, declare the interface:

@objc protocol MyInterfaceA {
    func someInstanceMethod()
}

Then, use Dynamic.convert(_:to:) to “cast” the object to that interface:

let obj = MyClass()
let converted = Dynamic.convert(obj, to: MyInterfaceA.self)

Swift sees converted as a type conforming to MyInterface, and so it is now possible to directly call the method on it:

converted.someInstanceMethod()

If MyClass conforms to NSObject, a way to do this with even more syntactic sugar is to use NSObject.as(interface:):

MyClass().as(interface: MyInterfaceA.self).someInstanceMethod()

Calling Undeclared Class Methods

Calling class methods is similar to calling instance methods. Say you wanted to call +[MyClass someClassMethod] where neither the class nor the method is publicly exposed to Swift. First, declare an interface:

@objc protocol MyInterfaceB {
    func someClassMethod()
}

Note that the method is declared as an instance method in the protocol, even though it is actually a class method.

Next, obtain an instance of Dynamic corresponding to the type, and use Dynamic.as(interface:) to cast it to the interface.

let converted = Dynamic.MyClass.as(interface: MyInterfaceB.self)

Following this, it is possible to call methods on the type which have been declared on the interface:

converted.someClassMethod()

As with instance methods, class methods too have syntactic sugar available via an extension on NSObject. If MyClass was accessible in Swift and conformed to NSObject, the private method could be called as follows:

MyClass.as(interface: MyInterfaceB.self).someClassMethod()

Calling Initializers

In order to call a private initializer using Dynamic, declare an interface with the initializer method (escaping `init` with backticks if required). Then call alloc(interface:) on the class or an instance of Dynamic corresponding to it, passing in the interface. Finally, call the initializer method on the returned object.

For example, initializing MyClass using -[MyClass initWithString:] would look like this:

@objc protocol MyInterfaceC {
    func initWithString(_ string: String)
}

let obj = Dynamic.MyClass
    .alloc(interface: MyInterfaceC.self)
    .initWithString("hello")

Since MyClass conforms to NSObject, it is again possible to utilize additional syntactic sugar, in this case replacing Dynamic.MyClass with simply MyClass.

  • The class with the given name.

    Declaration

    Swift

    public var `class`: AnyClass { get }
  • The protocol with the given name.

    Declaration

    Swift

    public var `protocol`: Protocol { get }
  • Initialize an instance of Dynamic with the given class.

    Declaration

    Swift

    public init(_ cls: AnyClass)

    Parameters

    cls

    The class with which the instance should be initialized.

  • Initialize an instance of Dynamic with the given type name.

    Declaration

    Swift

    public init(_ name: String)

    Parameters

    name

    The name of the type with which the instance should be initialized.

  • Initialize an instance of Dynamic with the given type name.

    Declaration

    Swift

    public static subscript(dynamicMember typeName: String) -> `Self` { get }

    Parameters

    typeName

    The name of the type with which the instance should be initialized.

  • Appends the provided component onto the name of the type, delimited by a period.

    Dynamic("Foo").appending(component: "Bar") == Dynamic("Foo.Bar")

    Declaration

    Swift

    public func appending(component: String) -> Dynamic

    Parameters

    component

    The component to append.

    Return Value

    A modified copy of the callee with component appended to the name after a period.

  • Appends the provided component onto the name of the type, delimited by a period.

    Dynamic.Foo.Bar == Dynamic("Foo.Bar")

    Declaration

    Swift

    public subscript(dynamicMember component: String) -> `Self` { get }

    Parameters

    component

    The component to append.

    Return Value

    A modified copy of the callee with component appended to the name after a period.

  • Casts the object to the given interface type.

    For more information, see the documentation for the Dynamic struct.

    Declaration

    Swift

    public static func convert<I>(_ object: AnyObject, to interface: I.Type) -> I

    Parameters

    object

    The object to cast.

    interface

    The interface to which the object should be converted.

    Return Value

    The object as if it conformed to the given interface.

  • Returns the class referred to by the callee, cast to the provided type.

    Declaration

    Swift

    public func `as`<T>(type: T.Type) -> T.Type where T : AnyObject

    Parameters

    type

    The type to which the class should be cast.

    Return Value

    The cast class.

  • Returns the class referred to by the callee as if it conformed to the given interface.

    Methods on the interface should be declared as instance methods.

    For more information, see the documentation for the Dynamic struct.

    Declaration

    Swift

    public func `as`<I>(interface: I.Type, protocol: Protocol? = nil) -> I

    Parameters

    interface

    The interface to which the class should be cast.

    protocol

    If the interface is a private protocol or is otherwise invisible to Objective-C via NSClassFromString, you may need to pass the protocol a second time as this parameter. For example, Dynamic.MyClass.as(interface: MyInterface.self, protocol: MyInterface.self).

    Return Value

    The class, “cast” to the interface.

  • Allocates an object corresponding to the callee’s class and casts it to the type of the passed interface.

    Declaration

    Swift

    public func alloc<I>(interface: I.Type) -> I

    Parameters

    interface

    The interface which the allocated type should be cast to. You usually want this to have an initializer method which should be chained to this call.

    Return Value

    The allocated instance of the class, as the interface type.