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.
@ForyStruct, @ForyEnum, and @ForyUnion to generate serializerstrackRef for reference graphsAny, AnyObject, arbitrary application protocols, AnyHashable, and dynamic containers| Target | Description |
|---|---|
Fory | Core Swift implementation and macro declarations |
ForyMacro | Macro implementation used by Fory model and field macros |
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: "1.6.1") ], 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 @ForyStruct struct User: Equatable { var name: String = "" var age: Int32 = 0 } let fory = Fory() try 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 Fory model macros, register user types, then serialize/deserialize.
import Fory @ForyStruct struct Address: Equatable { var street: String = "" var zip: Int32 = 0 } @ForyStruct 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() try fory.register(Address.self, id: 100) try 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(ref: true, compatible: false)
Shared reference identity is preserved:
import Fory @ForyStruct final class Animal { var name: String = "" required init() {} init(name: String) { self.name = name } } @ForyStruct 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(ref: true) try fory.register(Animal.self, id: 200) try 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:
@ForyStruct 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 Protocol existentialsAnyHashable[Any][String: Any][Int32: Any][AnyHashable: Any]If dynamic payloads contain user-defined concrete types, register those types before serialization/deserialization. Any and AnyObject roots use direct APIs. Arbitrary application protocols and dynamic carriers explicitly select DynamicSerializer<T> and the applicable carrier serializers.
import Fory @ForyStruct struct DynamicAddress { var street: String = "" var zip: Int32 = 0 } let fory = Fory() try fory.register(DynamicAddress.self, id: 410) let dynamic: Any = DynamicAddress(street: "main", zip: 94107) let dynamicData = try fory.serialize(dynamic) let dynamicOutput: Any = try fory.deserialize(dynamicData) let payload: [String: Any] = [ "id": Int32(7), "name": "alice", "addr": DynamicAddress(street: "main", zip: 94107), ] typealias PayloadSerializer = DictionarySerializer< String, DynamicSerializer<Any> > let data = try fory.serialize( payload, with: PayloadSerializer.self ) let decoded = try fory.deserialize( data, with: PayloadSerializer.self ) assert(decoded["id"] as? Int32 == 7)
Null decoding semantics:
Any null is represented as ForyAnyNullValueAnyObject null is represented as NSNullAnyHashable dynamic null key is represented as AnyHashable(ForyAnyNullValue())Use compatible mode to evolve schemas between peers.
import Fory @ForyStruct struct PersonV1 { var name: String = "" var age: Int32 = 0 var address: String = "" } @ForyStruct struct PersonV2 { var name: String = "" var age: Int32 = 0 var phone: String? = nil } let writer = Fory() try writer.register(PersonV1.self, id: 1) let reader = Fory() try 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 @ForyStruct 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 |
Nested collection fields can carry the same integer encoding metadata through field type hints:
@ForyStruct struct NestedMetrics { @ListField(element: .encoding(.fixed)) var values: [Int32?] = [] @SetField(element: .encoding(.fixed)) var ids: Set<UInt32?> = [] @MapField(value: .list(element: .encoding(.fixed))) var grouped: [String: [Int32?]] = [:] }
For List fields with non-null fixed-width integer elements, Swift emits the corresponding Fory primitive packed-array type. Set fields remain Fory sets, even when their element metadata uses fixed integer encoding.
Date maps to Fory timestamp. LocalDate maps to Fory date and exposes epochDay, init(epochDay:), fromEpochDay(_:), init(year:month:day:), year, month, day, toEpochDay(), init(utcDate:), and toUTCDate().
Use @ForyEnum for C-style enums and @ForyUnion for associated-value enums.
import Fory @ForyEnum enum Color: Equatable { case red case green case blue } @ForyUnion enum StringOrLong: Equatable { @ForyUnknownCase case unknown(UnknownCase) @ForyCase(id: 0) case text(String) @ForyCase(id: 1) case number(Int64) } let fory = Fory(compatible: false) try fory.register(Color.self, id: 300) try 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"))
Define an external structural serializer when the value type belongs to another module:
@ForyStruct(target: ThirdParty.User.self) struct UserSerializer { var name: String var age: UInt32 } try fory.register(UserSerializer.self, id: 400) let data = try fory.serialize(user, with: UserSerializer.self) let decoded = try fory.deserialize(data, with: UserSerializer.self)
Root carriers compose recursively:
let data = try fory.serialize( users, with: ArraySerializer<UserSerializer>.self )
See ../docs/object-serialization/swift/external-types.md.
A type that implements Serializer with Target == Self uses ordinary roots, generated fields, and carriers without with:. An application may give an external type one retroactive self-target conformance, but that conformance is process-global and must be the single binding for the (Target, Serializer) pair.
A separate serializer whose Target is another type is selected explicitly at every required root, field, or carrier child. Use separate serializers for public libraries and for multiple or alternative implementations. See ../docs/object-serialization/swift/custom-serializers.md.
Recommended preset:
let fory = Fory()
Type registration can be ID-based or name-based:
try fory.register(MyType.self, id: 100) try fory.register(MyType.self, name: "com.example.MyType")
Cross-language rules:
trackRef=false for value-only payloads to avoid reference-table overheadFory instance and register types once per process/service lifecyclecompatible=false only when every reader and writer always uses the same schema and you want faster serialization and smaller sizeRun 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.