Skip to content

Latest commit

 

History

History
264 lines (212 loc) · 5.61 KB

README.md

File metadata and controls

264 lines (212 loc) · 5.61 KB

Magritte-Swift

Extends Magritte with ability to generate Swift class declarations from Smalltalk classes.

Installing

Metacello new
	baseline: 'MagritteSwift';
	repository: 'github://grype/Magritte-Swift';
	onConflictUseLoaded;
	load.

Magritte-Swift depends on Magritte3, which is still tied to Seaside on smalltalkhub. So if you've loaded Seaside from GitHub, be sure to include #onConflictUseLoaded, as indicated above...

Using

As an example, let's start with a simple class that defines a few basic types of properties:

GRObject subclass: #She
  uses: TMASwiftDescribing
  instanceVariableNames: 'name balance seashore shells'
  classVariableNames: ''
  poolDictionaries: ''
  category: 'SheSellsSeashellsByTheSeashore'!

After generating accessors for the ivars, let's add magritte descriptions for those properties:

She class>>nameDescription
  <magritteDescription>
  ^ MAStringDescription new
    label: 'Name';
    accessor: #name;
    beSwiftCodable;    "<- makes this property Codable"
    swiftName: #name;  "<- swift name of the property"
    yourself.
    
She class>>balanceDescription
  <magritteDescription>
  ^ MANumberDescription new
    label: 'Balance';
    accessor: #balance;
    beSwiftCodable;
    swiftName: #balance;
    yourself.
    
She class>>seashoreDescription
  <magritteDescription>
  ^ MAToOneRelationDescription new
    label: 'Seashore';
    accessor: #seashore;
    classes: { Seashore };
    beSwiftCodable;
    swiftName: #seashore;
    yourself.
    
She class>>shells
  <magritteDescription>
  ^ MAToManyRelationDescription new
    label: 'Shells';
    accessor: #shells;
    classes: { Shell };
    beSwiftCodable;
    swiftName: #shells;
    yourself.

To retrieve the string declaration of that class in Swift:

She asSwift.

which should produce:

import Foundation

class She : Codable {
	var seashore: Seashore?
	var shells: [Shell]?
	var name: String?
	var balance: NSNumber?

	enum CodingKeys : String, CodingKey {
		case seashore
		case shells
		case name
		case balance
	}
}

Required attributes

Now let's say that the #name is a required attribute:

She class>>nameDescription
  <magritteDescription>
  ^ MAStringDescription new
    label: 'Name';
    accessor: #name;
    required: true; "<-- Adding required field"
    beSwiftCodable;
    swiftName: #name;
    yourself.

That would produce a slightly different swift declaration of the class:

class She : Codable {
	var seashore: Seashore?
	var shells: [Shell]?
	var name: String
	var balance: NSNumber?

	init(name aName: String) {
		name = aName
	}

	enum CodingKeys : String, CodingKey {
		case seashore
		case shells
		case name
		case balance
	}
}

Notice that the property is no longer optional and an init() method declaration was added...

Numbers

By default, MagritteSwift will use NSNumber as the numeric type. Let's try change that to a Double, making it required and initialized to a value:

balanceDescription
  <magritteDescription>
  ^ MANumberDescription new
    label: 'Balance';
    accessor: #balance;
    beSwiftCodable;
    swiftName: #balance;
    swiftType: #Double;    "<- Specify name of swift type"
    beRequired;            "<- Required"
    swiftDefaultValue: 0;  "<- Initial value"
    swiftDeclarationModifiers: #(#'private(set)'). "<- declaration modifiers is just a collection of swift source strings"
    yourself

The resulting swift declaration should now look like this:

class She : Codable {
	var seashore: Seashore?
	var shells: [Shell]?
	var name: String
	private(set) var balance: Double = 0

	init(name aName: String) {
		name = aName
	}

	enum CodingKeys : String, CodingKey {
		case seashore
		case shells
		case name
		case balance
	}
}

A more fine-grained declaration can be seen here:

MyClass class>>myPropertyDescription
  ^ MANumberDescription new
    swiftName: #foo;
    "let foo = ... as opposed to: let foo: SomeType = ..."
    swiftType: SwiftInferredType;
    " let foo = ?"
    swiftDefaultValue:
      "create initialization expression MyType<Generic>()"
      (MASwiftInitializingExpression new
        type: (MASwiftType new
	  name: #MyType;
	  genericParameters: #(#Generic);
	  yourself);
	yourself);
    "let as opposed to var"
    beSwiftConstant;
    yourself

That should produce:

let foo = MyType<Generic>()

How do I make this generate Realm models?

Generation of class description for Realm models requires a few tweaks, and the easiest way to accomplish that is to use TMASwiftRealmDescribing trait, as opposed to TMASwiftDescribing:

GRObject subclass: #MyModel
  uses: TMASwiftRealmDescribing
  instanceVariableNames: ''
  classVariableNames: ''
  package: 'MyPackage'

Better yet, let's use that as the base class for all of our models, which allows us to create extensions in Swift, that are applicable to all models:

MyModel subclass: #MyFriend
	instanceVariableNames: 'nickname'
	classVariableNames: ''
	package: 'MyPackage'
	
MyFriend class>>nicknameDescription
  <magritteDescription>
  ^ MAStringDescription new
    label: 'Nickname';
    accessor: #nickname;
    beSwiftCodable;
    swiftName: #nickname;
    swiftDeclarationModifiers: #(#'@objc' #dynamic);
    yourself

MyModel asSwift:

import Foundation
import RealmSwift

class MyModel : Object {

}

MyFriend asSwift:

import Foundation
import RealmSwift

class MyFriend : MyModel, Codable {
  @objc dynamic var nickname: String?

  enum CodingKeys : String, CodingKey {
    case nickname
  }
}

That's it...