Files
LCEssentials/Documentation/Extensions.md

54 KiB
Raw Blame History

LCEssentials — Extensions

Foundation, value-type, string, collection, numeric, date, and crypto helpers, plus the LCEssentials namespace itself. UIKit extensions live in UIKit.md; SwiftUI helpers in SwiftUI.md.

Every section is a collapsible block — click a heading to expand it.

Contents


API & Networking

API is an actor wrapping URLSession for typed JSON requests and multipart uploads. This is a summary — the full guide is in API.md (all parameters, error model, client certificates, custom body types, and the rationale vs. a hand-rolled URLSession).

API — typed async requests & uploads

API.shared

The shared actor instance. Every call is await; nothing runs on the main thread unless you hop there yourself.

struct User: Decodable, Sendable { let id: Int; let name: String }

let user: User = try await API.shared.request(
    url: "https://api.example.com/users/{id}",
    method: .get,
    pathParams: ["id": "42"]
)

request(url:method:body:pathParams:headers:debug:timeoutInterval:networkServiceType:persistConnection:)

Sends a request and decodes the JSON response into T: Decodable & Sendable. Non-2xx responses throw an NSError whose code is the HTTP status and whose localizedFailureReason is the response body. When T == String the raw body is returned without JSON decoding.

struct CreateUser: Encodable, Sendable { let name: String }

// JSON body
let created: User = try await API.shared.request(
    url: "https://api.example.com/users",
    method: .post,
    body: jsonBody(CreateUser(name: "Ana"))
)

// form-url-encoded body
let token: Token = try await API.shared.request(
    url: "https://api.example.com/oauth/token",
    method: .post,
    body: .form(["grant_type": "password", "username": "ana"])
)

upload(url:method:form:pathParams:headers:debug:timeoutInterval:networkServiceType:) and its onProgress: overload

Uploads a multipart/form-data body serialised to a temp file (removed afterwards) and streamed from disk, so large files never fully load into memory.

var form = MultipartForm()
form.field("caption", "Sunset")
form.file("photo", data: jpegData, filename: "p.jpg")
form.file("video", url: localVideoURL)          // streamed from disk

let result: UploadResult = try await API.shared.upload(
    url: "https://api.example.com/media",
    form: form,
    onProgress: { fraction in
        Task { @MainActor in progressView.progress = Float(fraction) }
    }
)

Body types — HTTPBody

jsonBody(_:) wraps any Encodable & Sendable; FormURLEncodedBody (a.k.a. .form([:])) percent-escapes every value and never drops one; RawBody lets you supply the bytes and Content-Type yourself. Conform your own type to HTTPBody and API accepts it with no change.

struct CSVBody: HTTPBody {
    let rows: [[String]]
    func encoded() throws -> (data: Data, contentType: String) {
        let text = rows.map { $0.joined(separator: ",") }.joined(separator: "\n")
        return (Data(text.utf8), "text/csv; charset=UTF-8")
    }
}

await API.shared.setupCertification(certData:password:)

Registers a client certificate (.p12) for mutual-TLS on subsequent requests.

let p12 = try Data(contentsOf: certURL)
await API.shared.setupCertification(certData: p12, password: "cert-pw")

Strings & Text

String — validation, parsing, formatting, masks, HTML, dates

URLs

var isValidUrl: Bool / var isValidHttpsUrl: Bool / var isValidHttpUrl: Bool

isValidUrl is true when URL(string:) succeeds; the other two additionally require the https / http scheme.

"https://google.com".isValidUrl        // true
"https://google.com".isValidHttpsUrl   // true
"http://google.com".isValidHttpsUrl    // false

var urlEncoded: String / var urlDecoded: String

Percent-encode (host-allowed set) / decode. urlDecoded returns the original string when it is not encoded.

"it's easy".urlEncoded       // "it's%20easy"
"it's%20easy".urlDecoded     // "it's easy"

mutating func urlEncode() -> String / mutating func urlDecode() -> String

In-place variants; also return the new value (@discardableResult).

var s = "a b"; s.urlEncode()   // s == "a%20b"

func stringByAddingPercentEncodingForRFC3986() -> String

Percent-encodes for use as a query key/value, escaping ;/?:@&=+$, and space.

"a&b=c".stringByAddingPercentEncodingForRFC3986()   // "a%26b%3Dc"

var url: String?

First URL detected inside the string (via NSDataDetector), or nil.

"visit www.site.com.br now".url   // "www.site.com.br"

var toURL: NSURL?

NSURL(string:) wrapper.

"https://x.com".toURL   // NSURL

Validation

var isEmail: Bool

Regex check for a syntactically valid email (TLD 220 chars).

"user@example.com".isEmail   // true
"nope".isEmail               // false

var isCPF: Bool

Validates a Brazilian CPF including both check digits. Strips formatting first (onlyNumbers), requires 11 digits.

"123.456.789-09".isCPF   // true when the check digits match

var isValidCNPJ: Bool

Validates a Brazilian CNPJ (14 digits, both check digits, rejects all-same-digit).

"11.222.333/0001-81".isValidCNPJ   // true when valid

var isAlphabetic: Bool

Letters only, no digits.

"abc".isAlphabetic     // true
"123abc".isAlphabetic  // false

var isAlphaNumeric: Bool

Contains at least one letter and one digit and nothing else — handy for password rules.

"123abc".isAlphaNumeric   // true
"abc".isAlphaNumeric      // false

var isHTML: Bool

True if the string contains an HTML tag.

"<b>hi</b>".isHTML   // true

func validateBolean(comparingBoolean: Bool = true) -> Bool

Loose truthy/falsy check against a large set of EN/PT words. With comparingBoolean: true returns whether the string means "true" (YES, ON, SIM, ATIVO, 1, T, …); with false, whether it means "false".

"SIM".validateBolean()                        // true
"nao".validateBolean(comparingBoolean: false) // true

Conversion

var bool: Bool?

"true"/"yes"/"1"true, "false"/"no"/"0"false, else nil (trimmed, case-insensitive).

" YES ".bool   // true
"maybe".bool   // nil

var int: Int? / var float: Float? / var double: Double?

Plain Int(self) / Float(self) / Double(self).

"101".int    // 101
"1.5".double // 1.5
"x".int      // nil

func float(locale: Locale = .current) -> Float? / func double(locale:) -> Double?

Locale-aware parsing via NumberFormatter (accepts grouping separators).

"1,5".double(locale: Locale(identifier: "pt_BR"))   // 1.5

