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.