{"id":32600919,"url":"https://github.com/apparata/approach","last_synced_at":"2026-04-15T11:38:04.082Z","repository":{"id":145005071,"uuid":"151164452","full_name":"apparata/Approach","owner":"apparata","description":"Approach is a small Swift framework for client/server message passing between apps over the network.","archived":false,"fork":false,"pushed_at":"2025-06-08T12:33:23.000Z","size":57,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-30T07:37:38.374Z","etag":null,"topics":["ios","macos","swift","swift-package"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"0bsd","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apparata.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-10-01T21:39:10.000Z","updated_at":"2025-07-14T11:35:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"1dc51d62-9f96-4376-b350-ebb25b47252b","html_url":"https://github.com/apparata/Approach","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/apparata/Approach","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apparata%2FApproach","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apparata%2FApproach/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apparata%2FApproach/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apparata%2FApproach/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apparata","download_url":"https://codeload.github.com/apparata/Approach/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apparata%2FApproach/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31840096,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T11:29:19.690Z","status":"ssl_error","status_checked_at":"2026-04-15T11:29:19.171Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","macos","swift","swift-package"],"created_at":"2025-10-30T07:23:15.431Z","updated_at":"2026-04-15T11:38:04.074Z","avatar_url":"https://github.com/apparata.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Approach\n\n**Approach** is a Swift library for message passing between apps over the network. It supports both client and server roles, with optional Bonjour service discovery for zero-configuration local networking.\n\n## License\n\nApproach is available under the BSD Zero Clause License. One advantage of this license is that it does not require attribution. See the [LICENSE](LICENSE) file in the repository for details.\n\n## Requirements\n\nMinimum OS version requirements:\n\n- iOS 16\n- tvOS 16\n- visionOS 1\n- macOS 13 Ventura\n\n## Features\n\n- TCP message transmission with structured payloads\n- Optional Bonjour advertisement and discovery\n- Codable-based data and metadata messaging\n- Asynchronous message reception using delegate callbacks\n- Configurable logging support for diagnostics\n\n## Installation\n\nTo use a dependency of another Swift package, add the following dependency to your `Package.swift` file:\n\n```swift\n.package(url: \"https://github.com/yourusername/approach.git\", from: \"x.y.z\")\n```\n\n- Replace `x.y.z` with the release version.\n- Include `Approach` in your target dependencies.\n\n## Example\n\n### Define Messages\n\nThe messages to pass between apps are typically defined as `Codable` enums. This allows structured, type-safe messaging.\n\n```swift\nenum GeneralMessage: Codable {\n    case helloWorld\n    \n    /// Client sends this to the server to ask about meaning of life\n    case whatsTheMeaningOfLife\n    \n    /// Server sends this to the client with a number as the meaning of life\n    case meaningOfLife(Int)\n}\n```\n\nEach message is accompanied by app specific metadata. The metadata should typically contain the type of the message, to inform the receiver about how the message should be decoded.\n\n```swift\nenum AppMessageMetadata: String, Codable {\n\n    /// Message should be decoded as `GeneralMessage`\n    case general\n}\n```\n\n### Encoding / Decoding Messages\n\nFor the purposes of this example, we will add encoding/decoding helpers to the message types, to make the example code more readable.\n\n```swift\nextension GeneralMessage {\n    func encode() -\u003e Data {\n        try! JSONEncoder().encode(self)\n    }\n\n    static func decode(from data: Data) -\u003e GeneralMessage {\n        try! JSONDecoder().decode(GeneralMessage.self, from: data)\n    }\n}\n\nextension AppMessageMetadata {\n    func encode() -\u003e Data {\n        try! JSONEncoder().encode(self)\n    }\n\n    static func decode(from data: Data) -\u003e AppMessageMetadata {\n        try! JSONDecoder().decode(AppMessageMetadata.self, from: data)\n    }\n}\n```\n\n### Setting up the Server\n\nThis example shows how to set up a server that advertises its presence on the local network using Bonjour, listens for incoming clients, performs connection handshakes, and handles structured messages.\n\n```swift\nimport Foundation\nimport Approach\n\nclass ExampleServer: MessageServiceDelegate, RemoteMessageClientDelegate {\n\n    private let service: MessageService\n    \n    init() throws {\n        // \"MyService\" is the Bonjour name to use for automatic discovery.\n        service = try MessageService(name: \"MyService\")\n        service.delegate = self\n    }\n    \n    @discardableResult\n    func start() -\u003e Self {\n        service.start()\n        return self\n    }\n\n    func messageService(\n        _ service: MessageService,\n        clientDidConnect remoteClient: RemoteMessageClient\n    ) {\n        remoteClient.delegate = self\n    }\n\n    func clientDidStartSession(_ remoteClient: RemoteMessageClient) {\n        print(\"Session started with remote client.\")\n    }\n\n    func client(\n        _ remoteClient: RemoteMessageClient,\n        didReceiveMessage data: Data,\n        metadata: Data\n    ) {\n        let metadata = AppMessageMetadata.decode(from: metadata)\n        guard metadata == .general else {\n            print(\"Received unexpected metadata\")\n            return\n        }\n\n        print(\"Server received a message of type: \\(metadata.rawValue)\")\n\n        let message = GeneralMessage.decode(from: data)\n\n        switch message {\n        case .helloWorld:\n            print(\"Client says hello. Say hello back.\")\n            sendMessage(.helloWorld, to: remoteClient)\n        case .whatsTheMeaningOfLife:\n            print(\"Client wants to know what the meaning of life is. It is 42.\")\n            sendMessage(.meaningOfLife(42), to: remoteClient)\n        default:\n            // Server does not care about e.g. the meaningOfLife message.\n            break\n        }\n    }\n    \n    private func sendMessage(\n        _ message: GeneralMessage,\n        to remoteClient: RemoteMessageClient\n    ) {\n        remoteClient.sendMessage(\n            data: message.encode(),\n            metadata: AppMessageMetadata.general.encode()\n        )\n    }\n}\n```\n\nIf you are starting the server from a command line tool, you probably want to start a `RunLoop`, or the process will terminate without waiting for any messages.\n\n```swift\nlet exampleServer = ExampleServer().start()\nRunLoop.main.run()\n```\n\n### Setting up the Client (Bonjour-based)\n\nThis example shows how to set up a client that looks for advertising servers on the local network using Bonjour, connects to the server, and sends structured messages.\n\n```swift\nimport Foundation\nimport Approach\n\nclass ExampleClient: MessageClientDelegate {\n\n    private let client: MessageClient\n\n    init() {\n        // \"MyService\" is the Bonjour name to use for automatic discovery.\n        client = MessageClient(serviceName: \"MyService\")\n        client.delegate = self\n    }\n    \n    @discardableResult\n    func connect() -\u003e Self {\n        client.connect()\n        return self\n    }\n\n    func clientDidStartSession(_ client: MessageClient) {\n        // Send a couple of messages once the session has started.\n        sendMessage(.helloWorld, to: client)\n        sendMessage(.whatsTheMeaningOfLife)\n    }\n\n    func client(\n        _ client: MessageClient,\n        didReceiveMessage data: Data,\n        metadata: Data\n    ) {\n        let metadata = AppMessageMetadata.decode(from: metadata)\n        guard metadata == .general else {\n            print(\"Invalid server message\")\n            return\n        }\n\n        let message = GeneralMessage.decode(from: data)\n\n        print(\"Client received: \\(message)\")\n    }\n\n    func client(_ client: MessageClient, didFailSessionWithError error: Error) {\n        print(\"Connection failed: \\(error)\")\n    }\n    \n    func sendMessage(_ message: GeneralMessage, to client: MessageClient) {\n        client.sendMessage(\n            data: message.encode(),\n            metadata: AppMessageMetadata.general.encode()\n        )\n    }\n}\n```\n\nIf you are starting the client from a command line tool, you probably want to start a `RunLoop`, or the process will terminate without waiting for any messages.\n\n```swift\nlet client = ExampleClient().connect()\nRunLoop.main.run()\n```\n\n### Setting up the Client (Direct IP Address)\n\nTo connect to a known server via IP and port, replace the `init` with:\n\n```swift\ninit() {\n    // \"MyService\" is the Bonjour name to use for automatic discovery.\n    client = MessageClient(host: \"192.168.1.10\", port: 4242)\n}\n```\n\n### Logging\n\nApproach includes optional logging hooks to help with debugging and\ndevelopment. You can assign a closure to `MessageClient.log` or\n`RemoteMessageClient.log` to receive diagnostic output.\n\n**Example**\n\n```swift\nMessageClient.log = { client, message in\n    print(\"[Client][\\(client)] \\(message)\")\n}\n\nRemoteMessageClient.log = { client, message in\n    print(\"[Server][\\(client.id)] \\(message)\")\n}\n```\n\nUse this to observe connection state changes, message events, and internal\nbehavior during development.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapparata%2Fapproach","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapparata%2Fapproach","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapparata%2Fapproach/lists"}