스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift and Objective-C Interoperability
Discover new features that make it easier than ever to craft Objective-C APIs that work beautifully in Swift, as well as new Swift language features that provide even better interoperability. Apple engineers will also discuss enhancements to Apple's SDKs that improve the Swift experience.
리소스
-
다운로드
JORDAN ROSE: Good afternoon, everyone. My name is Jordan Rose. JORDAN ROSE: Wow. Thank you. I haven't even done anything yet.
My name is Jordan Rose. Joining me later will be my colleague, Doug Gregor. We are here to talk to you about the language changes we made in both Swift and Objective-C over the last year, especially in the areas of interoperability.
And this is important because all Objective-C APIs are available in Swift.
And in Xcode 7 we made it even easier to see how this mapping works. Go to any Objective-C header and choose the 'show related items' button in the top-left corner. This will bring down a menu of related items, one of which is 'generated interface.' And this will show you the Swift mapping for that header.
This is the exact same view that you got in Xcode 6 using the 'jump to definition' feature, but now you can get it easily from any header in your target. So we are going to be talking about a variety of improvements today that we have made to Swift for working with Objective-C, and that we have been making to Objective-C itself. We will start off with some of the basic features working between Swift and Objective-C, and at the details of the new error-handling feature and how that works across the language boundaries. After that, we are going to talk about the three major improvements we've made to Objective-C in the last year, nullability annotations, lightweight generics, and kindof types.
Let's start on the Swift side of things.
We just saw how easy it is to get the Objective-C APIs into Swift. But a lot of people come up with the question, when is my Swift exposed to Objective-C? And so, let's go over the rules for this.
If you have a class that's a subclass of NSObject, then by default, its methods and properties are exposed to Objective-C. In this case, we have a class MyController that's a subclass of UIViewController. That's indirectly a subclass of NSObject, so this refresh method will be exposed to Objective-C.
However, if you mark that method as private, that means it's only supposed to be visible to the current file. So in this case, Swift will not expose the method to Objective-C by default.
Additionally, if you are only using -- if you are using some kind of Swift-only feature, like this unusual return type here, then, again, there's no way to expose this method to Objective-C and Swift won't even try. Finally, if you are not a subclass of NSObject but simply conforming to an Objective-C protocol, then you have to take extra care to make sure that your methods actually satisfy the requirements of the protocol. And in Xcode 7, you will get a warning for this. In this case, 'web view did start load' is non-objc, and so it cannot satisfy the requirements of the 'UI web view delegate protocol.' So that covers the defaults.
What happens when you want to change those from that default behavior? So let's go back to that version of MyController here with the refresh method, and I want to expose it to Objective-C, but it really depends -- what I want to do next really depends on what I need to do with this refresh method.
So, if I'm going to make this, say, something that I want to use an interface builder or maybe it's a property that I want to use from Core data, we have dedicated attributes for these, and adding that attribute is all you need to do. So, if you use the IBAction attribute as shown here, that's all you need to do to make this refresh method a valid action in interface builder.
Similarly, if you are looking to use a property with foundations key value observing system, or Cocoa bindings, all you need to do is add the dynamic modifier. This tells Swift that the implementation of this property might be replaced at run time by the key value observing system.
But if you really just want to expose a particular method or property to Objective-C, you can always just use the plain-old at-objc modifier, or objc attribute -- sorry -- and this will expose a method or objective to Objective-C without any additional semantics. The objc attribute is also useful for one other thing, which is to figure out why something is not visible in Objective-C when you thought it would be. So if I tried to apply this to that other version of Refresh that has the unusual result type here, then I will get an error message saying this 'method cannot be marked at-objc because its result type cannot be represented in Objective-C.' All of these rules are covered in the 'Using Swift with Cocoa and Objective-C' guide, which is available online at developer.apple.com/Swift.
So now we've covered the defaults, and we've covered what to do when you want to override those defaults to make things more ObjC. But what if you have the opposite problem? Let's look at this class here: calculator controller. It has two methods named 'perform operation.' One of them takes one kind of closure and the other takes a different kind of closure.
That's fine. Swift is able to differentiate these methods purely based on their argument types.
But Objective-C doesn't work like this. In Objective-C, methods are only differentiated by name, not by type.
And so in this case, Swift knows this might be an issue and it will give you an error message.
Now, you have always been able to fix this using the selector form of the ObjC attribute. So if I write this code, then I've renamed the bottom method to 'perform binary operation, when I'm accessing it from Objective-C.
The top method is still named 'perform operation.' In Xcode 7, we've added another option for solving this problem, which is the non-ObjC attribute. As you might have expected, this will take something that's normally exposed to Objective-C and prevent it from being exposed as such. You can apply the non-ObjC attribute to any method, property, subscript, or initializer.
So that's a fair amount about Objective-C and methods, but how about something lower level.
Let's talk about C. So there are a few people in this room that are really happy that we have a slide called function pointers.
A function pointer is what C uses for callbacks.
That means they are kind of like closures, but they can't carry any additional state with them.
What do I mean by that? Let's say I'm in Swift and I'm trying to call this C function, funopen. It takes a lot of arguments. You don't have to know what they all are. One of them is a callback.
In this callback, I'm trying to use self to do something. Now, self is not an argument to this closure. So it has to be stored away somewhere for the closure to access later.
That's what we mean by a closure carrying state. So when you try to use this closure, with this API, you get an error message. Now, in Swift 1.2, C function pointers weren't very useful. You had this C type that is shown on the top, and it came into Swift as this opaque, C function pointer type. You could pass it around but you couldn't do much else with it. In Swift 2.0, we've just made these a special kind of closure marked with the convention-C attribute. You can create these in Swift, pass them around, and even call them. Which means there's a whole family of C APIs that you can now access directly in Swift that you couldn't before.
JORDAN ROSE: So those are just two of the little things we have done to enhance the bridge from Swift and to C and Objective-C.
But there's also some big-name features this year that have had some really big impacts on this language bridging.
The most important, of course, being error handling.
Now, if you haven't really heard about the error handling model yet, essentially we took a lot of lessons from Cocoa's NSError paradigm and the rules and the conventions that surrounded that, and used what we learned from that to make a new language feature, represented by the 'throws' keyword.
This was covered in a lot of depth in this morning's talk "What's New In Swift." Which was -- which you -- if you didn't make it to that, I strongly suggest you check it out later on, to find out really how all this error handling stuff works. But what I want to focus on now is specifics about this going between the two languages.
And one thing I want you to understand is that this isn't just a mapping from Objective-C into Swift. It's also how Swift APIs get exposed back to Objective-C.
So this mapping is bidirectional. Let's take a closer look at the return types here.
In Objective-C, the error conventions say that returning a nil value represents a failure case.
That's when this 'out error' parameter is going to be populated.
In Swift, however, that's entirely covered by the error handling model, and so the return type you will see in Swift is a non-optional object type.
Similarly in Objective-C, you can also have a Boolean return value where the no case is the failure case. Again, that's entirely covered by the Swift model and so the return type you will get will be void.
Now, on both of these examples, I have shown that these methods in Objective-C have multiple parameters, only one of which is the error parameter, but there are also cases where methods only have one parameter and you will get something like 'check resource is researchable and return error.' As you can see in Swift, since we already know that the method can return an error from that 'throws' keyword, we will chop off those last three words for you, just for you! Now, all of this is about what happens when you have the NSError-star-star type, when you are using the really basic form of the NSError conventions. What happens in other cases? Well, we just keep things as is. If you have an NSError in Objective-C, it will come into Swift usually as an optional NSError reference and you can handle it as appropriate for whatever API you are using.
Remember that NSError conforms to Swift's error type protocol, so you can also use it with Swift's own error handling mechanisms. All of this shows that Swift is well equipped to deal with all the errors coming from Objective-C.
But what if I call a method from Objective-C, and it's a Swift method that produces a Swift error? Now, of course, we need this to work. Let's find out how it works.
Here I have a type named 'request error.' It's a new type I'm defining using Swift's error system. It conforms to the error type protocol, and in this case, it's an enum that only has one possible enum case.
I can use this error in a method very easily. Here I'm defining a 'send request' method. I mark it with the 'throws' keyword to indicate that it can fail, and then I actually use the 'throw' statement when I hit that failure condition. Now, if I try to call this method from Objective-C, it ought to just work. Let's see what that looks like.
The first thing to notice is that the name of the method has changed. Instead of just 'send request, we now have 'send request error.' The name that you would have used if you had written this method in Objective-C to begin with.
Additionally, we're just using a plain-old NSError type, from foundation here, even though the error that we produced came from Swift.
And what's more, that error type actually contains useful information. If we print the domain and code that go with this error, we will see that it turns out to be the type and the raw value of the enum case that we saw before. Now, there's one more nice thing that's going on here. I marked this enum with the ObjC attribute. This is a feature we added in Xcode 6.3. If you mark an enum with the ObjC attribute, then it will get printed into your generated header. The header that exposes the Objective-C side of your Swift classes.
In Xcode -- sorry -- in Xcode 7, we've added one more little nice feature here. This is an enum that conforms to the error type protocol, so we'll also generate a string constant representing the error domain.
Now, all of this is great for errors you have defined in Swift, and you probably want to run out and use this for your own classes, but then the errors that come back from Cocoa and the rest of the SDKs start to feel a little lacking. And, don't worry, we have got you covered there too. So if you were at the presentation earlier today, you might have seen this preflight method which is checking that a resource is reachable and then catching some various error cases.
Now one of the error cases here is listed as 'NSURLError, file does not exist.' That's an error that comes from Cocoa. Why is that showing up, using the Swift notation? Well, we have taken the most common error types throughout our SDKs and made it so that you can use them with Swift's own catch syntax.
So there's are all sorts of errors you can use here. And the general idea that you should take away from this, is that errors should feel like NSError when you're in Objective-C, and they should feel like Swift errors when you are in Swift. Things should just work. So that's just some of the enhancements we've made to Swift in the area of interoperability over the last year. So to talk about the enhancements we've made in the Objective-C side of the world, I would like to hand it over to Doug.
DOUG GREGOR: Thank you. Thank you, Jordan.
So today I would like to talk about three new Objective-C features we've added this year.
These features can be used to make your Objective-C code better, your Objective-C APIs better, make them reflect better into Swift, and improve static type safety in your Objective-C. The first thing we are going to talk about is nullability for Objective-C.
So take a look at this bit of Objective-C code.
Lots of pointers here.
Which one of these can be nil? The code doesn't tell you.
If you go read the documentation, maybe it will tell you if you are feeling lucky today.
You could go write some tests and see what you think it behaves like or maybe guess, but that's not really good. There's really missing information here.
When we introduced Swift last year, this lack of information became much more apparent in these implicitly unwrapped optionals, which essentially means we don't know if it can be nil on the Swift side.
So we weren't too thrilled with this, and so after releasing Swift 1.0, we went and audited a couple thousand pointers within our own core frameworks to tell the compiler which of these pointers can be nil. And the interfaces after this audit got much, much cleaner.
Now, we are only using optional types where nil is actually something you have to deal with, and everything else is non-optional.
This is still not that wonderful, because this knowledge is baked into the compiler. It's not something you can do.
So with Xcode 6.3, we introduced nullability qualifiers for Objective-C.
So a nullability qualifier is something you can add to a C/Objective-C pointer to state whether it accepts nil or not. Of course, this better communicates what your API actually does. Does it accept nil? Does that make sense for it? It helps our tools do better static checking to catch bugs before they manifest at run time, and it makes the Swift experience so much better with your Objective-C APIs! Now, there are three nullability qualifiers.
Nullable, which indicates that the pointer may be null.
This, of course, maps into a Swift optional.
And then there's non-null. So this indicates that null or nil is not a meaningful value.
Now, for a non-null pointer, it could end up being nil in an Objective-C program. Maybe it comes because we messaged nil at some point and the nil propagated through and maybe this has worked in the past. The compiler is not going to change the way it generates code because of a non-null annotation. But this indicates the intent of the API author that nil does not make sense here. We also have a third qualifier, and that is null-unspecified.
This is for cases where neither nullable nor non-null is actually the right thing, and we map these into Swift as the same implicitly unwrapped optional you would get if we knew nothing about the pointer whatsoever. And the most important thing about nullability qualifiers is we've rolled them out throughout our SDKs.
Alright, so instead of just covering a couple of core frameworks, we've covered the majority of the SDKs. So this gives a much, much better Swift experience with true optionals where they actually matter and non-optionals everywhere else.
For your Objective-C code, it means you are going to start to see new warnings for places where you have been misusing APIs.
So, here, for example, you see a warning that you've passed null or nil to a method that did not expect it here. It is not part of the API contract.
Now maybe passing a nil has worked before; it may continue to work. But you should heed these warnings, because the API author has told you, you should not pass nil here, it's good to be careful because it may change in the future. Now, let's say you want to add nullability qualifiers to your own headers. The place to start is with an audited region.
So, these are described by 'NS-assume non-null begin, 'NS-assume non-null end; bracket your header with these macros.
What this does is it allows the compiler to make default assumptions about pointers that are not otherwise annotated. So, if you have a single level pointer, that's going to be assumed non-null, because what we've found is that nil is not a meaningful value for most of our APIs. The other interesting special case here is NSError-star-star parameters. Where you're doing error handling in Objective-C, these are assumed to be nullable at both levels, because that's the way you work with them for nullability.
Now the defaults are good, they should cover most of the cases, but that means you have to annotate the exceptional cases. So here we are marking the super view property as being nullable, because, of course, not every view has a super view. Nil has meaning there.
Hit test with event has a nullable parameter.
You don't have to pass in an event to do hit testing and, of course, the result is nullable because nil has meaning there. It means we didn't hit anything. Now, this is where null-unspecified comes up.
Say you are auditing a header.
You run into some really weird implementation that's been around forever. You have no idea what it does with nil. It hasn't been documented. Maybe it's doing something really, really interesting with messaging nil that may or may not work and the only guy that can answer it retired five years ago.
Great place to use null unspecified.
Just mark it as null unspecified. This means, 'I thought about it, I couldn't come to an answer.' The best thing to do is keep it implicitly unwrapped optional in Swift, keep it null-unspecified here. You can always come back to it later. So when auditing, you really want to get some good breadth early to make your APIs much, much better, faster.
Now, when you go down to C, things get a little bit more murky.
So we have all of the same qualifiers but they need to be preceded by the double-underscore.
Now, these double-underscored keywords, those qualifiers, can be used on any pointer, anywhere. The important rule here is that the qualifier goes to the right of the pointer it applies to, the same place that const or volatile would go to, to apply to that pointer.
This is particularly important for multilevel pointers like this values parameter here where the outer pointer is nullable, because you can pass in nil to this parameter so long as num-values is also zero.
The inner pointer is non-null, because when you are passing in an array of values, all of those values must be non-null to work with CF array. That's all we're going to talk about with nullability.
We've pushed it throughout our SDKs this year, so you get a much better experience in both Objective-C and Swift. We highly recommend that you use it to improve your own Objective-C APIs, particularly for a much better Swift experience. Alright, let's come on to the next feature. This is a big one. Lightweight generics for Objective-C. Now, the origin of this feature, it's actually fairly easy to motivate. Collections.
Here we have this NSArray of subviews. What's in the array? We don't say what's in the array. We bring this into Swift and you say, ah, it's an array of any object, of course. That still tells me absolutely nothing.
I have to cast a lot. That's what it tells me.
So we have had, of course, this common request for typed collections.
Because people really want to say I have an array of views. I have a dictionary that maps from string keys over to the images associated with those keys.
This has been probably been the most highly requested feature for the last decade of Objective-C.
So, now we are finally rolling it out with lightweight generics for Objective-C. So this is a general language feature that can be used to improve the expressivity of your APIs; it makes collections way easier to use now that we have all the static type safety of typed collections. Let's take a look.
So here's our subviews property again.
To make this an array of UI-views, we just place UI-view-star, so UIV pointer, in angle brackets. Same syntax you have seen. Whoops, sorry.
The same syntax you have seen from Swift and C++ and C-Sharp, et cetera, et cetera. Yes, we are completely aware that angle brackets are quote protocol qualifiers. Don't worry, we've got it. So this introduces more type information into Objective-C. Of course, this reflects into Swift as much more beautiful type information. But really I want to talk about the Objective-C effect because this is a really useful feature for Objective-C. Because let's talk about type safety. Here's an example. So I'm taking the path components of any URL, NSURL. And I'm putting it into an array of URLs. That almost seems reasonable if I didn't know this API and it would be a while before I got the run time error, the unrecognized selector that points out that no, I'm completely wrong in my usage of path components.
With type collections and, of course, us rolling out type collections throughout our SDKs, now you get a warning to tell you just what you did wrong at the point where it happens. Many other cases here say I'm building up immutable array of NS strings and I foolishly go and add something to that array without turning it into a string first, okay, the compiler is going to tell me, no, you can't put an NS number into this NS mutable array of strings. It doesn't make sense. Now, the compiler actually has fairly deep knowledge of the semantics here of the Cocoa collections.
So let's take this example here, we have an array of views, we have an array of responders. We assign from the array of views to the array of responders, okay every view is a responder. This seems fine. It is okay.
Let's do the same thing with the mutable variant.
So we take the stored mutable array of views and assign it over to the stored mutable array of responders. There's a trap here.
The trap, of course, is I can go ahead and mutate my stored responders, put something in there that's a responder but not a view.
Now, something later on is getting very, very confused that the array of views it's looking at has a view controller in it, not a view.
Compiler understands this is as well. It will complain at the point of initialization, here, that while it's perfectly safe to do this kind of assignment with immutable arrays because they don't change from underneath you, it is not safe to do with the mutable variants. Alright. You have actually seen all you need to use lightweight generics and typed collections throughout your applications. But let's take a look at how we use this within Foundation itself, to actually create typed collections. Because the feature here is generics. Typed collections is one of the outcomes of it. So here we have NSArray as you are used to seeing it.
Now we are going to parameterize it, based on the object type that's stored in the NSArray, again, using angle brackets. We are just introducing a name here.
We can use that name throughout the interface, so, of course, object at index, returns something of the object type. If we go ahead and add other methods here.
When you initialize with objects the array you get in, the C-array, contains objects.
Use array by adding object. You put in a new object, you get an NSArray of objects. So it composes very, very, very nicely.
Of course this also works for categories and extensions.
Here we have an NSDictionary category, parameterized on key type and the object type, and we have gone ahead and added object for key here, takes in a key type, returns a nullable object type because, of course, nil matters here and we want to have that information in our APIs. Now, of course, there are existing categories defined on NSDictionary, NSArray, and so on, you may have some yourself. Those will continue to work as they always have. They cannot access the type parameters in any way, but they also won't change behavior at all, they will continue working as they always have.
That brings me to backward compatibility.
So the entire lightweight generics feature is based on a type erasure model. Which means that the compiler has all of this rich static type information but it erases that information when generating code.
There's huge benefits here for Objective-C, that we did not have to make any changes to the Objective-C run time to make this feature work.
That means we are able to roll out generics in type collections, you can adopt them in all of your applications and it does not affect your ability to deploy back to existing operating systems.
DOUG GREGOR: And if it wasn't obvious from that description, of course, we are also not going to change cogeneration in any way. We are not adding runtime checking to Objective-C that doesn't make sense in Objective-C.
We just maintain full binary compatibility. So adopt this feature, enjoy the warnings preventing you from making horrible mistakes.
And you can adopt it very, very, very... hmm gradually, I guess is the best term here.
So in addition to binary compatibility, we want to provide source compatibility, because we have rolled this feature out through our SDKs. We don't want you to have to update all of your source code to use these things everywhere. We want you to gradually adopt it. Use it where it makes sense for you. And so we provided these implicit conversions in the language that allow you to add type arguments or take them away.
Okay? Again, there's no run time cost to doing this. But it lets you get in and get out of the generic system as you need. So all of your new code can be written with generics and if you don't want to touch your old code, that's perfectly line. It's not going to change. All right. We have one last feature to talk about, this one's kind of cool. So kindof types. Actually, came out of our work on lightweight generics.
And so we started with, of course, using the untyped collection here for subviews, and there's code that may go grab a view and send some message to it.
This code is fine. It works today.
We went ahead and did these annotations to say, okay, subviews contains UI views and now we get some warnings.
The compiler is completely justified in giving this warning.
It knows now that the first subview is a UI view.
It can't know that you know it's a UI button that would actually respond to this selector.
And so while the compiler is right, it's not necessarily useful to be producing warnings on all of this code. You get a raft of warnings most of which would be benign and you would be adding casts everywhere.
This really forced us to consider ID and how it's used as an API contract.
And so let's make this a little simpler example. Let's talk about NSApp, which is this global in Cocoa that gives you access to your NSApplication instance.
Now, what this really means is NSApp is a subclass of NSApplication. But we couldn't describe that to the type system.
With kindof types, we can say this exactly in Objective-C. What does this mean? This means NSApp is some kind of application. So we are going to give it some limited behavior, like ID.
So I have my NSApp, of course I can convert it to an NSObject because every NSApplication is an NSObject.
The important part here, the ID-like behavior, is the implicit downcast from NSApplication to your MyApplication subclass. That's good. We want that behavior because that's how NSApp has always worked.
Now, what we won't allow is some silly cross-cast. You tried to use NSApp in a string? Well, that's not an NSApplication and never can be. So you can produce a warning there. Of course, coming with this is the notion that you can message NSApp and get all of the methods that are in NSApplication, its superclasses, and its subclasses. Now, with kindof types we actually found that this is much more useful than ID. Because it gives you more type information in the API contract which is great for both Swift and Objective-C. So here we have this NS tableview method, you have column, row, makeIfNecessary. It's always returned to ID, because that implicit downcasting behavior is important.
Well, now we can rewrite it as a kindof NSview. So returning some kind of NSView, and on the Swift side, ah, now we are returning an optional NSView. That's the right API for Swift and it works beautifully in Objective-C.
Let's bring it back to our original example with lightweight generics. Here we were with our subviews stated as an NSArray of UIViews. Well, now we can state that it's an NSArray of kinds of UI views. So we get the stronger API contract in Objective-C and Swift. It's far easier to tell what this property is, but you don't cause any spurious warnings because you still have that nice, implicit downcasting behavior from ID.
Hopefully this leads you to a question. Should I even use ID in an API? Over the years we have actually been rolling out a lot of features that give more specific type information than ID for various important scenarios.
And the same year we introduced Arc, we introduced instance type, which says that the method you are calling returns something of the same dynamic type as self.
This year we are introducing type collections that eliminate tons of uses of ID. From our own APIs we have been rolling out these features and, of course, from your APIs when you adopt this feature.
We now have kindof X to talk about any subclass of X with implicit downcasting. So you can keep your code working, at least all the actually working code that's not doing weird things, but have a better API contract, and, of course, protocols have been in Objective-C for a very, very long time and using ID of some protocol is a great way to say I don't care what the class type is. It just has to conform to this protocol. So there's one major class of uses for ID. And that's when you really, really do mean an object of any type. And there's no static type information that could make that statement better.
So, a canonical example here is the user info dictionary. You have string keys and you have ID for the values.
It's completely reasonable because the different keys will have different types in those values and it's something you can only determine dynamically. Let's wrap things up. So, we have big improvements for both Swift and Objective-C. These languages are codesigned and coevolved to work together beautifully. And Xcode and the tools supporting it will help you move between the two languages, the best way for your workflow.
We have rolled out a lot of great features for Objective-C. We highly recommend you modernize your usage of Objective-C. These new language features give you much better APIs, and you will see this in our APIs and in yours, and give you far better type safety to catch bugs before they manifest as the dreaded unrecognized selector at runtime. And using these features can really make your Objective-C interfaces beautiful in Swift, so you have the best Swift experience with your own code. For more information, please contact our Evangelist, Stefan Lesser, check out the documentation, or talk to us in the forum online. There's a bunch of related sessions talking about what's new in Swift and Cocoa. Lots of great Swift sessions here.
Thank you very much. [Applause]
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.