var currencyStringToDouble: Double

Parses a pt_BR currency string to Double, 0.0 on failure.

"R$ 1.234,56".currencyStringToDouble   // 1234.56

var btcToSats: Int / var bitcoinToSatoshis: Int

Multiplies a BTC amount string by 100,000,000. bitcoinToSatoshis is an alias.

"0.0001".btcToSats   // 10000

var data: Data

UTF-8 bytes.

"hi".data   // 2 bytes

var nsString: NSString / var fullNSRange: NSRange

Bridge to NSString; NSRange spanning the whole string (UTF-16 aware).

"café".fullNSRange   // {0, 4}

func nsRange(from range: Range<String.Index>) -> NSRange?

Convert a Swift Range to an NSRange in the UTF-16 view.

var base64Encode: String? / var base64Decode: String?

Base64 encode the UTF-8 bytes / decode a Base64 string back to text.

"hi".base64Encode        // "aGk="
"aGk=".base64Decode      // "hi"

func date(withCurrFormatt:localeIdentifier:timeZone:) -> Date?

Parse the string to Date using the given input format (default "yyyy-MM-dd HH:mm:ss", locale pt-BR, current time zone). A " 0000" suffix is normalised to " +0000".

"2026-08-29 14:30:00".date()   // Date

func date(withCurrFormatt:newFormatt:localeIdentifier:timeZone:) -> Date?

Parse with one format and round-trip through another (normalises the value).

var currentTimeZone: String

Current time-zone offset string, e.g. "-0300".

Cleaning & filtering

var withoutSpacesAndNewLines: String

Removes every space and \n.

"  a \n b ".withoutSpacesAndNewLines   // "ab"

var onlyNumbers: String / var numbers: String

Digits only. onlyNumbers uses a \D regex; numbers uses decimalDigits.

"(11) 98765-4321".onlyNumbers   // "11987654321"

var letters: String / var lettersWithWhiteSpace: String

Keep only letters (optionally keeping spaces).

"a1 b2".letters                // "ab"
"a1 b2".lettersWithWhiteSpace  // "a b"

var alphanumeric: String / var alphanumericWithWhiteSpace: String

Keep only alphanumerics (optionally keeping spaces).

"a-b_c 1".alphanumeric   // "abc1"

var removeSpecialChars: String

Keeps [A-Za-z0-9 -] only.

"a@b#c".removeSpecialChars   // "abc"

var removeHTMLTags: String / var removeEmoji: String

Strip HTML tags / strip emoji (CharacterSet.symbols).

"<p>hi</p>".removeHTMLTags   // "hi"
"hi 😀".removeEmoji           // "hi "

Slicing & padding

var first: String / var last: String

First / last character as a String ("" when empty).

var uppercaseFirst: String

Capitalises the first character only.

"hello".uppercaseFirst   // "Hello"

var firstCharacterAsString: String? / var lastCharacterAsString: String?

Optional variants — nil when empty.

func paddingStart(_ length: Int, with: String = " ") -> String / func paddingEnd(...)

Pad to length with a repeating pad string at the start / end. No-op if already long enough.

"hue".paddingStart(10)             // "       hue"
"hue".paddingEnd(10, with: "br")   // "huebrbrbrb"

func truncated(toLength: Int, trailing: String? = "...") -> String

Non-mutating truncation.

"This is long".truncated(toLength: 7)   // "This is..."

mutating func truncate(toLength: Int, trailing: String? = "...") -> String

In-place truncation (@discardableResult).

mutating func trim() -> String

Trim leading/trailing whitespace and newlines, in place (@discardableResult).

var s = "  hi \n"; s.trim()   // s == "hi"

mutating func reverse() -> String

Reverse in place (@discardableResult).

mutating func insertAtIndexEnd(string:ind:) / insertAtIndexStart(string:ind:)

Insert string at an offset measured from endIndex (negative ind moves left).

var s = "abcd"; s.insertAtIndexEnd(string: "-", ind: -1)   // "abc-d"

Replacing

func replace(from:to:) / func findAndReplace(from:to:)

Simple substring replacement (findAndReplace is generic over StringProtocol).

"a.b.c".replace(from: ".", to: "-")   // "a-b-c"

func replacing(range: CountableClosedRange<Int>, with: String) -> String

Replace by integer character range.

"abcdef".replacing(range: 1...3, with: "X")   // "aXef"

func replacingLastOccurrenceOfString(_:with:caseInsensitive: Bool = true) -> String

Replace only the last match.

"a-b-c".replacingLastOccurrenceOfString("-", with: "+")   // "a-b+c"

func replaceAll(of pattern: String, with: String, options: = []) -> String

Regex replace-all; returns the original on a bad pattern.

"a1b2c3".replaceAll(of: "[0-9]", with: "#")   // "a#b#c#"

@discardableResult func replaceURL(_ withDict: [String: Any]) -> String

Substitute {key} placeholders — used by API.request(pathParams:).

"/users/{id}/posts/{p}".replaceURL(["id": 7, "p": "x"])   // "/users/7/posts/x"

func words() -> [String] / func wordCount() -> Int

Split on whitespace + punctuation, dropping empties.

"Swift is amazing".words()      // ["Swift", "is", "amazing"]
"Swift is amazing".wordCount()  // 3

func contains(_:caseSensitive: Bool = true) -> Bool

Substring check with optional case-insensitivity.

"Hello".contains("ell")                       // true
"Hello".contains("HELLO", caseSensitive: false) // true

Formatting helpers

func applyMask(toText: String, mask: String) -> String

Apply a #-placeholder mask; literal characters in the mask are inserted.

"11987654321".applyMask(toText: "11987654321", mask: "(##) #####-####")
// "(11) 98765-4321"

func exponentize(str: String) -> String

Turn ^-prefixed digits into Unicode superscripts.

"x^2 + y^3".exponentize(str: "x^2 + y^3")   // "x² + y³"

func stringFromTimeInterval(_ interval: TimeInterval) -> NSString

Format a TimeInterval as HH:MM:SS.mmm.

"".stringFromTimeInterval(3661.5)   // "01:01:01.500"

func toSlug() -> String

Lowercase, de-accent, spaces → -, strip other punctuation.

"Olá Mundo!".toSlug()   // "ola-mundo"

func localized(comment: String = "") -> String

NSLocalizedString(self, comment:).

"welcome_title".localized()

Generators & misc

static func loremIpsum(ofLength length: Int = 445) -> String

