Swift: Creating a Factory Function That Takes a Class Type as a Parameter, and Outputs a Constructor of That Class

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 valid

let 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



Leave a reply



Submit