Skip to content

Instantly share code, notes, and snippets.

View rnapier's full-sized avatar

Rob Napier rnapier

View GitHub Profile
@rnapier
rnapier / OrderedTasks.swift
Created June 19, 2024 19:18
Ordering async tasks
// Built with Swift 6 (Xcode 16b1)
actor Accumulator {
static let shared = Accumulator()
var values: [String] = []
// A couple of approaches, neither of which works.
func add(value: String) { values.append(value) }
nonisolated func addSync(value: String) { Task { await add(value: value) } }
@rnapier
rnapier / Mutex.swift
Last active June 8, 2024 05:43
Mutex backport
// Version of Swift 6 Mutex, with minimal API (just withLock), portable to at least iOS 15, probably much earlier.
// I cannot yet promise it's actually correct.
@frozen
public struct Mutex<Value> {
private let buffer: ManagedBuffer<Value, os_unfair_lock>
public init(_ initialValue: Value) {
buffer = ManagedBuffer<Value, os_unfair_lock>.create(minimumCapacity: 1) { _ in initialValue }
buffer.withUnsafeMutablePointerToElements { lockPtr in
@rnapier
rnapier / MainActorRun.swift
Last active September 18, 2024 05:13
Regarding MainActor.run
// In regards to https://mastodon.social/@mattiem/112285978801305971
// MainActor class with synchronous methods
@MainActor final class M {
func methodA() {}
func methodB() {}
}
// Actor that relies on M.
actor A {
@rnapier
rnapier / SelectorNotifiction.swift
Last active September 26, 2024 07:59
Musings on Notifications and Actors
/// Some exploration into how selector-based notification interact with actors.
///
/// Or in the words of Brent Simmons (@[email protected]),
/// "Selector-based Notification Observers Are Actually Good"
/// Overall, I'm reasonably convinced, in that it avoids the headaches of `deinit` in actors.
/// However, Combine-based observation is also good at this, so I don't yet have a strong opinion
/// about old-school selectors vs Combine beyond my usual nervousness around Combine.
/// Whether "I'd like to reduce Combine" is more or less powerful than "I'd like to reduce @objc"
/// is yet to be seen.
@rnapier
rnapier / DefaultValueConcurrency.swift
Last active April 12, 2024 01:30
Somewhat surprising impact of non-Sendable default values
// With Strict Concurrency
class C {}
struct S {
func f() {
Task { // Surprisingly need `@MainActor in` here to make this correct
await g() // Warning: Passing argument of non-sendable type 'C' into main actor-isolated context may introduce data races
}
}
@rnapier
rnapier / TestVO.swift
Created July 31, 2023 20:04
Demonstration of VoiceOver bug
// Demonstration of FB12811151
// On some devices (so far we haven't found any newer than an 2020 SE2), VoiceOver will allow you to select
// the link by swiping down, and then will immediately re-select the entire text. The user generally cannot
// follow the link because it snaps back too quickly.
//
// On one iPhone 8, this did not reproduce until the device went to sleep, then it reliably reproduced.
import SwiftUI
let text = try! AttributedString(markdown: "Here is a link to [ArchiveOrg](https://archive.org). It should be selectable with the rotor without jumping back to the whole text.")
import Foundation
public class Disposable {
private var isDisposed = false
private let _dispose: () -> Void
public func dispose() {
if !isDisposed {
_dispose()
isDisposed = true
}
@rnapier
rnapier / GridView.swift
Last active September 27, 2020 20:32
GridView that makes me cry
// This GridView makes me cry. It is recreating an HTML-style bordered table, sized to
// its data, with a header. It requires a GeometryReader and Preferences, which might
// be unavoidable, but it also requires a *horrible* DispatchQueue.main.async in updateMaxValue.
// This means it doesn't work in Previews, and completely breaks the idea of "declarative" UI.
import SwiftUI
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
struct LengthLimitedTextField: UIViewRepresentable {
var title: String
@Binding var text: String
var maxLength: Int
var onCommit: () -> Void
init(_ title: String, text: Binding<String>, maxLength: Int = 255, onCommit: @escaping () -> Void) {
self.title = title
self._text = text
self.maxLength = maxLength
// Based on https://twitter.com/ravibastola/status/1249555595285291008?s=20
extension Bundle {
// This is synchronous, which is bad if called on the main queue.
func decodeJSON<T: Decodable>(_ type: T.Type = T.self, from filename: String) throws -> T {
guard let path = self.url(forResource: filename, withExtension: nil) else {
throw NSError(domain: NSCocoaErrorDomain,
code: CocoaError.fileNoSuchFile.rawValue,
userInfo: [NSFilePathErrorKey: filename])
}