jjrscott
(John Scott)
1
Please, is there a better way to implement an async initializer where the actual construction is external to the call?
private protocol NSOtherable { }
extension NSObject: NSOtherable { }
extension NSOtherable where Self: NSObject {
init(other: Self) {
self.init(__other: other)
}
}
extension UIImage {
convenience init(url: URL?) async throws {
try await self.init(other: Self.load(url: url) as! Self)
}
private static func load(url: URL?) async throws -> UIImage {
// ...
}
}
@interface NSObject (Other)
- (instancetype)initWithOther:(NSObject*)other NS_REFINED_FOR_SWIFT;
@end
@implementation NSObject (Other)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
- (instancetype)initWithOther:(NSObject*)other {
if (self.class != other.class) {
[NSException raise:@"Incorrect class" format:@"'%@' can't be passed as '%@'", NSStringFromClass(other.class), NSStringFromClass(self.class)];
}
return other;
}
#pragma clang diagnostic pop
@end
As an aside, extending NSObjectProtocol insead of creating NSOtherable causes a compule fail:
extension NSObjectProtocol where Self: NSObject {
init(other: Self) {
self.init(__other: other)
}
}
// Undefined symbols for architecture arm64:
// "(extension in AsyncInit):__C.NSObject< where A: __C.NSObject>.init(foo: A) -> A", referenced from:
// (2) suspend resume partial function for (extension in AsyncInit):__C.UIImage.init(url: Foundation.URL?) async throws -> __C.UIImage in UIImage+Extensions.o
// ld: symbol(s) not found for architecture arm64
// clang: error: linker command failed with exit code 1 (use -v to see invocation)
tera
2
Is this specific to UIImage, or are you using UIImage as an illustrative example only?
I must admit the way you do it is quite ingenious. However, can't you use a static function directly?
try await UIImage.image(with: url)
FWIW, there are quite a few UIImage creation API's that are not "inits": imageNamed, systemImageNamed, animatedImageNamed, etc. and it doesn't seem to be a problem in real world apps.
If you can only stomach this "canonic" form of image creation: try await UIImage(url: url) then consider another ingenious trick that would allow just that:
func UIImage(url: URL) async throws -> UIImage { ... }
For completeness, there might be other ways that go through some intermediate representation (e.g. CGImage -> UIImage, or Data -> UIImage) but they have their limitations and performance issues.
NB. This is how obj-c imageNamed static method is translated into swift's "init":
UIKit.apinotes:
...
- Name: UIImage
Methods:
- Selector: 'imageNamed:'
MethodKind: Class
SwiftName: 'init(named:withConfiguration:)'
1 Like
jjrscott
(John Scott)
3
I really appreciate you taking the time to make a wonderful enumeration of what’s available.
I’m honestly not sure why I wanted an init based solution, maybe I just thought it was cooler or something. Also I’m an old Objective-C guy so was more than happy to go down the rabbit hole.
tera
4
BTW, your ingenious trick works without going to Obj-C:
protocol Otherable { // what a name...
init(other: Self)
}
extension Otherable {
init(other: Self) { self = other } // 😂
}
extension UIImage: Otherable {
enum Err: Error { case imageConversionError }
convenience init(url: URL) async throws {
let image = try await Self.load(url: url)
self.init(other: image as! Self)
}
private static func load(url: URL) async throws -> UIImage {
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
throw UIImage.Err.imageConversionError
}
return image
}
}
2 Likes