Show HN:Pledge——一个轻量级的Swift响应式框架(无Rx额外开销)
Show HN: Pledge – A Lightweight Reactive Framework for Swift (No Rx Overhead)

原始链接: https://github.com/gokulnair2001/Pledge

Pledge是一个轻量级、线程安全的Swift响应式编程框架,简化了状态管理和事件传播。它利用观察者模式,通过可观察的值来通知订阅者变化。 其主要特性包括线程安全、基于优先级的通知、可自定义的交付队列、批量更新、速率限制(节流和去抖)、函数式操作符(map、filter等)以及通过`PLGlobalStore`进行全局状态管理。 `PLObservable`是核心组件,代表一个可观察的值容器。`PLGlobalStore`充当一个集中式、轻量级的状态存储库。订阅者可以指定交付队列、优先级,并使用`map`、`filter`、`throttle`和`debounce`等转换操作。批量更新优化了通知。 Pledge通过组合可观察对象来方便派生状态的创建,并通过观察网络状态来简化异步操作的处理。它采用MIT许可证,并可通过Swift Package Manager轻松集成。

Hacker News上的一篇名为“Show HN”的帖子介绍了Pledge,一个轻量级的Swift响应式框架,作者为gokulnair2001。帖子链接到该项目的GitHub仓库。发布一小时内,帖子获得了3个赞和2条评论。 评论者singpolyma3质疑Pledge的功能是否已被SwiftUI覆盖。gokulnair2001回应道,提供了Pledge的介绍、性能测试、文档和使命宣言链接,以进一步解释该框架的价值。帖子还包含标准的Hacker News页脚链接(指南、常见问题解答等)以及一个醒目的旧金山“AI创业学校”广告。

原文

PledgeBanner

A thoughtfully designed reactive programming framework in Swift

Pledge is a lightweight, thread-safe reactive programming framework for Swift that simplifies state management, event propagation and balances power with simplicity in your applications. While other frameworks force you to learn complex concepts and operators, Pledge focuses on solving the real problems developers face daily:

Pledge provides a clean, flexible way to implement the observer pattern in Swift applications. It enables you to create observable values that notify subscribers of changes, with powerful features like:

  • Thread-safe implementation
  • Priority-based notifications
  • Customizable delivery queues
  • Batch updates
  • Rate limiting (throttling and debouncing)
  • Functional operators (map, filter, etc.)
  • Global state management

Add the following to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/gokulnair2001/Pledge.git", from: "1.0.0")
]

PLObservable is the heart of Pledge, representing a thread-safe container for a value that can be observed for changes.

// Create an observable string
let messageObservable = PLObservable("Hello")

// Subscribe to changes
let subscription = messageObservable.subscribe { newMessage in
    print("Message changed to: \(newMessage)")
}

// Update the value
messageObservable.setValue("Hello World")

PLGlobalStore provides a centralized repository for observables, acting as a lightweight state management solution.

// Access shared instance
let store = PLGlobalStore.shared

// Get or create an observable
let userNameObservable = store.string(for: "userName", defaultValue: "Guest")

// Subscribe to changes
userNameObservable.subscribe { name in
    print("User name is now: \(name)")
}

// Update from anywhere in your app
PLGlobalStore.shared.string(for: "userName").setValue("John")

HOW

The diagram above illustrates the flow of data in Pledge:

  1. An observable holds a value and maintains a list of subscribers
  2. When the value changes, all subscribers are notified in priority order
  3. Subscribers can perform transformers and specify delivery queues for thread-safety
  4. Optional rate limiting can control notification frequency
// Initialize with a value
let counter = PLObservable(0)
let isEnabled = PLObservable(true)
let userData = PLObservable(["name": "Guest", "role": "User"])
// Basic subscription
let subscription = observable.subscribe { newValue in
    print("Value changed to: \(newValue)")
}

// Unsubscribe when no longer needed
observable.unsubscribe(subscription)

