[Swift] Swift implementation 🎉🎉 (#5603)

* Implemented the swift version of Flatbuffers

Implemented serailzing, reading, and mutating data from object monster

Fixes mis-aligned pointer issue

Fixes issue when shared strings are removed from table

Adds swift enum, structs code gen

Fixed namespace issues + started implementing the table gen

Added Mutate function to the code generator

Generated linux test cases

Fixed an issue with bools, and structs readers in table writer

Swift docker image added

Updated the test cases, and removed a method parameters in swift

Fixed createVector api when called with scalars

Fixed issues with scalar arrays, and fixed the code gen namespaces, added sample_binary.swift

Cleaned up project

Added enum vectors, and their readers

Refactored code

Added swift into the support document

Added documentation in docs, and fixed a small issue with Data() not being returned correctly

Fixes Lowercase issue, and prevents generating lookups for deprecated keys

* Made all the required funcs to have const + removed unneeded code + fix lowercase func

* Removed transform from lowercased and moved it to function

* Fixes an issue with iOS allocation from read

* Refactored cpp code to be more readable

* casts position into int for position

* Fix enums issue, moves scalar writer code to use memcpy

* Removed c_str from struct function

* Fixed script to generate new objects when ran on travis ci: fix

* Handles deallocating space allocated for structs

* Updated the test cases to adhere to the fileprivate lookup, no mutation for unions, and updated the names of the vector functions
This commit is contained in:
mustiikhalil
2020-01-09 23:12:10 +03:00
committed by Wouter van Oortmerssen
parent 55686100aa
commit 04d80f255d
41 changed files with 4015 additions and 40 deletions

View File

@@ -47,6 +47,8 @@ For any schema input files, one or more generators can be specified:
- `--rust`, `-r` : Generate Rust code.
- `--swift`: Generate Swift code.
For any data input files:
- `--binary`, `-b` : If data is contained in this file, generate a

View File

@@ -4,7 +4,7 @@ FlatBuffers {#flatbuffers_index}
# Overview {#flatbuffers_overview}
[FlatBuffers](@ref flatbuffers_overview) is an efficient cross platform
serialization library for C++, C#, C, Go, Java, Kotlin, JavaScript, Lobster, Lua, TypeScript, PHP, Python, and Rust.
serialization library for C++, C#, C, Go, Java, Kotlin, JavaScript, Lobster, Lua, TypeScript, PHP, Python, Rust and Swift.
It was originally created at Google for game development and other
performance-critical applications.
@@ -148,6 +148,8 @@ sections provide a more in-depth usage guide.
own programs.
- How to [use the generated Rust code](@ref flatbuffers_guide_use_rust) in your
own programs.
- How to [use the generated Swift code](@ref flatbuffers_guide_use_swift) in your
own programs.
- [Support matrix](@ref flatbuffers_support) for platforms/languages/features.
- Some [benchmarks](@ref flatbuffers_benchmarks) showing the advantage of
using FlatBuffers.

View File

@@ -18,23 +18,23 @@ In general:
NOTE: this table is a start, it needs to be extended.
Feature | C++ | Java | C# | Go | Python | JS | TS | C | PHP | Dart | Lobster | Rust
------------------------------ | ------ | ------ | ------ | ------ | ------ | --------- | --------- | ------ | --- | ------- | ------- | ----
Codegen for all basic features | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | WiP | Yes | Yes | Yes
JSON parsing | Yes | No | No | No | No | No | No | Yes | No | No | Yes | No
Simple mutation | Yes | Yes | Yes | Yes | No | No | No | No | No | No | No | No
Reflection | Yes | No | No | No | No | No | No | Basic | No | No | No | No
Buffer verifier | Yes | No | No | No | No | No | No | Yes | No | No | No | No
Testing: basic | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ? | Yes | Yes | Yes
Testing: fuzz | Yes | No | No | Yes | Yes | No | No | No | ? | No | No | Yes
Performance: | Superb | Great | Great | Great | Ok | ? | ? | Superb | ? | ? | Great | Superb
Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | Yes | VS2010 | ? | Yes | Yes | Yes
Platform: Linux | GCC282 | Yes | ? | Yes | Yes | ? | Yes | Yes | ? | Yes | Yes | Yes
Platform: OS X | Xcode4 | ? | ? | ? | Yes | ? | Yes | Yes | ? | Yes | Yes | Yes
Platform: Android | NDK10d | Yes | ? | ? | ? | ? | ? | ? | ? | Flutter | Yes | ?
Platform: iOS | ? | ? | ? | ? | ? | ? | ? | ? | ? | Flutter | Yes | ?
Engine: Unity | ? | ? | Yes | ? | ? | ? | ? | ? | ? | ? | No | ?
Primary authors (github) | aard* | aard* | ev*/js*| rw | rw | evanw/ev* | kr* | mik* | ch* | dnfield | aard* | rw
Feature | C++ | Java | C# | Go | Python | JS | TS | C | PHP | Dart | Lobster | Rust | Swift
------------------------------ | ------ | ------ | ------ | ------ | ------ | --------- | --------- | ------ | --- | ------- | ------- | ------- | ------
Codegen for all basic features | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | WiP | Yes | Yes | Yes | Yes
JSON parsing | Yes | No | No | No | No | No | No | Yes | No | No | Yes | No | No
Simple mutation | Yes | Yes | Yes | Yes | No | No | No | No | No | No | No | No | No
Reflection | Yes | No | No | No | No | No | No | Basic | No | No | No | No | No
Buffer verifier | Yes | No | No | No | No | No | No | Yes | No | No | No | No | No
Testing: basic | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ? | Yes | Yes | Yes | Yes
Testing: fuzz | Yes | No | No | Yes | Yes | No | No | No | ? | No | No | Yes | No
Performance: | Superb | Great | Great | Great | Ok | ? | ? | Superb | ? | ? | Great | Superb | ?
Platform: Windows | VS2010 | Yes | Yes | ? | ? | ? | Yes | VS2010 | ? | Yes | Yes | Yes | No
Platform: Linux | GCC282 | Yes | ? | Yes | Yes | ? | Yes | Yes | ? | Yes | Yes | Yes | Yes
Platform: OS X | Xcode4 | ? | ? | ? | Yes | ? | Yes | Yes | ? | Yes | Yes | Yes | Yes
Platform: Android | NDK10d | Yes | ? | ? | ? | ? | ? | ? | ? | Flutter | Yes | ? | No
Platform: iOS | ? | ? | ? | ? | ? | ? | ? | ? | ? | Flutter | Yes | ? | Yes
Engine: Unity | ? | ? | Yes | ? | ? | ? | ? | ? | ? | ? | No | ? | No
Primary authors (github) | aard* | aard* | ev*/js*| rw | rw | evanw/ev* | kr* | mik* | ch* | dnfield | aard* | rw | mi*/mz*
* aard = aardappel (previously: gwvo)
* ev = evolutional
@@ -42,5 +42,7 @@ Primary authors (github) | aard* | aard* | ev*/js*| rw | rw | ev
* mik = mikkelfj
* ch = chobie
* kr = krojew
* mi = mustiikhalil
* mz = mzaks
<br>

93
docs/source/SwiftUsage.md Normal file
View File

@@ -0,0 +1,93 @@
Use in Swift {#flatbuffers_guide_use_swift}
=========
## Before you get started
Before diving into the FlatBuffers usage in Swift, it should be noted that
the [Tutorial](@ref flatbuffers_guide_tutorial) page has a complete guide
to general FlatBuffers usage in all of the supported languages (including Swift).
This page is designed to cover the nuances of FlatBuffers usage, specific to
Swift.
You should also have read the [Building](@ref flatbuffers_guide_building)
documentation to build `flatc` and should be familiar with
[Using the schema compiler](@ref flatbuffers_guide_using_schema_compiler) and
[Writing a schema](@ref flatbuffers_guide_writing_schema).
## FlatBuffers Swift library code location
The code for the FlatBuffers Swift library can be found at
`flatbuffers/swift`. You can browse the library code on the [FlatBuffers
GitHub page](https://github.com/google/flatbuffers/tree/master/swift).
## Testing the FlatBuffers Swift library
The code to test the Swift library can be found at `flatbuffers/Flatbuffers.Test.Swift`.
The test code itself is located in [Flatbuffers.Test.Swift](https://github.com/google/
flatbuffers/blob/master/tests/FlatBuffers.Test.Swift).
To run the tests, use the [SwiftTest.sh](https://github.com/google/flatbuffers/
blob/master/tests/FlatBuffers.Test.Swift/SwiftTest.sh) shell script.
*Note: The shell script requires [Swift](https://swift.org) to
be installed.*
## Using the FlatBuffers Swift library
*Note: See [Tutorial](@ref flatbuffers_guide_tutorial) for a more in-depth
example of how to use FlatBuffers in Swift.*
FlatBuffers supports reading and writing binary FlatBuffers in Swift.
To use FlatBuffers in your own code, first generate Swift structs from your
schema with the `--swift` option to `flatc`. Then include FlatBuffers using `SPM` in
by adding the path to `FlatBuffers/swift` into it. The generated code should also be
added to xcode or the path of the package you will be using. Note: sometimes xcode cant
and wont see the generated files, so it's better that you copy them to xcode.
For example, here is how you would read a FlatBuffer binary file in Swift: First,
include the library and copy thegenerated code. Then read a FlatBuffer binary file or
a data object from the server, which you can pass into the `GetRootAsMonster` function.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.swift}
import FlatBuffers
typealias Monster1 = MyGame.Sample.Monster
typealias Vec3 = MyGame.Sample.Vec3
let path = FileManager.default.currentDirectoryPath
let url = URL(fileURLWithPath: path, isDirectory: true).appendingPathComponent("monsterdata_test").appendingPathExtension("mon")
guard let data = try? Data(contentsOf: url) else { return }
let monster = Monster.getRootAsMonster(bb: ByteBuffer(data: data))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now you can access values like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.swift}
let hp = monster.hp
let pos = monster.pos
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some cases it's necessary to modify values in an existing FlatBuffer in place (without creating a copy). For this reason, scalar fields of a Flatbuffer table or struct can be mutated.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.swift}
let monster = Monster.getRootAsMonster(bb: ByteBuffer(data: data))
if !monster.mutate(hp: 10) {
fatalError("couldn't mutate")
}
// mutate a struct field
let vec = monster.pos.mutate(z: 4)
// This mutation will fail because the mana field is not available in
// the buffer. It should be set when creating the buffer.
if !monster.mutate(mana: 20) {
fatalError("couldn't mutate")
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The term `mutate` is used instead of `set` to indicate that this is a special use case. All mutate functions return a boolean value which is false if the field we're trying to mutate is not available in the buffer.
<br>

View File

@@ -35,6 +35,7 @@ Please select your desired language for our quest:
<input type="radio" name="language" value="lua">Lua</input>
<input type="radio" name="language" value="lobster">Lobster</input>
<input type="radio" name="language" value="rust">Rust</input>
<input type="radio" name="language" value="swift">Swift</input>
</form>
\endhtmlonly
@@ -152,6 +153,9 @@ For your chosen language, please cross-reference with:
<div class="language-rust">
[sample_binary.rs](https://github.com/google/flatbuffers/blob/master/samples/sample_binary.rs)
</div>
<div class="language-swift">
[sample_binary.swift](https://github.com/google/flatbuffers/blob/master/samples/sample_binary.swift)
</div>
## Writing the Monsters' FlatBuffer Schema
@@ -363,6 +367,12 @@ Please be aware of the difference between `flatc` and `flatcc` tools.
./../flatc --rust monster.fbs
~~~
</div>
<div class="language-swift">
~~~{.sh}
cd flatbuffers/samples
./../flatc --swift monster.fbs
~~~
</div>
For a more complete guide to using the `flatc` compiler, please read the
[Using the schema compiler](@ref flatbuffers_guide_using_schema_compiler)
@@ -523,6 +533,21 @@ The first step is to import/include the library, generated files, etc.
~~~
</div>
<div class="language-swift">
~~~{.swift}
/**
// make sure that monster_generated.swift is included in your project
*/
import Flatbuffers
// typealiases for convenience
typealias Monster = MyGame1.Sample.Monster
typealias Weapon = MyGame1.Sample.Weapon
typealias Color = MyGame1.Sample.Color
typealias Vec3 = MyGame1.Sample.Vec3
~~~
</div>
Now we are ready to start building some buffers. In order to start, we need
to create an instance of the `FlatBufferBuilder`, which will contain the buffer
as it grows. You can pass an initial size of the buffer (here 1024 bytes),
@@ -627,6 +652,12 @@ which will grow automatically if needed:
let mut builder = flatbuffers::FlatBufferBuilder::new_with_capacity(1024);
~~~
</div>
<div class="language-swift">
~~~{.swift}
// create a `FlatBufferBuilder`, which will be used to serialize objects
let builder = FlatBufferBuilder(initialSize: 1024)
~~~
</div>
After creating the `builder`, we can start serializing our data. Before we make
our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
@@ -878,6 +909,25 @@ our `orc` Monster, lets create some `Weapon`s: a `Sword` and an `Axe`.
~~~
</div>
<div class="language-swift">
~~~{.swift}
let weapon1Name = builder.create(string: "Sword")
let weapon2Name = builder.create(string: "Axe")
// start creating the weapon by calling startWeapon
let weapon1Start = Weapon.startWeapon(builder)
Weapon.add(name: weapon1Name, builder)
Weapon.add(damage: 3, builder)
// end the object by passing the start point for the weapon 1
let sword = Weapon.endWeapon(builder, start: weapon1Start)
let weapon2Start = Weapon.startWeapon(builder)
Weapon.add(name: weapon2Name, builder)
Weapon.add(damage: 5, builder)
let axe = Weapon.endWeapon(builder, start: weapon2Start)
~~~
</div>
Now let's create our monster, the `orc`. For this `orc`, lets make him
`red` with rage, positioned at `(1.0, 2.0, 3.0)`, and give him
a large pool of hit points with `300`. We can give him a vector of weapons
@@ -1068,6 +1118,16 @@ traversal. This is generally easy to do on any tree structures.
let inventory = builder.create_vector(&[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Name of the Monster.
let name = builder.create(string: "Orc")
// create inventory
let inventory: [Byte] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let inventoryOffset = builder.createVector(inventory)
~~~
</div>
We serialized two built-in data types (`string` and `vector`) and captured
their return values. These values are offsets into the serialized data,
@@ -1212,6 +1272,13 @@ offsets.
let weapons = builder.create_vector(&[sword, axe]);
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Create a FlatBuffer `vector` that contains offsets to the sword and axe
// we created above.
let weaponsOffset = builder.createVector(ofOffsets: [sword, axe])
~~~
</div>
<br>
Note there's additional convenience overloads of `CreateVector`, allowing you
@@ -1348,6 +1415,15 @@ for the `path` field above:
// let path = builder.create_vector(&[&x, &y]);
~~~
</div>
<div class="language-swift">
~~~{.swift}
//
let points = builder.createVector(structs: [MyGame.Sample.createVec3(x: 1, y: 2, z: 3),
MyGame.Sample.createVec3(x: 4, y: 5, z: 6)],
type: Vec3.self)
~~~
</div>
We have now serialized the non-scalar components of the orc, so we
can serialize the monster itself:
@@ -1621,6 +1697,20 @@ can serialize the monster itself:
});
~~~
</div>
<div class="language-swift">
~~~{.swift}
let start = Monster.startMonster(builder)
Monster.add(pos: pos, builder)
Monster.add(hp: 300, builder)
Monster.add(name: name, builder)
Monster.add(inventory: inventoryOffset, builder)
Monster.add(color: .red, builder)
Monster.add(weapons: weaponsOffset, builder)
Monster.add(equippedType: .weapon, builder)
Monster.add(equipped: axe, builder)
var orc = Monster.endMonster(builder, start: start)
~~~
</div>
Note how we create `Vec3` struct in-line in the table. Unlike tables, structs
are simple combinations of scalars that are always stored inline, just like
@@ -1789,6 +1879,13 @@ Here is a repetition these lines, to help highlight them more clearly:
monster_builder.add_equipped(axe.as_union_value()); // Union data
~~~
</div>
<div class="language-swift">
~~~{.swift}
Monster.add(equippedType: .weapon, builder) // Type of union
Monster.add(equipped: axe, builder) // Union data
~~~
</div>
After you have created your buffer, you will have the offset to the root of the
data in the `orc` variable, so you can finish the buffer by calling the
@@ -1884,6 +1981,12 @@ appropriate `finish` method.
builder.finish(orc, None);
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Call `finish(offset:)` to instruct the builder that this monster is complete.
builder.finish(offset: orc)
~~~
</div>
The buffer is now ready to be stored somewhere, sent over the network, be
compressed, or whatever you'd like to do with it. You can access the buffer
@@ -2012,6 +2115,15 @@ like so:
~~~
</div>
<div class="language-swift">
~~~{.swift}
// This must be called after `finish()`.
// `sizedByteArray` returns the finished buf of type [UInt8].
let buf = builder.sizedByteArray
// or you can use to get an object of type Data
let bufData = ByteBuffer(data: builder.data)
~~~
</div>
Now you can write the bytes to a file, send them over the network..
**Make sure your file mode (or transfer protocol) is set to BINARY, not text.**
@@ -2312,6 +2424,16 @@ myGame.Monster monster = new myGame.Monster(data);
~~~
</div>
<div class="language-swift">
~~~{.swift}
// create a ByteBuffer(:) from an [UInt8] or Data()
let buf = // Get your data
// Get an accessor to the root object inside the buffer.
let monster = Monster.getRootAsMonster(bb: ByteBuffer(bytes: buf))
~~~
</div>
If you look in the generated files from the schema compiler, you will see it generated
accessors for all non-`deprecated` fields. For example:
@@ -2419,6 +2541,14 @@ accessors for all non-`deprecated` fields. For example:
~~~
</div>
<div class="language-swift">
~~~{.swift}
let hp = monster.hp
let mana = monster.mana
let name = monster.name // returns an optional string
~~~
</div>
These should hold `300`, `150`, and `"Orc"` respectively.
*Note: The default value `150` wasn't stored in `mana`, but we are still able to retrieve it.*
@@ -2544,6 +2674,15 @@ To access sub-objects, in the case of our `pos`, which is a `Vec3`:
~~~
</div>
<div class="language-swift">
~~~{.swift}
let pos = monster.pos
let x = pos.x
let y = pos.y
let z = pos.z
~~~
</div>
`x`, `y`, and `z` will contain `1.0`, `2.0`, and `3.0`, respectively.
*Note: Had we not set `pos` during serialization, it would be a `NULL`-value.*
@@ -2644,6 +2783,20 @@ FlatBuffers `vector`.
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Get a the count of objects in the vector
let count = monster.inventoryCount
// get item at index 4
let object = monster.inventory(at: 4)
// or you can fetch the entire array
let inv = monster.inventory
// inv[4] should equal object
~~~
</div>
For `vector`s of `table`s, you can access the elements like any other vector,
except your need to handle the result as a FlatBuffer `table`:
@@ -2756,6 +2909,16 @@ except your need to handle the result as a FlatBuffer `table`:
let second_weapon_damage = wep2.damage();
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Get the count of weapon objects
let wepsCount = monster.weaponsCount
let weapon2 = monster.weapons(at: 1)
let weaponName = weapon2.name
let weaponDmg = weapon2.damage
~~~
</div>
Last, we can access our `Equipped` FlatBuffer `union`. Just like when we created
the `union`, we need to get both parts of the `union`: the type and the data.
@@ -2943,6 +3106,17 @@ We can access the type to dynamically cast the data as needed (since the
~~~
</div>
<div class="language-swift">
~~~{.swift}
// Get and check if the monster has an equipped item
if monster.equippedType == .weapon {
let _weapon = monster.equipped(type: Weapon.self)
let name = _weapon.name // should return "Axe"
let dmg = _weapon.damage // should return 5
}
~~~
</div>
## Mutating FlatBuffers
As you saw above, typically once you have created a FlatBuffer, it is read-only
@@ -3046,6 +3220,15 @@ mutators like so:
~~~
</div>
<div class="language-swift">
~~~{.swift}
let monster = Monster.getRootAsMonster(bb: ByteBuffer(bytes: buf))
monster.mutate(hp: 10) // mutates a value in a table
monster.pos.mutate(z: 4) // mutates a value in a struct
monster.mutate(inventory: 6, at index: 0) // mutates a value in an Scalar array
~~~
</div>
We use the somewhat verbose term `mutate` instead of `set` to indicate that this
is a special use case, not to be confused with the default way of constructing
FlatBuffer data.
@@ -3215,5 +3398,7 @@ For your chosen language, see:
<div class="language-rust">
[Use in Rust](@ref flatbuffers_guide_use_rust)
</div>
<div class="language-swift">
[Use in Swift](@ref flatbuffers_guide_use_swift)
</div>
<br>