Objective-C APIs do a lot of object construction using class methods:
+ (NSDate *)date;
+ (NSURL *)urlWithString:(NSString *)string;
+ (instancetype)layerWithSession:(AVCaptureSession *)session
Sometimes I even see these appearing in old Swift tutorials as class methods, but when I try to call them, I get compiler errors like these:
date()
is unavailable, use object constructionNSDate()
urlWithString()
is unavailable, use object constructionNSURL(string:)
layerWithSession()
is unavailable, use object constructionAVCaptureVideoPreviewLayer(session:)
(I see declarations like convenience init(URL url: NSURL)
in the docs, and in Xcode's generated Swift interfaces for SDK classes, but what do they have to do with any of this?)
This even happens when I declare class methods in my own ObjC code, like for a singleton pattern:
@interface MyManager: NSObject
+ (instancetype)manager;
@end
When I try to use them from Swift, I get the same kind of errors:
let manager = MyManager.manager()
// error: 'manager()' is unavailable, use object construction 'MyManager()'
What gives? How do I fix these errors?
Swift does away with the alloc
/init
initialization pattern from Objective-C and replaces it with a construction syntax similar to that seen in Java or C++.
When, in a Swift class interface (or in Apple's documentation), you see something like:
class Thing {
init(number: Int)
}
That means you can create a Thing
by calling it with the following "construction syntax":
let thingTwo = Thing(number: 2)
It doesn't matter if it's a class
or a struct
, or if you see other things like convenience
or required
or public
before init
... that's how you use it. (Of course, replace Thing
with the type you're using, number
with any parameter label(s) in the actual initializer you want, and 2
with an appropriate value for that parameter.)
When classes originally defined in ObjC are imported into Swift, the Swift compiler does a bunch of things to make those APIs look and feel more like native Swift code. One of those things is to replace the common pattern of using class methods as convenience constructors with real initializers.
All the examples from the question:
+ (NSDate *)date;
+ (NSURL *)urlWithString:(NSString *)string;
+ (instancetype)layerWithSession:(AVCaptureSession *)session;
... are such convenience constructors. That means they import into Swift not as class methods (NSDate.date()
, NSURL.urlWithString()
, etc), but as initializers:
class NSDate {
init()
}
class NSURL {
init?(string: String)
}
class AVCaptureVideoPreviewLayer {
init!(session: AVCaptureSession)
}
... and you call them with object construction syntax:
let date = NSDate()
let url = NSURL(string: "http://apple.com")
let layer = AVCaptureVideoPreviewLayer(session: mySession)
In fact, when you see error messages about "is unavailable, use object construction", Xcode pretty much always offers an automatic fix-it to insert the proper syntax.
How does this work? Swift looks for possible "base names" of a class and matches between those names and the class method / initializer method names. (It also checks for class methods that return an instance of that class, initializers that start with "init...", etc.)
For example, Swift sees the following as a convenience constructor:
@interface PDQMediaManager: NSObject
+ (PDQMediaManager *)manager;
@end
This may be a problem if what you (or the original developer of PDQMediaManager
) intend is for that class to act as a singleton, and the +manager
method to return the singleton instance — if a Swift developer writes PDQMediaManager()
, they'll be constructing a new instance instead of retrieving the singleton.
In this case, one good solution is to rename the singleton method:
+ (PDQMediaManager *)defaultManager;
This won't get seen as a convenience constructor, so Swift clients can call PDQMediaManager.defaultManager()
to get the singleton instance.
You can also use the NS_SWIFT_NAME
to rename the singleton method only for Swift clients:
+ (PDQMediaManager *)manager NS_SWIFT_NAME(defaultManager());
If you don't control the codebase with the offending method, your best bet is probably to write your own ObjC code to forward to the singleton method and expose that to your Swift code via a bridging header.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With