mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-02 20:15:34 +00:00
[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:
committed by
Wouter van Oortmerssen
parent
55686100aa
commit
04d80f255d
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user