Files
LCEssentials/Documentation/Extensions.md
2026-08-29 19:26:46 -03:00

34 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

Date, Data & Files

Encoding & Errors

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