Swift: Creating a factory function that takes a class type as a parameter, and outputs a constructor of that class
Let's work backwards. In your makeFruit
function, you'll need to declare the typeOfFruit
parameter as a metatype of your Fruit
superclass and explicitly reference the initializer:
func makeFruit(typeOfFruit: Fruit.Type) -> (Int) -> Fruit {
return { (unknownNumber: Int) -> Fruit in
return typeOfFruit.init(unknownNumber: unknownNumber)
}
}
You can only access required
initializers on a metatype, so that init
needs to be marked as such:
class Fruit {
required init(unknownNumber: Int) {
// ...
}
}
The rest should just work:
let orangeMaker = makeFruit(Orange.self)
let tenOranges = orangeMaker(10)
How are factory constructors supposed to be done in Swift?
I wouldn't do for a Factory method on Shape, neither a protocol nor a super class should know details about implementations or derived classes. I'd rather go for a ShapeBuilding protocol and a factory class.
protocol Shape {
func draw()
}
protocol ShapeBuilding {
static func build(kind:String) -> Shape?
}
struct ShapeBuilder: ShapeBuilding {
static func build(kind: String) -> Shape? {
switch kind {
case "Circle": return Circle()
case "Triangle": return Triangle()
default: return nil // Error - bad Shape
}
}
}
let circle = ShapeBuilder.build("Circle")
Motto: Just because Swift offers extensions we don't have to force everything into extension.
Swift class introspection & generics
Well, for one, the Swift equivalent of [NSString class]
is .self
(see Metatype docs, though they're pretty thin).
In fact, NSString.class
doesn't even work! You have to use NSString.self
.
let s = NSString.self
var str = s()
str = "asdf"
Similarly, with a swift class I tried...
class MyClass {
}
let MyClassRef = MyClass.self
// ERROR :(
let my_obj = MyClassRef()
Hmm… the error says:
Playground execution failed: error: :16:1: error: constructing an object of class type 'X' with a metatype value requires an '@required' initializer
Y().me()
^
<REPL>:3:7: note: selected implicit initializer with type '()'
class X {
^
It took me a while to figure out what this means… turns out it wants the class to have a @required init()
class X {
func me() {
println("asdf")
}
required init () {
}
}
let Y = X.self
// prints "asdf"
Y().me()
Some of the docs refer to this as .Type
, but MyClass.Type
gives me an error in the playground.
Is there difference between using a constructor and using .init?
From the Initializer Expression section of the language guide:
If you specify a type by name, you can access the type’s initializer without using an initializer expression. In all other cases, you must use an initializer expression.
let s1 = SomeType.init(data: 3) // Valid
let s2 = SomeType(data: 1) // Also validlet s3 = type(of: someValue).init(data: 7) // Valid
let s4 = type(of: someValue)(data: 5) // Error
Initializing using the explicit .init
on the type directly is no different than without it; they are equivalent from Swift's perspective, so most folks prefer the brevity of omitting .init
.
How to define Typescript function that takes a type as a strongly typed argument
If you want only the classes Page, DerivedPage, AnotherDerivedPage; you can use something as follows:
function foo (foo: Page | DerivedPage | AnotherDerivedPage)
If you want an argument which extends Page, probably below is the way to go:
function foo<T extends Page> (a: T) { ... }
EDIT:
Adding an example of such type to use with arrow functions
type foo<T extends Page> = (v: T) => void
const fn: foo<DeribedPage> = (v) => { /* ... */ }
Problem assigning properties with async code in class initializer
Make the async call before constructing the class, and pass the result to the constructor so it can be put as a property synchronously. For example:
(async () => {
class MyClass {
property: number;
constructor(foo: number) {
this.property = foo;
}
static async create() {
const foo = await Promise.resolve(10);
const myClass = new MyClass(foo);
return myClass;
}
}
const myClass = await MyClass.create()
})();
With regards to your update, you can use the same technique. Don't call new Api
until the async results are finished, and pass the results to the constructor, and the constructor can assign to the instance:
class Api {
api: AuthAPI;
// etc
private constructor(api: AuthAPI, /* etc */) {
this.api = api;
}
// Static factory function to execute async code on class initialization, this can be replaced with `init method` to get the same result: execute asynchronous code when the class is initialised.
static async create(
// Configuration values
client: ApolloClient<any>,
config: ConfigInput,
onStateUpdate?: () => any,
): Promise<Api> {
const finalConfig = {
...defaultConfig,
...config,
};
const localStorageHandler = new LocalStorageHandler();
const apolloClientManager = new ApolloClientManager(client);
const jobsManager = await JobsManager.create(
localStorageHandler,
apolloClientManager,
);
// Async classes/methods to await
const saleorState = await SaleorState.create(
finalConfig,
localStorageHandler,
apolloClientManager,
jobsManager,
);
const localStorageManager = new LocalStorageManager(
localStorageHandler,
saleorState,
);
if (onStateUpdate) {
saleorState.subscribeToNotifiedChanges(onStateUpdate);
}
return new Api(
// Create properties with async results
new AuthAPI(saleorState, jobsManager, finalConfig),
new SaleorCheckoutAPI(saleorState, jobsManager),
new SaleorCartAPI(
localStorageManager,
apolloClientManager,
saleorState,
jobsManager,
),
new CategoriesAPI(client),
new CollectionsAPI(client),
new ProductsAPI(client),
});
}
}
For a more minimal example of this that compiles:
class Api {
auth: number;
private constructor(auth: number) {
this.auth = auth;
}
static async create(): Promise<Api> {
const auth = await Promise.resolve(5);
return new Api(auth );
}
}
(async () => {
const myApi = await Api.create();
const result = myApi.auth + 5;
})();
Demo
That said, for this particular situation, does the main class you're creating really not have any other methods? If not, then there's not much point to it - just use a plain object instead. You could change the bottom of create
to
return {
// Create properties with async results
auth: new AuthAPI(saleorState, jobsManager, finalConfig),
checkout: new SaleorCheckoutAPI(saleorState, jobsManager),
cart: new SaleorCartAPI(
localStorageManager,
apolloClientManager,
saleorState,
jobsManager,
),
categories: new CategoriesAPI(client),
collections: new CollectionsAPI(client),
products: new ProductsAPI(client),
};
and have create
be just a plain function and not part of a class. That's what I'd strongly prefer if I was in your situation - it'll significantly cut down on the syntax noise of typing out the class and class properties/values and arguments (not to mention is just plain more appropriate).
Related Topics
Swiftui: Using View Modifiers Between Different iOS Versions Without #Available
Using Custom Cifilter on Calayer Shows No Change to Calayer
How to Grant Discoveruserinfowithuserrecordid Permission
Create Spotlight-Like Window in Swift 4
How to Replace Limited Number of Occurrences in String
Ios/Tvos Playground Fails with "Unable to Find Execution Service for Selected Run Destination"
Nsdateformatter Detect 24-Hour Clock in Os X and iOS
How to Understand What Is Causing a Crash Involving '_Nstouchbarfinderobservation'
Prevent Redirect Response with Alamofire in Swift
Curl with Alamofire - Swift - Multipart/Form-Data
Swift Firebase Custom Object with Document Id
Swiftui Sheet Not Updating Variable
Calculate Time Difference in Swift 4
How to Draw Line Node Keep Same Size in Camera as Measure App in Iphone
Swift Alternative to Respondstoselector:
Realitykit - How to Edit or Add a Lighting