Hallo ramen en deuren, Leo hier. Today we will talk about the difference between @objc and @objcMembers in Swift.
Today we have a lot of codebases written only in Swift, and if you are working in one of those consider yourself lucky. However, there is still a lot o legacy code running around the Apps. And that legacy code can sometimes be written in objective-c and that’s when the plot intensifies.
Sometimes in older code bases, you can have a lot of objective-c code mixed with new Swift code. The good thing is that Swift and objective-c have interoperability the bad thing is that has some limitations, for example, a class written in Swift can not be subclassed by an objective-c one, and the fact that objective-c doesn’t have structs this way you can only use classes when dealing with objective-c code.
I’m not advocating for one language over another, because I’m more than aware that they are all tools, and as a tool should be used for their specific purpose. If you are happy with your App written in objective-c or even C++ … Great! Go for it. I personally prefer Swift but I know amazing developers that prefer objective-c and that’s ok.
As we are talking about Swift features, did you know that you can run more than one Switch case? You can read about that here. Another interesting Swift that is worth checking is that you can use StaticStrings to disable any form of String concatenation or updates, that is really good to show your intentions in the code.
Today we study how you can use Swift code in your objective-c code, we will discover how to add just some subset of Swift functions to objc and more!
Let’s code! But first…
Painting of the Day
I chose the 1876 painting called The Promenade by legendary master Pierre-Auguste Renoir.
He was a French artist and the greatest painter of the Impressionist technique.
As a youthful boy, he worked in a porcelain plant. His drawing skills were prematurely acknowledged, and he was soon employed to create compositions on fine china. He also painted ornaments on fans before starting art school. He moved to Paris in 1862 to explore art and new skills, where he met Frederic Bazille, Claude Monet, and Alfred Sisley, all great impressionist painters. By 1864, he was exhibiting works at the Paris Salon, but his works went largely unnoticed for the next ten years, mostly in part to the disorder caused by the Franco-Prussian War.
I chose this painting because it is like Swift and Objective-C giving hands. Like the languages although they are giving hands one of them is not so pleased to be there.
The Problem – @objc and @objcMembers in Swift
You want to use Swift objects in objective-c code.
First, we need to set up the code example. Let’s suppose that you want to create a class that can interoperate with objective-c. That class will have three members, two properties, and one function. We want to expose the function and the class itself to the objc code.
How can we do that?
How to Use Swift code in Objective-C with @objc?
We need to make some changes in the UserManager to be available to objc.
Something like this:
class UserManager { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" func add(user: String) { users.append(user) print(users) } }
Now if we try to just instantiate it in objc, like the code below we will have an error:
UserManager *userManager = [UserManager init];
But why? Because we don’t expose the Swift code to the objc one. Let’s do that using the @objc keyword at the class level.
@objc class UserManager { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" func add(user: String) { users.append(user) print(users) } }
Now your class has this error:
Another change to the class is needed to use it in objc, let’s make UserManager inherit from NSObject:
@objc class UserManager: NSObject { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" func add(user: String) { users.append(user) print(users) } }
Now you can use it in your objc code, like the example below:
UserManager *userManager = [[UserManager alloc] init];
The function add(user:) is not accessible to the objc code, and our class is kinda useless without it. We will start to discuss the real use, how about fixing that?
How to expose Swift functions to Objective-C?
Properties and functions are members of a type, keep that in mind. To expose any Swift function to objc you just need to add the annotation @objc on it. Try it with our class:
@objc class UserManager: NSObject { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" @objc func add(user: String) { users.append(user) print(users) } }
And invoking in objc like this:
UserManager *userManager = [[UserManager alloc] init]; [userManager addWithUser:@"Galadriel"];
You do realize now the number of @objc annotations in this class is two. And as more functions or properties that we want to expose to objc more @objc annotations we will have to add in the Swift class.
Also if you annotate the property users or URL with @objc it will not be exposed to the objc code. To do that you would need to remove the private access modifier. But what if you don’t want to remove the access modifier and still want to have that member accessible in objc? Let’s start to explore the wonders of the @objcMembers annotation.
@objc Annotation Disadvantages
Everything is great and working, and now it is time to add more features to your UserManager class. Let’s add remove and log features:
@objc class UserManager: NSObject { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" @objc func add(user: String) { users.append(user) } @objc func remove(user: String) { users.removeAll { $0 == user } } @objc func logUsers() { print(users) } }
And you can use it like this:
UserManager *userManager = [[UserManager alloc] init]; [userManager addWithUser:@"Galadriel"]; [userManager removeWithUser:@"mike"] [userManager logUsers];
Now the objc annotation count is 4, and that is starting to become convoluted. We don’t want to every time we add a new function that we need to expose to objc we need to add the @objc annotation too.
Luckily we have the @objcMembers to help us do the heavy lifting.
How to use @objcMembers annotation?
We can erase all the @objc annotations and just annotate at the class level with @objcMembers so that all the members of that class will become available to Objective-C.
Check the code below:
@objcMembers // just need to add this class UserManager: NSObject { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" func add(user: String) { users.append(user) } func remove(user: String) { users.removeAll { $0 == user } } func logUsers() { print(users) } }
Now you have a much cleaner code! But this comes with an undesired side effect.
When you use @objcMembers, ALL your class members become available to Objective-C, and this probably is not what you want. But how can I create just a subset of functions exposed to @objc?
One thing to keep in mind while using @objcMembers is that although all members of the class will be exposed to Objective-C only the ones that are compatible with Objective-C will be available in Objective-C. This way it doesn’t matter if you annotate your class with @objcMembers if all the members depend on non-compatible Objective-C objects, they won’t be available on Objective-C anyway.
Using @objc to a Subset of Functions in the Class
To expose just part of your class functions to Objective-C you just need to apply it to an extension. This way you can expose some functions to objc and others not, without having to tag them individually.
The steps are:
- Create an extension of your class.
- Put all the functions that you want to expose to objc in the new extension created.
- Annotate the new extension with @objc
See below:
class UserManager: NSObject { private var users = ["leo","ninha","mike","pepijn","alan"] private let url = "myurl.com" func logUsers() { print(users) } } @objc // add this extension UserManager { func add(user: String) { users.append(user) } func remove(user: String) { users.removeAll { $0 == user } } }
See how easy was to add that?
Custom Naming of Swift functions to use with Objective-C
The last tip today is when you don’t want to use the auto-generated function name in your Objective-C code. For example: you have a function in swift that reload users, but in Objective-C you want to know that function is from Swift code so you want to call it reloadUserBridge.
Check the example below:
@objc(reloadUserBridge:) func reload(user: String){ // the add users function }
The “:” is important in the naming to say to Objective-C that it is has a parameter. If that function doesn’t receive a parameter you could just omit the “:” in the @objc declaration. Now, calling that in Objective-C would be:
[userManager reloadUserBridge:@"Ninha"]
And that’s it for today!
Wrap up – @objc and @objcMembers annotations
The @objc modifier enables your Swift code to be exposed to Objective-C code. You can use it on class, properties, and functions levels. Annotating a class with @objc creates the obligation of adding the same annotation to whatever member you want to expose to Objective-C. Unless you annotate an extension, this way all the extension members will be available to Objective-C. And @objc annotation also enable you to rename you Swift object/properties/functions to expose to Objective-C.
On the other hand, the @objcMembers is very useful when you want all the class members to be available to Objective-C at once. You don’t need to individually add annotations to each member which makes your life easier.
Summary – @objc vs @objcMembers in Swift
Today we checked the main differences between @objc and @objcMembers annotations in Swift. We studied how you can expose your code to be used in Objective-C code and how you can create a subset of functions that are exposed to Objective-C within a class.
Fellow developers, that’s all. I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or just say hello on Twitter. You can reach me on LinkedIn, or send me an e-mail through the contact page.
You can also sponsor this blog so I can get my blog free of random advertisers.
Thanks for the reading and… That’s all folks.
Image Credit: Wikiart