// Remove all subscribers
observable.removeAllSubscribers()
// Deliver on a specific queue
observable.deliver(on: myCustomQueue).subscribe { value in
    // This closure runs on myCustomQueue
}

// Deliver on the main queue
observable.deliverOnMain().subscribe { value in
    // This closure runs on the main queue
}

// Set subscription priority
observable.withPriority(.high).subscribe { value in
    // High priority subscribers are notified first
}
// Set a new value and notify subscribers
observable.setValue(newValue)

// Set a value without notification
observable.setValue(newValue, notify: false)

// Trigger notification with current value
observable.notifyObservers()
// Begin batch updates
observable.beginUpdates()

// Make multiple changes
observable.setValue(1)
observable.setValue(2)
observable.setValue(3)

// End batch updates - only sends one notification
observable.endUpdates()
// Throttle: limit to one notification per 0.5 seconds
observable.throttle(for: 0.5).subscribe { value in
    // Called at most once per 0.5 seconds
}

// Debounce: wait until updates pause for 0.3 seconds
observable.debounce(for: 0.3).subscribe { value in
    // Called after 0.3 seconds of no updates
}
// Map values to a different type
let stringCounter = counter.map { "Count: \($0)" }

// Flat-map to another observable
let userDetails = userIdObservable.flatMap { userId in
    return fetchUserDetails(userId)
}

// Unwrap optional values
let optionalValue = PLObservable<String?>("test")
let unwrapped = optionalValue.compactMap()
// Only emit values that pass a predicate
let evenNumbers = counter.filter { $0 % 2 == 0 }

// Skip the first N emissions
let skipFirst = counter.skip(2)

// Only emit when value changes
let distinct = values.distinctUntilChanged()
// Merge two observables of the same type
let allEvents = userEvents.merge(systemEvents)

// Combine latest values from two observables
let credentials = username.zip(password)
// Get/create typed observables
let counter = PLGlobalStore.shared.integer(for: "counter")
let userName = PLGlobalStore.shared.string(for: "userName")
let settings = PLGlobalStore.shared.dictionary(for: "settings")
let items = PLGlobalStore.shared.array(for: "items")
let isEnabled = PLGlobalStore.shared.boolean(for: "isEnabled")

// Remove specific observable
PLGlobalStore.shared.removeObservable(for: "counter")

// Clear all observables
PLGlobalStore.shared.removeAllObservables()
let username = PLObservable("")
let password = PLObservable("")
let isFormValid = PLObservable(false)

// Create derived state
let isUsernameValid = username.map { $0.count >= 3 }
let isPasswordValid = password.map { $0.count >= 8 }

// Combine validations
isUsernameValid.subscribe { usernameValid in
    isPasswordValid.subscribe { passwordValid in
        isFormValid.setValue(usernameValid && passwordValid)
    }
}

// React to form validity
isFormValid.subscribe { valid in
    submitButton.isEnabled = valid
}
enum NetworkState {
    case idle, loading, success(Data), error(Error)
}

let networkState = PLObservable<NetworkState>(.idle)

// Handle different states
networkState.subscribe { state in
    switch state {
    case .idle:
        // Hide indicators
    case .loading:
        // Show loading spinner
    case .success(let data):
        // Update UI with data
    case .error(let error):
        // Show error message
    }
}

// Function to load data
func fetchData() {
    networkState.setValue(.loading)
    
    apiClient.fetchData { result in
        switch result {
        case .success(let data):
            networkState.setValue(.success(data))
        case .failure(let error):
            networkState.setValue(.error(error))
        }
    }
}
let searchQuery = PLObservable("")

// Throttle to avoid excessive API calls
searchQuery.throttle(for: 0.3).subscribe { query in
    if !query.isEmpty {
        performSearch(query)
    }
}

Pledge is available under the MIT license. See the LICENSE file for more info.

联系我们 contact @ memedata.com