Apache Foryβ’ is a blazing-fast multi-language serialization framework.
The Swift implementation provides high-performance object graph serialization with macro-based code generation, schema evolution support, and xlang interoperability.
@ForyObject to generate serializers for structs, classes, and enumstrackRef for reference graphsAny, AnyObject, any Serializer, AnyHashable, and dynamic containers| Target | Description |
|---|---|
Fory | Core Swift runtime and macro declarations |
ForyMacro | Macro implementation used by @ForyObject and @ForyField |
ForyXlangTests | Executable used by Java-driven xlang integration tests |
ForyTests | Swift unit tests |
Package.swift:
dependencies: [ .package(url: "https://github.com/apache/fory.git", from: "0.16.0") ], targets: [ .target( name: "MyApp", dependencies: [ .product(name: "Fory", package: "fory") ] ) ]
Swift Package Index documentation for the Swift target:
https://swiftpackageindex.com/apache/fory/main/documentation/fory
import Fory @ForyObject struct User: Equatable { var name: String = "" var age: Int32 = 0 } let fory = Fory() fory.register(User.self, id: 1) let input = User(name: "alice", age: 30) let data = try fory.serialize(input) let output: User = try fory.deserialize(data) assert(input == output)
var out = Data() try fory.serialize(input, to: &out) let buffer = ByteBuffer(data: out) let output2: User = try fory.deserialize(from: buffer) assert(output2 == input)
Fory is the fastest option for single-threaded reuse. Keep one instance per thread.
Use @ForyObject, register user types, then serialize/deserialize.
import Fory @ForyObject struct Address: Equatable { var street: String = "" var zip: Int32 = 0 } @ForyObject struct Person: Equatable { var id: Int64 = 0 var name: String = "" var nickname: String? = nil var tags: Set<String> = [] var scores: [Int32] = [] var addresses: [Address] = [] var metadata: [Int8: Int32?] = [:] } let fory = Fory() fory.register(Address.self, id: 100) fory.register(Person.self, id: 101) let person = Person( id: 42, name: "Alice", nickname: nil, tags: ["swift", "xlang"], scores: [10, 20, 30], addresses: [Address(street: "Main", zip: 94107)], metadata: [1: 100, 2: nil] ) let bytes = try fory.serialize(person) let decoded: Person = try fory.deserialize(bytes) assert(decoded == person)
Enable reference tracking for class/reference graphs:
let fory = Fory(xlang: true, trackRef: true, compatible: false)
Shared reference identity is preserved:
import Fory @ForyObject final class Animal { var name: String = "" required init() {} init(name: String) { self.name = name } } @ForyObject final class AnimalPair { var first: Animal? = nil var second: Animal? = nil required init() {} init(first: Animal? = nil, second: Animal? = nil) { self.first = first self.second = second } } let fory = Fory(xlang: true, trackRef: true) fory.register(Animal.self, id: 200) fory.register(AnimalPair.self, id: 201) let shared = Animal(name: "cat") let input = AnimalPair(first: shared, second: shared) let data = try fory.serialize(input) let decoded: AnimalPair = try fory.deserialize(data) assert(decoded.first === decoded.second)
For cyclic graphs, use weak on at least one edge to avoid ARC leaks:
@ForyObject final class Node { var value: Int32 = 0 weak var next: Node? = nil required init() {} }
Top-level and field-level dynamic serialization is supported for:
AnyAnyObjectany SerializerAnyHashable[Any][String: Any][Int32: Any][AnyHashable: Any]If dynamic payloads contain user-defined concrete types, register those types before serialization/deserialization.
import Fory @ForyObject struct DynamicAddress { var street: String = "" var zip: Int32 = 0 } let fory = Fory() fory.register(DynamicAddress.self, id: 410) let payload: [String: Any] = [ "id": Int32(7), "name": "alice", "addr": DynamicAddress(street: "main", zip: 94107), ] let data = try fory.serialize(payload) let decoded: [String: Any] = try fory.deserialize(data) assert(decoded["id"] as? Int32 == 7)
Null decoding semantics:
Any null is represented as ForyAnyNullValueAnyObject null is represented as NSNullUse compatible mode to evolve schemas between peers.
import Fory @ForyObject struct PersonV1 { var name: String = "" var age: Int32 = 0 var address: String = "" } @ForyObject struct PersonV2 { var name: String = "" var age: Int32 = 0 var phone: String? = nil } let writer = Fory(xlang: true, compatible: true) writer.register(PersonV1.self, id: 1) let reader = Fory(xlang: true, compatible: true) reader.register(PersonV2.self, id: 1) let v1 = PersonV1(name: "alice", age: 30, address: "main st") let bytes = try writer.serialize(v1) let v2: PersonV2 = try reader.deserialize(bytes) assert(v2.name == "alice") assert(v2.age == 30) assert(v2.phone == nil)
Compatible mode supports:
Not supported:
Int32 to String)Use @ForyField(encoding:) to control integer wire encoding.
import Fory @ForyObject struct Metrics { @ForyField(encoding: .fixed) var u32Fixed: UInt32 = 0 @ForyField(encoding: .tagged) var u64Tagged: UInt64 = 0 }
Supported combinations:
| Swift type | Supported encodings |
|---|---|
Int32, UInt32 | .varint, .fixed |
Int64, UInt64, Int, UInt | .varint, .fixed, .tagged |
@ForyObject supports C-style enums and associated-value enums.
import Fory @ForyObject enum Color: Equatable { case red case green case blue } @ForyObject enum StringOrLong: Equatable { case text(String) case number(Int64) } let fory = Fory(xlang: true, compatible: false) fory.register(Color.self, id: 300) fory.register(StringOrLong.self, id: 301) let a = try fory.serialize(Color.green) let b = try fory.serialize(StringOrLong.text("hello")) let color: Color = try fory.deserialize(a) let value: StringOrLong = try fory.deserialize(b) assert(color == .green) assert(value == .text("hello"))
For types that should not use @ForyObject, implement Serializer manually and register the type. See ../docs/guide/swift/custom-serializers.md for a complete example.
Recommended xlang preset:
let fory = Fory(xlang: true, trackRef: false, compatible: true)
Type registration can be ID-based or name-based:
fory.register(MyType.self, id: 100) try fory.register(MyType.self, namespace: "com.example", name: "MyType")
Cross-language rules:
Swift runtime currently exposes object graph serialization APIs (Fory.serialize / Fory.deserialize). Row-format APIs are not exposed yet in Swift.
trackRef=false for value-only payloads to avoid reference-table overheadFory instance and register types once per process/service lifecyclecompatible=false) when strict schema parity is guaranteedRun Swift tests:
cd swift ENABLE_FORY_DEBUG_OUTPUT=1 swift test
Run Java-driven Swift xlang tests:
cd java/fory-core ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest
Licensed under the Apache License, Version 2.0. See LICENSE.
Contributions are welcome. See CONTRIBUTING.md.