This workshop will be retired on May 31, 2020.
Limiting @objc Inference10:23 with Pasan Premaratne
In Swift you can annotate a property with the @objc keyword to expose it to the Objective-C runtime. Swift also infers @objc in certain places to improve interoperability with Objective-C. In Swift 4, this inference is limited in an effort to reduce binary size and improve performance. In this video, we take a look at how this change affects us.
SEO 160 or limiting @objc inference isn't a big change per se, 0:00 but it's one that you should be aware of, because you will certainly run into it. 0:05 You might be familiar with the @objc attribute if you've been writing iOS apps 0:10 with Swift. 0:14 You use it when you want to expose something from Swift to the Objective-C 0:15 runtime. 0:19 Now the most common place for 0:20 this is any Swift method you want to refer to using a selector. 0:22 You also use the @objc attribute when you want to 0:26 give a method a different name in Objective-C than in Swift. 0:29 In Swift 3, there are lots of places where this attribute is inferred. 0:33 And either a class, property, 0:37 or method exposed to the Objective-C runtime automatically. 0:39 One of the main reasons for 0:43 this change is that, there's no consistency to when this happens. 0:45 When this inference automatically happens, or even if there is, it's not obvious. 0:48 So in Swift 4, this inference is scaled back. 0:54 There are definite benefits to this. 0:57 When you use @objc, the Swift compiler creates a thunk method. 0:59 An intermediate function so to speak, that maps from Objective-C to Swift, and 1:04 records this method along with some metadata. 1:08 This isn't cost free. 1:11 According to the Swift team, this increases the size of the average binary 1:13 by 6% to 8%, and much of this code sits unused. 1:17 Having all this metadata, also increases load time. 1:22 By limiting this inference, both the binary size and 1:25 load times thereby are reduced. 1:28 So what does this look like in practice? 1:31 For the most part, you now have to declare @objc, 1:33 specifically when you want to expose Swift code to the Objective-C runtime. 1:36 And the compiler will let you know when you need to, so 1:41 NSObject subclasses no longer infer @objc. 1:45 This is probably the change that affects binary size the most since there 1:49 are plenty of NSObject subclasses now that won't be needing these functs, or 1:53 intermediate functions all over the place. 1:57 You might be thinking, well, if this is the case, but 2:01 I still want an entire NSObject subclass to be visible to Objective-C, do I need to 2:04 annotate every single property and method with the @objc keyword? 2:09 If that's the case, that's pretty annoying. 2:14 And thankfully, we don't have to do that. 2:15 So there's a new attribute that we can use and it's called @objcMembers. 2:18 That reenables @objc inference for the entire class, its extensions, 2:22 subclasses, and all of their extensions. 2:27 If however, you wanted to group certain methods as visible to Objective-C and 2:31 others as Swift only, you have keywords for that, too. 2:35 Okay, so let's write some code here. 2:40 A regular class can designate an extension as @objc to expose methods to Objective-C. 2:42 So if I have a class, let's call this SwiftClass, 2:49 I can write @objc extension to the SwiftClass. 2:54 And now, anything in here is exposed to the Objective-C runtime. 3:01 So foo now is exposed to the Objective-C runtime. 3:08 There are caveats, though. 3:12 If any methods within this extension use Swift only features, like tuples or 3:14 structs, then this method is not expressible in Objective-C, 3:19 and you'll get an error. 3:22 So if I were to write a function bar, and 3:24 this returns a tuple of two integers, I'm going to get an error because it says, 3:26 tuples cannot be represented in Objective-C. 3:32 So here, we've declared anything in this extension to be Objective-C since this can 3:35 be represented, we have an error. 3:39 All right, so let's get rid of that. 3:43 One way instead of getting rid of it altogether is 3:46 we can declare this @nonobjc. 3:51 And now, even though it's inside this Objective-C extension, 3:54 this method won't be exposed to the Objective-C runtime. 3:58 So keep this distinction in mind, and there's an error here because I need to 4:02 actually return something from this function. 4:06 So let's just do this. 4:08 Okay, and there we go, that compiles. 4:10 Now if you have a class that reenables @objc, 4:14 because you used Objective-C members or objc members in the declaration, 4:18 another way we can do it is by declaring a class as having @objc members. 4:23 So if I have a class here called MyClass, even though I declare that this 4:29 inherits from NSObject, wibble, let's write a new function here. 4:34 This function is not exposed to the Objective-C runtime. 4:41 And that's because we're limiting the inference and Swift form. 4:45 If we want to do that, we can declare this class as having @objcMembers. 4:48 And now everything in the class will be exposed to the Objective-C runtime. 4:53 So this is the same as writing implicitly @objc in front of the function. 4:58 So if you have a class the reenables @objc, again, 5:05 because we use this @objcMembers in the declaration, we can still define certain 5:08 methods that are Swift only, by using this @nonobjc attribute that we did earlier. 5:13 So if I wanted to group all of my Swift only methods in an extension, 5:20 just like we did @objc extension here, to expose only these methods to objc. 5:25 We can do the reverse as well and we can say, 5:31 @nonobjc extension MyClass. 5:36 And now, anything declared in here is not exposed to the Objective-C runtime. 5:42 It's not @objc, even though we've declared the class as having Objective-C members. 5:49 Okay, so a couple things to keep in mind, lots of new keywords, but 5:55 this only matters if you want to expose certain things back and forth. 5:58 Having said all this, 6:02 there are still places where @objc is going to be inferred automatically. 6:04 If you override a method from a superclass that was defined with the @objc keyword, 6:09 the overridden method in the subclass has @objc inferred. 6:14 If you're implementing a protocol method or the method or the entire 6:19 protocol has been annotated with @objc, it is in inferred here as well. 6:24 By default, @IBAction, @IBOutlet, and 6:29 @IBInspectable attributes also indicate that @objc is being inferred, because 6:32 interaction with Interface Builder always occurs through the Objective-C runtime. 6:37 NSManaged object, @NSManaged object, which is used in core data code, 6:43 also means @objc, because again, core data works with the Objective-C runtime. 6:47 Finally, @GKInspectable, infers objc, with the Gameplay Kit Framework, 6:53 also occurs to the Objective-C runtime. 6:57 There's one thing to keep in mind here as well, 7:01 another attribute you might have run into before is the dynamic keyword. 7:03 By using the dynamic keyword, we're informing the Swift compiler 7:08 that it should use dynamic dispatch to access that member. 7:12 Since Swift doesn't implement its own dynamic dispatch model, 7:16 using the dynamic modifier meant using Objective-C's runtime. 7:21 This is no longer the case in Swift 4. 7:25 Dynamic no longer means automatically inferring at objc. 7:28 The reason for this is very straightforward, and 7:33 I'll just read it right off the proposal. 7:35 In the future, it is plausible that the Swift language and runtime 7:38 will evolve to support dynamic without relying on the Objective-C runtime. 7:42 And it is important that we leave the door open for that language evolution. 7:47 This means that now, when you use the dynamic modifier, 7:52 you won't be getting @objc for free and you need to explicitly list that keyword. 7:55 This also makes it very clear that the dynamic behavior that 8:01 you're using is tied to the Objective-C runtime. 8:04 So if you go back to class MyClass over here, if I were to define a function, 8:07 so I say dynamic func foo. 8:13 You should give the error now over here. 8:18 No, let me redo this. 8:22 There's a trick here and I'll explain why in a second. 8:23 So I'll say down here, I'll say class, NewClass, dynamic func foo. 8:26 And now when I do this, I will get an error. 8:36 It says, dynamic instance method foo must also be @objc. 8:38 So right now, we don't have an implementation of a dynamic runtime 8:43 in Swift. 8:46 So when we use dynamic, we also need to make this @objc 8:47 to clearly indicate that we're using the Objective-C runtime. 8:51 Now why did this work earlier? 8:55 Oops, @objc should come before, so order matters here. 8:58 Okay, so why did this work earlier? 9:03 Well, when we put it in here, so if I say, dynamic func foo. 9:05 The reason this works is because we're implicitly getting @objc here, 9:12 because the class is declared as having Objective-C members. 9:16 So everything in here is implicitly @objc. 9:20 So this works because dynamism, a dynamic runtime is tied to Objective-C. 9:23 But one day in future, hopefully, 9:29 we can just get rid of the Objective-C keyword and rely just on Swift. 9:31 I know what you're thinking, we had enough keywords, attributes and 9:36 modifiers already, and this makes it even more confusing. 9:40 Now this is true, 9:44 but this is laying the ground work to allow Swift to move away from Objective-C. 9:45 By choosing to explicitly get the Objective-C runtime involved, we know what 9:50 behavior to expect from Swift which makes semantics more consistent cross platform. 9:54 The Swift team also consider it doing nothing, and 10:00 leaving things as is, and eliminating Objective-C inference all together. 10:02 Both of these proposals involved more work or 10:07 just muddied the Swift Objective-C waters even further. 10:10 In a subsequent video, we'll convert a project over to Swift 4. 10:14 And you'll see that while there are a fair number of keywords, 10:17 the migration tool should handle most of it for you. 10:20
You need to sign up for Treehouse in order to download course files.Sign up