Lorem-ipsum text truncated to length (max 445).

String.loremIpsum(ofLength: 20)   // "Lorem ipsum dolor si"

func randomString(length: Int) -> String

Random [A-Za-z0-9] string. (Instance method — the receiver is ignored.)

"".randomString(length: 8)   // e.g. "a9Fk2Lp0"

var JSONStringToDictionary: [String: Any]?

Parse a JSON object string to a dictionary (nil + logs on failure).

#"{"a":1}"#.JSONStringToDictionary   // ["a": 1]

var convertToHTML: NSAttributedString?

Render an HTML string to NSAttributedString (UIKit path uses the CSS converter below).

func convertHtmlToAttributedStringWithCSS(font:csscolor:lineheight:csstextalign:customCSS:) -> NSAttributedString?UIKit only

HTML → NSAttributedString with an injected <style> block. Returns the plain HTML rendering when font is nil.

"<b>Hi</b>".convertHtmlToAttributedStringWithCSS(
    font: .systemFont(ofSize: 16), csscolor: "#333",
    lineheight: 0, csstextalign: "left"
)

func height(withConstrainedWidth:font:) -> CGFloat / func width(withConstraintedHeight:font:) -> CGFloatUIKit only

Measured bounding height/width for the string at a fixed width/height and font.

"Some label text".height(withConstrainedWidth: 200, font: .systemFont(ofSize: 14))
Character — classification, conversion, repetition

var isEmoji: Bool

True when the scalar falls in a known emoji range.

Character("😀").isEmoji   // true
Character("a").isEmoji    // false

var int: Int? / var string: String

Digit value (or nil) / one-character String.

Character("7").int   // 7
Character("A").int   // nil

var lowercased: Character / var uppercased: Character

Case-flipped character.

Character("a").uppercased   // "A"

func unicodeScalarCodePoint() -> UInt32

First Unicode scalar value.

Character("A").unicodeScalarCodePoint()   // 65

static func randomAlphanumeric() -> Character

Random [A-Za-z0-9] character.

Character.randomAlphanumeric()   // e.g. "k"

static func * (Character, Int) -> String / static func * (Int, Character) -> String

Repeat a character into a string.

Character("-") * 5   // "-----"
5 * Character("-")   // "-----"
NSString

var string: String?

String(describing:) of the NSString.

func randomAlphaNumericString(_ length: Int = 8) -> String

Random [A-Za-z0-9] string of the given length.

("" as NSString).randomAlphaNumericString(12)
NSAttributedString / NSMutableAttributedString

NSAttributedString(html: String)failable init

Build an attributed string from an HTML fragment.

label.attributedText = NSAttributedString(html: "<b>Hello</b> world")

NSMutableAttributedString builders — UIKit only, all @discardableResult and chainable

Method Effect
customize(_:withFont:color:lineSpace:alignment:changeCurrentText:) append (or restyle) a run with font/color/spacing/alignment
underline(_:withFont:color:changeCurrentText:) underlined run
strikethrough(_:changeCurrentText:) strikethrough run
linkTouch(_:url:withFont:color:changeCurrentText:) tappable link run
supperscript(_:withFont:color:offset:changeCurrentText:) baseline-offset (superscript) run
appendImageToText(_:) append an inline UIImage attachment
normal(_:) append an unstyled run

changeCurrentText: true restyles the first occurrence of the text already in the string instead of appending.

let s = NSMutableAttributedString()
    .customize("Total: ", withFont: .systemFont(ofSize: 14))
    .customize("R$ 10", withFont: .boldSystemFont(ofSize: 14), color: .label)
    .supperscript("00", withFont: .systemFont(ofSize: 9), offset: 6)
label.attributedText = s

func canSetAsLink(textToFind: String, linkURL: String) -> Bool

Adds a .link attribute to the first match; returns whether it was found.

func attributtedString() -> NSAttributedString

Immutable copy of the whole string.

func height(withConstrainedWidth:) -> CGFloat / func width(withConstrainedHeight:) -> CGFloat

Bounding size for the attributed content.

BytesSequence where Element == UInt8

var data: Data

Wrap the byte sequence in Data.

var base64Decoded: Data?

Interpret the bytes as Base64 text and decode.

var string: String?

Decode the bytes as UTF-8.

let bytes: [UInt8] = [0x68, 0x69]
bytes.data       // 2 bytes
bytes.string     // "hi"

Collections & Sequences

Array — dedup, mutation helpers (Element: Equatable)

var unique: [Element] / func withoutDuplicates() -> [Element]

New array with duplicates dropped, first occurrence kept (order preserved).

[1, 2, 2, 3].unique   // [1, 2, 3]

mutating func removeDuplicates() -> [Element]

In-place dedup (@discardableResult).

func withoutDuplicates<E: Equatable>(keyPath:) -> [Element] / <E: Hashable>(keyPath:)

Dedup by a key path. The Hashable overload is O(n).

users.withoutDuplicates(keyPath: \.id)

var removeNilElements: [Element]

compactMap { $0 } — only meaningful when Element is itself optional.

mutating func removeAll(_ item: Element) -> [Element] / removeAll(_ items: [Element])

Remove every occurrence of one value / of any value in a list (@discardableResult).

var a = [1, 2, 2, 3]; a.removeAll(2)          // [1, 3]
var b = [1, 2, 3, 4]; b.removeAll([2, 4])     // [1, 3]

mutating func prepend(_ newElement: Element)

Insert at index 0.

mutating func safeSwap(from:to:)

Swap two indices; silently no-ops if either is out of bounds or equal.

var a = [1, 2, 3]; a.safeSwap(from: 0, to: 2)   // [3, 2, 1]
a.safeSwap(from: 0, to: 9)                       // unchanged
Collection — safe indexing, chunking, indices, averages

var fullRange: Range<Index>

startIndex..<endIndex.

subscript(safe index:) -> Element? / subscript(exist index:) -> Element?

Bounds-checked access, nil instead of a crash.

let a = [1, 2, 3]
a[safe: 1]    // 2
a[safe: 9]    // nil

func group(by size: Int) -> [[Element]]?

Split into chunks of size (last chunk may be shorter). nil when empty or size <= 0.

[0, 2, 4, 7, 6].group(by: 2)   // [[0, 2], [4, 7], [6]]

func forEach(slice: Int, body: ([Element]) -> Void)

Iterate in chunks without building the intermediate array.

[0, 2, 4, 7].forEach(slice: 2) { print($0) }   // [0, 2] then [4, 7]

