For the last year and a half, I’ve been working full time as a Swift developer. I love Swift, and I’ve also been really enjoying diving into Functional Reactive Programming using RxSwift. Nevertheless, I find myself longing for something that I don’t have anymore: a robust introspection API.
When I was writing C#, I could write a simple class like this:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Now let’s say I want to send or receive a Person
from a RESTful API.
Perhaps the transmission will be JSON, and it will look something like
this:
{
"fn": "Casey",
"ln": "Liss"
}
If we wanted to map a C# Person
from a JSON dictionary, it’s reasonably
straightforward, except that there’s an important discrepancy: we need
to tie fn
to FirstName
and ln
to LastName
.
There’s a ton of ways one can create a link between those different names.
My favorite, in C#, was to use introspection. In C#, this is called
Reflection
, and that’s how I still refer to it to this day.
Reflection is neat because it allows my code to learn about itself. That means
I can add some metadata to my code—I can annotate it—in order
to provide some supplementary information. How does that work in practice? Let’s
augment the Person
class, adding and leveraging a new Attribute
.
public class JsonKeyAttribute: Attribute
{
public string Key { get; set; }
public JsonKeyAttribute(string key)
{
this.Key = key;
}
}
public class Person
{
[JsonKey("fn")]
public string FirstName { get; set; }
[JsonKey("ln")]
public string LastName { get; set; }
}
Now we are annotating our properties with new metadata: we’re storing the keys
needed to translate to/from JSON right there inline. How do we leverage it? Let’s
write a static
factory method:
public class Person
{
[JsonKey("fn")]
public string FirstName { get; set; }
[JsonKey("ln")]
public string LastName { get; set; }
public static Person FromJson(string jsonString)
{
var retVal = new Person();
var properties = typeof(Person).GetProperties();
foreach (var property in properties)
{
var attribs = property.GetCustomAttributes(typeof(JsonKeyAttribute), true);
var attrib = attribs.FirstOrDefault() as JsonKeyAttribute;
if (attrib != null)
{
var key = attrib.Key;
var value = // Get value from JSON object
// using the key we just discovered.
// Set the property's value
property.SetValue(retVal, value);
}
}
return retVal;
}
}
Admittedly I’ve fluffed over the conversion from JSON string to something
meaningful, as well as glossing over extracting "Casey"
and "Liss"
from the
JSON. However, the rest of the code is the point. We can leverage Reflection to
look at the Person
class and see what the key is for each of its properties.
Having the ability to annotate our code with information about itself is super
powerful. Using an Attribute
, we were able to leave information about how to
convert between different representations of the same data right in the class
that needs to know about it. Some purists say that’s a poor separation of
concerns; to me, that’s improving local reasoning.
Furthermore, using annotations can accomplish interesting things in arguably far cleaner ways.
As an example, if you want to specify a “pretty printer” for the purposes of
debugging a Swift class, you can use CustomDebugStringConvertible
.
However, to do so, you must implement the protocol
. For example:
struct Person {
var firstName: String
var lastName: String
}
extension Person: CustomDebugStringConvertible {
var debugDescription: String {
return "\(firstName) \(lastName)"
}
}
The approximate equivalent in C# is arguably cleaner, because it doesn’t
require implementing a new interface
. Instead, you simply leverage the
DebuggerDisplayAttribute
:
[DebuggerDisplay("{FirstName,nq} {LastName,nq}")]
public class Person
{
[JsonKey("fn")]
public string FirstName { get; set; }
[JsonKey("ln")]
public string LastName { get; set; }
}
I can think of a ton of other places where reflection is useful as well. I’m
particularly interested in how cool it could be to really open up the
already crazy-powerful Swift enum
s by adding the ability to annotate them,
or reflect over them. Oh, the crazy things I could do… 🤔
Reflection isn’t for everyone. In fact, I got the following email from an ATP listener:
If you need reflection to reason about and execute code runtime, your API is poorly designed. Can you please explain why you feel the the need for a reflection API?
That’s a pretty severe take-down.
I understand the sentiment, and this particular listener isn’t necessarily wrong. But what I love about reflection is that it opens up the possibility for a whole new way of solving problems. A way that I’ve found to be quite convenient from time to time.
In fact, all of you Objective-C developers out there may enjoy doing things like this on occasion:
id person = [[NSClassFromString("Person") alloc] init];
To me, that’s reflection.
A while back there was a big kerfuffle amongst some Objective-C developers who were, erm, objecting to the lack of dynamic features in Swift. To me, the canonical, level-headed post about this was this wonderful post by my pal Brent Simmons. It’s an extremely short but accurate summary of what all of the Objective-C folks seemed to think Swift was lacking.
To me, I can summarize his post in one word: reflection.