Skip to content

Instantly share code, notes, and snippets.

View swhitty's full-sized avatar

Simon Whitty swhitty

View GitHub Profile
// Closures cannot be weak, but we can wrap an object and its static factory method that returns an
// instance to create a pseudo weak closure
//
struct Method {
static func weak<T: AnyObject, A>(_ base: T, factory: @escaping (T) -> ((A) -> Void)) -> (A) -> Void {
{ [weak base] in base.map(factory)?($0) }
}
import Foundation
/// Property wrapper that protects the wrapped value with `NSLock`.
/// Non trivial read/writes should access via `projectedValue` which acquires lock
/// and runs the supplied closure against it.
///
/// ```
/// @Locked var count: Int
/// $count { $0 = doSomething($0) }
/// ```
import Combine
/// Subscriber that weakly references its target with a closure.
/// The target is sent to the action when it exists, so the caller can avoid
/// the [weak self] dance.
///
/// ```
/// let binder = Binder(self) { $0.doSomething($1) )
/// ```
///
import Combine
extension Publisher {
/// Binds a publisher to a subscriber with a resulting cancellable
func bind<S: Subscriber>(to subscriber: S) -> AnyCancellable where S.Failure == Failure, S.Input == Output {
let binding = Binding(subscriber)
subscribe(binding)
return AnyCancellable(binding.cancel)
}
}
// Combine.Publishers.ReceiveOn is seriously broken on iOS 13.0–2
// https://forums.swift.org/t/combine-receive-on-runloop-main-loses-sent-value-how-can-i-make-it-work/28631
// Provides fix by using OpenCombine version on iOS 13.0–2 and Combine version for iOS 13.3+
import Combine
import Foundation
@available(iOS, deprecated: 13.3, message: "use receive(on:)")
public extension Publisher {
@swhitty
swhitty / AnyPub.swift
Last active September 17, 2021 23:43
// Publisher type erasure that uses SE-0309 + unsafebitcast instead of the subclass approach of AnyPublisher.
// https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md
// Available in latest toolchains on swift.org
// OpenCombine.AnyPublisher
// https://github.com/OpenCombine/OpenCombine/blob/master/Sources/OpenCombine/AnyPublisher.swift
extension Publisher {
func eraseToAnyPub() -> AnyPub<Output, Failure> {
AnyPub(self)
extension NSAttributedString {
/// Iterates over all embedded `UIImage` instances within the string, updating the configuration
/// to include the provided `UITraitCollection`.
///
func updatingImageAttachments(to traitCollection: UITraitCollection) -> NSAttributedString {
var updatedImages = [(Int, UIImage)]()
enumerateAttributes(in: NSRange(location: 0, length: length), options: .init(rawValue: 0)) { attributes, range, _ in
if let attachment = attributes[.attachment] as? NSTextAttachment,
let image = attachment.image ?? attachment.image(forBounds: .zero, textContainer: nil, characterIndex: 0),
import Combine
extension Publisher where Failure == Never {
/// Converts publisher to AsyncSequence
@available(iOS, deprecated: 15.0, message: "Use publisher.values directly")
var valuesAsync: AsyncPublisher<Self> {
AsyncPublisher(self)
}
}
import Combine
extension Publisher {
/// Converts publisher to AsyncSequence.
@available(iOS, deprecated: 15.0, message: "Use publisher.values directly")
var valuesAsync: AsyncThrowingPublisher<Self> {
AsyncThrowingPublisher(self)
}
}
extension Task where Failure == Error {
// Start a new Task with a timeout. If the timeout expires before the operation is
// completed then the task is cancelled and an error is thrown.
init(priority: TaskPriority? = nil, timeout: TimeInterval, operation: @escaping @Sendable () async throws -> Success) {
self = Task(priority: priority) {
try await withThrowingTaskGroup(of: Success.self) { group -> Success in
group.addTask(operation: operation)
group.addTask {
try await _Concurrency.Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))