func indices(where condition:) -> [Index]?

All indices matching a predicate, nil if none.

[1, 7, 1, 2, 1].indices(where: { $0 == 1 })   // [0, 2, 4]

func indices(of item: Element) -> [Index](Element: Equatable)

All indices equal to item.

func adjacentPairs() -> AnySequence<(Element, Element)>

Every unordered pair (i, j) with i before j.

Array([1, 2, 3].adjacentPairs())   // [(1, 2), (1, 3), (2, 3)]

func forEachInParallel(_:)

DispatchQueue.concurrentPerform over the elements. No ordering guarantee.

func average() -> Double (Element: BinaryInteger) / func average() -> Element (Element: FloatingPoint)

Mean, 0 for an empty collection.

[1, 2, 3, 4].average()        // 2.5
[1.2, 2.3, 4.5].average()     // 2.666…
BidirectionalCollection

subscript(offset distance: Int) -> Element

Positive offset from the start, negative from the end.

let a = [1, 2, 3, 4, 5]
a[offset: 1]    // 2
a[offset: -2]   // 4

func last<T: Equatable>(where keyPath:, equals value:) -> Element?

Last element whose key-path value equals value.

events.last(where: \.type, equals: .login)
Sequence — predicates, key-path sorting, sums, dedup

func all(matching:) / func none(matching:) / func any(matching:)

Whether the predicate holds for all / no / at least one element.

[2, 4, 6].all(matching: { $0 % 2 == 0 })   // true
[1, 3].any(matching: { $0 % 2 == 0 })      // false

func reject(where:) -> [Element]

Inverse of filter.

[2, 3, 4, 7].reject(where: { $0 % 2 == 0 })   // [3, 7]

func count(where:) -> Int

Number of elements matching a predicate.

func forEachReversed(_:) / func forEach(where:body:)

Iterate right-to-left / iterate only matching elements.

func accumulate<U>(initial:next:) -> [U]

Like reduce but returns every interim result.

[1, 2, 3].accumulate(initial: 0, next: +)   // [1, 3, 6]

func filtered<T>(_ isIncluded:, map transform:) -> [T]

filter + map in one lazy pass.

[1, 2, 3, 4].filtered({ $0 % 2 == 0 }, map: { "\($0)" })   // ["2", "4"]

func single(where:) -> Element?

The one matching element, or nil if zero or more than one match.

[1, 4, 7].single(where: { $0 % 2 == 0 })      // 4
[2, 4].single(where: { $0 % 2 == 0 })         // nil

func divided(by condition:) -> (matching:, nonMatching:)

Partition into two arrays.

let (even, odd) = [0, 1, 2, 3].divided { $0 % 2 == 0 }   // ([0, 2], [1, 3])

func withoutDuplicates<T: Hashable>(transform:) -> [Element]

Dedup by a derived hashable value.

[(1, "a"), (2, "b"), (1, "c")].withoutDuplicates { $0.0 }   // [(1, "a"), (2, "b")]

func sorted(by keyPath:) / sorted(by keyPath:with:) / sorted(by:and:) / sorted(by:and:and:)

Sort by one, two, or three key paths (later paths break ties). The with: variant takes an explicit comparator.

people.sorted(by: \.lastName, and: \.firstName)
scores.sorted(by: \.value, with: >)

func sum() -> Element (Element: AdditiveArithmetic) / func sum<T: AdditiveArithmetic>(for keyPath:) -> T

Total of the elements, or of a numeric property.

[1, 2, 3].sum()                         // 6
["ab", "cde"].sum(for: \.count)         // 5

func first<T: Equatable>(where keyPath:, equals value:) -> Element?

First element matching on a key path.

func contains(_ elements: [Element]) -> Bool (Element: Equatable or Hashable)

Whether every element of elements is present (the Hashable overload is O(m+n)).

func containsDuplicates() -> Bool / func duplicates() -> [Element] (Element: Hashable)

Whether any value repeats / the set of repeated values.

[1, 2, 2, 3, 3].duplicates().sorted()   // [2, 3]
RangeReplaceableCollection — rotate, take/skip, offset subscripts

init(expression:count:)

Build a collection by evaluating an autoclosure count times.

Array(expression: Int.random(in: 0..<10), count: 3)   // e.g. [4, 9, 1]

func rotated(by places: Int) -> Self / mutating func rotate(by:) -> Self

Rotate elements; positive moves the tail to the front.

[1, 2, 3, 4].rotated(by: 1)    // [4, 1, 2, 3]
[1, 2, 3, 4].rotated(by: -1)   // [2, 3, 4, 1]

mutating func removeFirst(where:) -> Element?

Remove and return the first match (@discardableResult).

mutating func removeRandomElement() -> Element?

Remove and return a random element.

mutating func keep(while:) -> Self / func take(while:) -> Self / func skip(while:) -> Self

keep/take return the leading run that matches; skip returns everything after it.

[0, 2, 4, 7, 6].take(while: { $0 % 2 == 0 })   // [0, 2, 4]
[0, 2, 4, 7, 6].skip(while: { $0 % 2 == 0 })   // [7, 6]

mutating func removeDuplicates<E>(keyPath:)

In-place dedup by an Equatable or Hashable key path.

subscript(offset: Int) -> Element / subscript<R: RangeExpression>(range:) -> SubSequence

Get/set by integer offset or integer range.

var a = [10, 20, 30]; a[1] = 99      // [10, 99, 30]
a[0..<2]                              // [10, 99]

mutating func appendIfNonNil(_:) / appendIfNonNil(contentsOf:)

Append only when the optional value / sequence is non-nil.

var a = [1]; a.appendIfNonNil(Optional<Int>.none)   // [1]
a.appendIfNonNil(2)                                  // [1, 2]
Dictionary — key paths, JSON, key/value maps, merge operators

subscript(path path: [Key]) -> Any?

Deep get/set through nested dictionaries.

var d: [String: Any] = ["a": ["b": ["c": 1]]]
d[path: ["a", "b", "c"]]              // 1
d[path: ["a", "b", "c"]] = 2

var queryString: String

key=value&key=value (no percent-encoding — encode yourself if needed).

["a": 1, "b": 2].queryString   // "a=1&b=2" (order not guaranteed)

var convertToJSON: String

Pretty-printed JSON, or an error description string on failure.

init(grouping sequence: by keyPath:)

Group a sequence into [Key: [Element]] by a key path.

Dictionary(grouping: people, by: \.city)

func toObjetct<T: Codable & Sendable>() throws -> T

Encode to JSON then decode into T. Throws DecodingError on a shape mismatch.

let user: User = try ["id": 1, "name": "Ana"].toObjetct()

func has(key:) -> Bool

Key presence check.

mutating func removeAll<S: Sequence>(keys:) / static func - (lhs:keys:) / static func -= (lhs:keys:)

Remove a set of keys — mutating, or as a new dictionary via - / -=.

var d = ["a": 1, "b": 2, "c": 3]
d -= ["a", "b"]                       // ["c": 3]

mutating func removeValueForRandomKey() -> Value?

Remove and return one random entry's value.

func jsonData(prettify: Bool = false) -> Data? / func jsonString(prettify: Bool = false) -> String?

Serialise to Data / String, nil if the dictionary isn't a valid JSON object.

func mapKeysAndValues<K, V>(_:) -> [K: V] / func compactMapKeysAndValues<K, V>(_:) -> [K: V]

Transform both keys and values in one pass; the compact variant drops nil results.

["a": 1].mapKeysAndValues { ($0.key.uppercased(), $0.value * 10) }   // ["A": 10]

func pick(keys: [Key]) -> [Key: Value]

Sub-dictionary limited to the given keys.

static func + (lhs:rhs:) / static func += (lhs:rhs:)

Merge two dictionaries (right wins on key clash).

func keys(forValue value:) -> [Key] (Value: Equatable)

All keys mapping to a value.

mutating func lowercaseAllKeys() (Key: StringProtocol)

Lowercase every key in place.

var uniqueValues: [Key: Value] (Value: Hashable)

Keep only the first entry seen for each distinct value.

Optional — safe unwrap, conditional assignment

func unwrapped(or defaultValue: Wrapped) -> Wrapped

self ?? defaultValue, read nicely.

let name: String? = nil
name.unwrapped(or: "Guest")   // "Guest"

func unwrapped(or error: Error) throws -> Wrapped

Unwrap or throw a chosen error.

let id = try userId.unwrapped(or: AppError.missingID)

func run(_ block: (Wrapped) -> Void)

Run a block only when non-nil (like if let, expression-style).

token.run { print("have token \($0)") }

static func ??= (lhs: inout Optional, rhs: Optional)

Assign only if the right side is non-nil.

var params: String? = "a"; params ??= nil   // still "a"

static func ?= (lhs: inout Optional, rhs: @autoclosure)

Assign only if the left side is currently nil.

var text: String? = nil
text ?= "first"     // "first"
text ?= "second"    // still "first"

var isNilOrEmpty: Bool / var nonEmpty: Wrapped? (Wrapped: Collection)

Nil-or-empty check; nonEmpty returns the collection only when it has content.

let list: [Int]? = []
list.isNilOrEmpty   // true
list.nonEmpty       // nil

static func == / != (Optional, Wrapped.RawValue?) (Wrapped: RawRepresentable)

Compare an optional enum directly against an optional raw value.

let status: Status? = .active
status == "active"   // true
Comparable

func isBetween(_ range: ClosedRange<Self>) -> Bool

Range membership.

7.isBetween(6...12)   // true

func clamped(to range: ClosedRange<Self>) -> Self

Constrain a value to a range.

1.clamped(to: 3...8)      // 3
0.32.clamped(to: 0.1...0.29)   // 0.29
Bool

var int: Int / var string: String / var data: Data

1/0, "true"/"false", or a single-byte Data.

true.int      // 1
false.string  // "false"

Numbers & Geometry

Int — conversions, digits, primes, roman numerals, operators

var double: Double / var float: Float / var cgFloat: CGFloat / var uInt: UInt / var uInt32: UInt32 / var uInt64: UInt64

Straight numeric conversions (uInt32/uInt64 truncate).

5.double    // 5.0
(-1).uInt32 // 4294967295

var countableRange: CountableRange<Int>

0..<self.

3.countableRange   // 0..<3

var degreesToRadians: Double / var radiansToDegrees: Double

Angle conversion.

180.degreesToRadians   // 3.14159…

var digits: [Int] / var digitsCount: Int

Decimal digits of abs(self), and how many.

1234.digits       // [1, 2, 3, 4]
1234.digitsCount  // 4

var kFormatted: String

Compact "k"/"kk" formatting for values ≥ 1000.

5300.kFormatted     // "5k"
2_500_000.kFormatted // "25kk"

var timestampToDate: Date

Date(timeIntervalSince1970:).

1_700_000_000.timestampToDate   // 2023-11-14 …

var satsToBTC: String / var convertToBTC: String / var toBTC: String

Satoshis → BTC string, 8 decimal places. All three are the same.

150_000_000.satsToBTC   // "1.50000000"

func isPrime() -> Bool

Primality test (trial division up to √n).

7.isPrime()   // true
9.isPrime()   // false

func romanNumeral() -> String?

Roman numerals for positive integers, nil for 0 or negative.

2024.romanNumeral()   // "MMXXIV"

func roundToNearest(_ number: Int) -> Int

Round to the closest multiple of number.

47.roundToNearest(10)   // 50

Operators

Operator Meaning Example
a ** b exponentiation → Double 2 ** 38.0
√ n (prefix) square root → Double √ 93.0
a ± b (infix) (a+b, a-b) 5 ± 3(8, 2)
± n (prefix) (n, -n) ± 2(2, -2)
Float / Double

var int: Int / var double: Double (Float) / var float: Float (Double) / var cgFloat: CGFloat

Numeric conversions.

var satsToBTC / convertToBTC / toBTC

Satoshis → BTC (Double here, unlike Int which returns a String).

150_000_000.0.satsToBTC   // 1.5

func rounded(toPlaces places: Int) -> Float(Float only)

Round to N decimal places.

Float(3.14159).rounded(toPlaces: 2)   // 3.14

Operator a ** b

Exponentiation, staying in the same type (Float ** Float → Float, Double ** Double → Double).

4.4 ** 0.5   // 2.0976…
Decimal

mutating func round(_ scale: Int, _ roundingMode:) / func rounded(_ scale:, _ roundingMode:) -> Decimal

Decimal rounding via NSDecimalRound — mutating and non-mutating.

Decimal(2.567).rounded(2, .plain)   // 2.57
BinaryInteger

var bytes: [UInt8]

Big-endian raw byte representation.

Int16(-128).bytes   // [255, 128]

init?(bytes: [UInt8])

Reconstruct an integer from bytes (traps if the byte count exceeds the type size; nil if the value doesn't fit exactly).

Int16(bytes: [0xFF, 0xFD])   // -3
BinaryFloatingPoint

func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self

Round to N places with an explicit rule (negative places treated as 0).

3.1415927.rounded(numberOfDecimalPlaces: 3, rule: .up)   // 3.142
SignedNumeric

var string: String

String(describing:).

var asLocaleCurrency: String? / func asCurrency(locale: Locale = pt_BR) -> String?

Currency formatting in the current locale / a specified locale.

1234.5.asCurrency()                                  // "R$ 1.234,50"
1234.5.asCurrency(locale: Locale(identifier: "en_US")) // "$1,234.50"

func spelledOutString(locale: Locale = .current) -> String?

Number spelled out in words.

92.spelledOutString(locale: Locale(identifier: "en"))   // "ninety-two"
CGFloat

var abs / ceil / floor / var int / float / double

Math and numeric conversions.

CGFloat(-3.2).abs    // 3.2
CGFloat(3.2).ceil    // 4.0

var isPositive: Bool / var isNegative: Bool

Sign checks.

var degreesToRadians: CGFloat / var radiansToDegrees: CGFloat

Angle conversion.

CGRect

var center: CGPoint

Rect centre.

init(center: CGPoint, size: CGSize)

Build a rect from its centre and size.

CGRect(center: CGPoint(x: 50, y: 50), size: CGSize(width: 20, height: 10))
// origin (40, 45), size 20×10

func resizing(to size: CGSize, anchor: CGPoint = (0.5, 0.5)) -> CGRect

Resize while keeping the given normalised anchor point fixed.

rect.resizing(to: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0, y: 1))
// grows from the bottom-left corner
CGSize

var aspectRatio: CGFloat / var maxDimension: CGFloat / var minDimension: CGFloat

width / height (0 when height is 0), and the larger / smaller side.

CGSize(width: 16, height: 9).aspectRatio   // 1.777…

func aspectFit(to boundingSize:) -> CGSize / func aspectFill(to boundingSize:) -> CGSize

Scale to fit inside / fill a bounding size, preserving ratio.

CGSize(width: 120, height: 80).aspectFit(to: CGSize(width: 100, height: 50))
// 75 × 50

Operators

+, -, * and their +=/-=/*= forms, between two CGSizes, a CGSize and a (width, height) tuple, or a CGSize and a scalar.

CGSize(width: 5, height: 10) + CGSize(width: 3, height: 4)   // 8 × 14
CGSize(width: 5, height: 10) * 3                              // 15 × 30
CGPoint / CGRect / CGSize — Hashable

When SwiftUI is available, CGPoint, CGRect, and CGSize are made Hashable (retroactive conformance) so they can be used as dictionary keys or in Sets and as SwiftUI identifiers.

var seen: Set<CGPoint> = []
seen.insert(CGPoint(x: 1, y: 2))

Date, Data & Files

Date — components, comparisons, formatting, arithmetic, init

Calendar-component accessors

Read (and, where noted, write) individual components using the user's current calendar.

Property Get Set
year
month (clamped to valid range)
day (clamped)
hour / minute / second (clamped)
nanosecond / millisecond (clamped)
weekday / weekOfMonth / weekOfYear / quarter / era
calendar (Calendar.current)
var d = Date()
d.year = 2030          // shifts the date to 2030, keeping everything else
d.minute = 0
Date().weekday          // 1 = Sunday (Gregorian)

Relative checks

isInFuture, isInPast, isInToday, isInYesterday, isInTomorrow, isInWeekend, isWorkday, isInCurrentWeek, isInCurrentMonth, isInCurrentYear.

someDate.isInToday        // Bool
someDate.isInWeekend      // Bool

func isInCurrent(_ component: Calendar.Component) -> Bool

Same granularity check, for an arbitrary component.

Date().isInCurrent(.year)   // true

var iso8601String: String / var unixTimestamp: Double

yyyy-MM-dd'T'HH:mm:ss.SSS + Z (GMT); seconds since 1970.

Date().iso8601String    // "2026-08-29T14:51:29.574Z"

Rounding

nearestFiveMinutes, nearestTenMinutes, nearestQuarterHour, nearestHalfHour, nearestHour — all return a new Date.

var d = Date(); d.minute = 44
d.nearestFiveMinutes    // :45
d.nearestHour           // rounds up because minute ≥ 30

var yesterday: Date / var tomorrow: Date

±1 day.

func adding(_:value:) -> Date / mutating func add(_:value:)

Add multiples of a calendar component.

Date().adding(.day, value: 7)         // one week later
var d = Date(); d.add(.month, value: -1)

func changing(_:value:) -> Date?

Set one component to a specific value (validated; nil if out of range).

Date().changing(.hour, value: 9)      // 9am today

func beginning(of component:) -> Date? / func end(of component:) -> Date?

Start / end instant of the enclosing .day / .month / .year / .hour / week, etc.

Date().beginning(of: .month)    // 1st, 00:00:00
Date().end(of: .day)            // 23:59:59

Differences

secondsSince(_:), minutesSince(_:), hoursSince(_:), daysSince(_:) — signed Double.

endDate.hoursSince(startDate)   // e.g. 3.5

func isBetween(_ start: Date, _ end: Date, includeBounds: Bool = false) -> Bool

Range check.

func isWithin(_ value: UInt, _ component: Calendar.Component, of date: Date) -> Bool

Whether two dates are within N components of each other.

a.isWithin(3, .day, of: b)   // true if ≤ 3 days apart

Formatting

Method Example output
string(withFormat: String = "dd/MM/yyyy HH:mm") "29/08/2026 14:30"
dateString(ofStyle: .medium) "Aug 29, 2026"
dateTimeString(ofStyle: .short) "8/29/26, 2:30 PM"
timeString(ofStyle: .short) "2:30 PM"
dayName(ofStyle: .full / .threeLetters / .oneLetter) "Saturday" / "Sat" / "S"
monthName(ofStyle: .full / .threeLetters / .oneLetter) "August" / "Aug" / "A"
Date().string(withFormat: "yyyy-MM-dd")   // "2026-08-29"
Date().dayName(ofStyle: .threeLetters)     // "Sat"

Random dates

static func random(in: Range<Date>) -> Date, plus ClosedRange and using generator: variants.

Date.random(in: startDate...endDate)

Initializers

  • init?(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:) — every field defaults to "now".
  • init?(iso8601String:) — parse yyyy-MM-dd'T'HH:mm:ss.SSSZ.
  • init(unixTimestamp:) — seconds since 1970.
  • init?(integerLiteral:) — parse a packed yyyyMMdd integer.
Date(year: 2010, month: 1, day: 12)
Date(iso8601String: "2026-01-12T16:48:00.959Z")
Date(integerLiteral: 2026_12_25)
Data — JSON, hashing, hex, XOR, decoding

var prettyJson: String?

Pretty-printed JSON if the data is a valid JSON value.

responseData.prettyJson

var toDictionay: [String: Any]?

Parse a JSON object to a dictionary (note the spelling — toDictionay).

var toHexString: String / func toHexadecimalString() -> String

Lower-case hex representation of the bytes.

Data([0x0f, 0xa0]).toHexString   // "0fa0"

var bool: Bool

first != 0.

init?(hexString:)

Parse a hex string (spaces allowed) to bytes; nil on an invalid nibble.

Data(hexString: "0f a0")   // 2 bytes

func SHA256() -> Data / func SHA512() -> Data

CommonCrypto digests (empty Data if CommonCrypto is unavailable).

Data("abc".utf8).SHA256().toHexString

func HMACSHA512(key: Data) -> DataiOS 13+

CryptoKit HMAC-SHA512.

message.HMACSHA512(key: secret)

func XOR(with other: Data) -> Data

Byte-wise XOR (result length = shorter of the two).

a.XOR(with: pad)

static func MD5(string:) -> DataiOS 13+

MD5 of a string (insecure — legacy interop only). Returns the hex digest as UTF-8 bytes.

Data.MD5(string: "Hello").toHexString

func object<T: Codable & Sendable>() -> T?

Decode JSON data to T, nil + logs on failure.

let user: User? = responseData.object()
URL

var params: [String: String]

Query items as a dictionary (missing values become "").

URL(string: "https://x.com?a=1&b=2")!.params   // ["a": "1", "b": "2"]
FileManager — Documents-directory helpers

func createDirectory(_ directoryName: String) -> URL?

Create (if missing) a folder under Documents; returns its URL.

let dir = FileManager.default.createDirectory("cache")

func retrieveFile(_ directoryAndFile: String) -> URL

Build a file:// URL under Documents for the given relative path (no existence check).

func convertToURL(path: String) -> URL?

Directory URL under Documents, or nil if it can't be listed.

func saveFileToDirectory(_ sourceURL: URL, toPathURL: URL) -> Bool

moveItem(at:to:), returning success.

func saveImageToDirectory(_ imageWithPath: String, imagem: UIImage) -> BoolUIKit only

Write a UIImage as PNG to an absolute path.

FileManager.default.saveImageToDirectory(path, imagem: photo)

func removeFile(_ directoryAndFile: String) -> Bool

removeItem(atPath:), returning success.

func retrieveAllFilesFromDirectory(directoryName: String) -> [String]?

File names in a Documents sub-folder (.DS_Store filtered out).

func directoryExistsAtPath(_ path: String) -> Bool

Exists and is a directory.

UserDefaults — Codable storage, common flags (@MainActor)

var isLoggedIn: Bool / var isFirstTimeOnApp: Bool

Ready-made boolean flags (keys "isLoggedIn" / "isFirstTimeOnApp"), auto-synchronize() on set.

UserDefaults.standard.isFirstTimeOnApp = false

func set<T: Codable>(object: T, forKey key: String, usingEncoder: = JSONEncoder())

Encode and store any Codable value.

func object<T: Codable>(_ type: T.Type, with key: String, usingDecoder: = JSONDecoder()) -> T?

Decode a stored Codable value.

UserDefaults.standard.set(object: user, forKey: "user")
let user = UserDefaults.standard.object(User.self, with: "user")

func removeSavedObject(forKey:) -> Bool

Remove a value only if it is a String; returns whether it removed anything.

func removeAllSaved()

Wipe the app's entire persistent domain.

func showEverything() -> [String: Any]

Full dictionaryRepresentation() — handy for debugging.

Encoding & Errors

Encodable — dictionary / JSON conversion

var dictionary: [String: Any]

JSON-encode self, then reparse to a dictionary ([:] on failure).

struct Point: Encodable { let x = 1; let y = 2 }
Point().dictionary   // ["x": 1, "y": 2]

var json: String / var data: Data

Dictionary → JSON string / Data.

subscript(key: String) -> Any?

Read one value from the encoded form.

Point()["x"]   // 1
JSONDecoder — decoding helpers

All return T: Decodable & Sendable and throw a readable NSError naming the missing key / mismatched type / missing value on failure.

static func decode<T>(data: Data) throws -> T

let user: User = try JSONDecoder.decode(data: responseData)

static func decode<T>(_ json: String, using encoding: String.Encoding = .utf8) throws -> T

let user: User = try JSONDecoder.decode(#"{"id":1,"name":"Ana"}"#)

static func decode<T>(fromURL url: URL) throws -> T

Read a file/URL then decode.

static func decode<T>(dictionary: Any) throws -> T

Re-encode a dictionary/array to JSON then decode (uses .convertFromSnakeCase).

let user: User = try JSONDecoder.decode(dictionary: ["user_id": 1, "full_name": "Ana"])
NSError

static func createErrorWith(code: Int, description: String, reasonForError: String) -> NSError

Build an NSError in the LoverdeCoErrorDomain with a localized description and failure reason. Used throughout API for HTTP errors.

throw NSError.createErrorWith(code: 404, description: "Not found", reasonForError: body)

Crypto

RIPEMD_160 — RIPEMD-160 digest

static func hash(_ message: Data) -> Data

Returns the 20-byte RIPEMD-160 digest of message. Pure Swift, no system dependency. Mainly useful for Bitcoin-style address hashing (RIPEMD160(SHA256(x))).

let digest = RIPEMD_160.hash(Data("abc".utf8))
digest.count                                   // 20
digest.map { String(format: "%02x", $0) }.joined()
// "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"
LCECryptoKitManager — OTP / peppered-login bridge (needs the LCECryptoKit binary)

A thin facade over the optional LCECryptoKit binary product (enabled by the LCE_ENABLE_CRYPTO_BINARY build flag). When the binary is not linked every method is a no-op returning nil / "" / false, so calling code still compiles and runs.

init() / init(privateKey:)

Create a manager. The privateKey (a.k.a. "hash key") is only needed by the *WithKey methods.

let crypto = LCECryptoKitManager()
let keyed  = LCECryptoKitManager(privateKey: serverHashKey)

static func generateKey() -> String

Generates a random AES key string.

let key = LCECryptoKitManager.generateKey()

func encodeTP(email:password:) -> String? / func decodeOTP(_:) -> String?

Encode an email+password pair into an OTP seed hash, and decode it back.

let hash = crypto.encodeTP(email: "ana@x.com", password: "s3cr3t")
let back = crypto.decodeOTP(hash ?? "")

func encodeOTPWithKey(email:password:) -> String? / func decodeOTPWithKey(_:) -> Bool

Same as above but bound to the instance's privateKey; decodeOTPWithKey returns whether the hash validates against that key rather than the decoded value.

let keyed = LCECryptoKitManager(privateKey: serverHashKey)
let hash  = keyed.encodeOTPWithKey(email: "ana@x.com", password: "s3cr3t")
let ok    = keyed.decodeOTPWithKey(hash ?? "")     // Bool

static func generateSalt() -> String

Random salt for the salted/iterated/peppered login flow.

let salt = LCECryptoKitManager.generateSalt()

static func computeClientHash(email:password:salt:) -> String

Client-side hash of the credentials with the given salt — sent to the server instead of the raw password.

let clientHash = LCECryptoKitManager.computeClientHash(
    email: "ana@x.com", password: "s3cr3t", salt: salt
)

static func computeLoginBearerToken(userId:clientHash:) -> String?

Derives the login bearer token from a user id and the client hash.

let token = LCECryptoKitManager.computeLoginBearerToken(userId: "42", clientHash: clientHash)

static func otpEncode(_:) -> String? / static func otpDecode(_:) -> String?

One-time-pad encode/decode of an arbitrary string.

let enc = LCECryptoKitManager.otpEncode("secret-value")
let dec = LCECryptoKitManager.otpDecode(enc ?? "")     // "secret-value"

Core — the LCEssentials namespace

LoggingprintLog / printInfo / printWarn / printError

Four global functions used across the package. Each takes title: + msg: Any and an optional prettyPrint: Bool (wraps the message in a START/END banner and, for info/warn/error, prints the call-site file/function/line).

printLog(title: "STATE", msg: viewModel.state)
printError(title: "Decode", msg: error.localizedDescription, prettyPrint: true)

Operator a ^^ b

Float exponentiation.

2.0 ^^ 10.0   // 1024.0
LCEssentials — app / device / environment info (static, @MainActor)

App info

Property Value
appDisplayName CFBundleDisplayName
appBundleID Bundle.main.bundleIdentifier
appBuild build number string
appVersion short version string
applicationIconBadgeNumber get/set the icon badge (iOS/tvOS)
LCEssentials.appVersion        // "2.0.0"
LCEssentials.applicationIconBadgeNumber = 0

Device / screen

Property Value
currentDevice UIDevice.current / WKInterfaceDevice.current()
screenWidth / screenHeight main screen bounds
deviceOrientation UIDeviceOrientation (iOS)
batteryLevel Float (iOS)
systemVersion OS version string
isPad / isPhone idiom checks (iOS)
isMultitaskingSupported iOS
if LCEssentials.isPad {  }
LCEssentials.screenWidth

Environment

Property Value
isInDebuggingMode built in Debug
isInTestFlight running a TestFlight build
isRunningOnSimulator simulator target
isRegisteredForRemoteNotifications push registration state
isStatusBarHidden status-bar visibility (iOS)
keyWindow current key UIWindow (iOS/tvOS)
sharedApplication UIApplication.shared
guard !LCEssentials.isRunningOnSimulator else { return }

static func sourceFileName(filePath: String) -> String

Last path component of a #file string.

static func getTopViewController(base: UIViewController? = nil, aboveBars: Bool = true) -> UIViewController?

Walk the presentation/navigation/tab hierarchy to the front-most controller. aboveBars: false descends into the visible child of nav/tab containers.

let top = LCEssentials.getTopViewController()
top?.present(alert, animated: true)
LCEssentials — threading, timing, sharing (static methods)

static func backgroundThread(delay: Double = 0, background: (@Sendable () -> Void)? = nil, completion: (@Sendable () -> Void)? = nil)

Run background off the main queue, then completion on the main queue after delay.

LCEssentials.backgroundThread(delay: 0.3, background: {
    let result = heavyWork()
}, completion: {
    updateUI()
})

static func dispatchAsync(completion: @escaping () -> Void)

DispatchQueue.main.async shorthand.

@discardableResult static func delay(milliseconds: Double, queue: DispatchQueue = .main, completion:) -> DispatchWorkItem

Delayed call; keep the returned item to .cancel() it.

let task = LCEssentials.delay(milliseconds: 500) { fire() }
task.cancel()   // if no longer needed

static func debounce(millisecondsDelay: Int, queue: DispatchQueue = .main, action:) -> () -> Void

Returns a debounced wrapper — call it repeatedly, action runs at most once per idle window.

let search = LCEssentials.debounce(millisecondsDelay: 300) { runSearch() }
textField.onChange = search

static func didTakeScreenShot(_ action: @escaping (Notification) -> Void)iOS/tvOS

Observe userDidTakeScreenshotNotification.

static func shareApp(message: String = "", url: String = "")UIKit

Present a UIActivityViewController from the top view controller.

static func call(_ number: String) / static func openSafari(_ urlStr: String)UIKit

Open tel:// / open a URL.

LCEssentials.call("+5511999999999")
LCEssentials.openSafari("https://loverde.com.br")
LCEssentials — cached file download (iOS)

A shared 50 MB memory / 200 MB disk URLCache backs these.

static func downloadFileWithCache(from url: URL, completion: @escaping (Result<URL, Error>) -> Void)

Return a local temp-file URL for a remote file, hitting the cache first.

LCEssentials.downloadFileWithCache(from: pdfURL) { result in
    if case .success(let localURL) = result { showPDF(localURL) }
}

static func cachedFileURL(for url: URL) -> URL?

Temp-file URL if the response is already cached, else nil.

static func cleanExpiredCache(expiration: TimeInterval = 7 days)

Drop cached responses and temp files older than expiration.

LCESingletonDelegate

@objc protocol LCESingletonDelegate: AnyObject

Optional callback singleton(object: Any?, withData: Any) — the delegate contract for the package's singleton-style helpers.

final class Foo: NSObject, LCESingletonDelegate {
    func singleton(object: Any?, withData: Any) {  }
}