Skip to content

Commit

Permalink
Merge branch 'rafael-assis-rafael_perf_improvements' into 0.9.9
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Naumov committed Nov 25, 2023
2 parents 1c9ede2 + 4f1b3b6 commit 1be47b7
Showing 1 changed file with 140 additions and 79 deletions.
219 changes: 140 additions & 79 deletions Sources/ViewInspector/Inspector.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,125 @@
import SwiftUI

@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
public struct Inspector { }
public struct Inspector {

/// Removes the "(unknown context at <memory_address>)" portion of a type name.
/// Calls to this method are memoized and retained for the lifetime of the program.
/// - Parameter typeName: The raw type name. (e.g. `SomeTypeName.(unknown context at $138b3290c).SomePropertyName`)
/// - Returns: The sanitized type name. (e.g. `SomeTypeName.SomePropertyName`)
static func sanitizeNamespace(ofTypeName typeName: String) -> String {
var str = typeName

if let sanitized = sanitizedNamespacesCache[typeName] {
return sanitized
}

let range = NSRange(location: 0, length: str.utf16.count)
str = sanitizeNamespaceRegex.stringByReplacingMatches(in: str,
options: [],
range: range,
withTemplate: "")

// For Objective-C classes String(reflecting:) sometimes adds the namespace __C, drop it too
str = str.replacingOccurrences(of: "<__C.", with: "<")

Inspector.sanitizedNamespacesCache[typeName] = str

return str

}

/// Replaces the generic types of a given type name with a string.
/// Calls to this method are memoized and retained for the lifetime of the program.
/// - Parameters:
/// - typeName: The original type name. (e.g. `SomeTypeName<SomeGenericTypeParameter>`)
/// - replacement: The string to replace the generic parameters with. (e.g. "<EmptyView>")
/// - Returns: The type name with its generic parameters replaced. (e.g. `SomeTypeName<EmptyView>`)
static func replaceGenericParameters(inTypeName typeName: String,
withReplacement replacement: String) -> String {
// Check memoized value
if let typeNameDict = Inspector.replacedGenericParametersCache[typeName],
let cachedResult = typeNameDict[replacement] {
return cachedResult
}

// Compute value
let result = computeReplacementOfGenericParameters(inTypeName: typeName,
withReplacement: replacement)

// Store memoized value
var typeNameDict = Inspector.replacedGenericParametersCache[typeName] ?? [:]
typeNameDict[replacement] = result
Inspector.replacedGenericParametersCache[typeName] = typeNameDict

return result
}

private static func computeReplacementOfGenericParameters(inTypeName typeName: String,
withReplacement replacement: String) -> String {
guard let start = typeName.firstIndex(of: "<") else {
return typeName
}

var balance = 1
var current = typeName.index(after: start)
while balance > 0 && current < typeName.endIndex {
let char = typeName[current]
if char == "<" { balance += 1 }
if char == ">" {
guard let indexOfPreviousChar = typeName.index(current,
offsetBy: -1,
limitedBy: typeName.startIndex) else {
return typeName
}
let previousChar = typeName[indexOfPreviousChar]
if previousChar == "-" {
// We've found the "->" arrow for a closure type. Ignore this ">".
} else {
balance -= 1
}
}
current = typeName.index(after: current)
}

if balance == 0 {
return String(typeName[..<start]) +
replacement +
replaceGenericParameters(inTypeName: String(typeName[current...]),
withReplacement: replacement)
}

return typeName
}

private static var replacedGenericParametersCache: [String : [String : String]] = [:]
private static var sanitizedNamespacesCache: [String: String] = [:]

private static let sanitizeNamespacePatterns = [
"(\\.\\(unknown context at ..........\\))",
// This pattern may be helpful to solve issue #268.
// It will remain disabled until it is confirmed.
// https://github.com/nalexn/ViewInspector/issues/268
//"(\\(extension in [a-zA-Z0-9]*\\)\\:)",
]

private static let sanitizeNamespaceRegex = {
// swiftlint:disable force_try
try! NSRegularExpression(pattern: sanitizeNamespacePatterns.joined(separator: "|"))
// swiftlint:enable force_try
}()
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
internal extension Inspector {

static func attribute(label: String, value: Any) throws -> Any {
if label == "super", let superclass = Mirror(reflecting: value).superclassMirror {
return superclass
}
return try attribute(label: label, value: value, type: Any.self)
}

static func attribute<T>(label: String, value: Any, type: T.Type) throws -> T {
let mirror = (value as? Mirror) ?? Mirror(reflecting: value)
guard let child = mirror.descendant(label) else {
Expand All @@ -21,26 +128,26 @@ internal extension Inspector {
}
return try cast(value: child, type: T.self)
}

static func attribute(path: String, value: Any) throws -> Any {
return try attribute(path: path, value: value, type: Any.self)
}

static func attribute<T>(path: String, value: Any, type: T.Type) throws -> T {
let labels = path.components(separatedBy: "|")
let child = try labels.reduce(value, { (value, label) -> Any in
try attribute(label: label, value: value)
})
return try cast(value: child, type: T.self)
}

static func cast<T>(value: Any, type: T.Type) throws -> T {
guard let casted = value as? T else {
throw InspectionError.typeMismatch(value, T.self)
}
return casted
}

static func unsafeMemoryRebind<V, T>(value: V, type: T.Type) throws -> T {
guard MemoryLayout<V>.size == MemoryLayout<T>.size else {
throw InspectionError.notSupported(
Expand All @@ -55,13 +162,13 @@ internal extension Inspector {
.assumingMemoryBound(to: T.self).pointee
}
}

enum GenericParameters {
case keep
case remove
case customViewPlaceholder
}

static func typeName(value: Any,
namespaced: Bool = false,
generics: GenericParameters = .keep) -> String {
Expand All @@ -72,105 +179,59 @@ internal extension Inspector {
return typeName(type: type(of: value), namespaced: namespaced,
generics: generics)
}

static func isSystemType(value: Any) -> Bool {
let name = typeName(value: value, namespaced: true)
return isSystemType(name: name)
}

static func isSystemType(type: Any.Type) -> Bool {
let name = typeName(type: type, namespaced: true)
return isSystemType(name: name)
}

private static func isSystemType(name: String) -> Bool {
return [
String.swiftUINamespaceRegex, "Swift\\.",
"_CoreLocationUI_SwiftUI\\.", "_MapKit_SwiftUI\\.",
"_AuthenticationServices_SwiftUI\\.", "_AVKit_SwiftUI\\.",
].containsPrefixRegex(matching: name, wholeMatch: false)
}

static func typeName(type: Any.Type,
namespaced: Bool = false,
generics: GenericParameters = .keep) -> String {
let typeName = namespaced ? String(reflecting: type).sanitizingNamespace() : String(describing: type)
let typeName = namespaced ? sanitizeNamespace(ofTypeName: String(reflecting: type)) : String(describing: type)
switch generics {
case .keep:
return typeName
case .remove:
return typeName.replacingGenericParameters("")
return replaceGenericParameters(inTypeName: typeName,
withReplacement: "")
case .customViewPlaceholder:
let parameters = ViewType.customViewGenericsPlaceholder
return typeName.replacingGenericParameters(parameters)
return replaceGenericParameters(inTypeName: typeName,
withReplacement: parameters)
}
}
}

private extension String {
func sanitizingNamespace() -> String {
var str = self

let pattern = "\\.\\(unknown context at ..........\\)"
// swiftlint:disable force_try
let regex = try! NSRegularExpression(pattern: pattern)
// swiftlint:enable force_try
let range = NSRange(location: 0, length: str.utf16.count)
str = regex.stringByReplacingMatches(
in: str,
options: [],
range: range,
withTemplate: "")

// For Objective-C classes String(reflecting:) sometimes adds the namespace __C, drop it too
str = str.replacingOccurrences(of: "<__C.", with: "<")

return str
}

func replacingGenericParameters(_ replacement: String) -> String {
guard let start = self.firstIndex(of: "<")
else { return self }
var balance = 1
var current = self.index(after: start)
while balance > 0 && current < endIndex {
let char = self[current]
if char == "<" { balance += 1 }
if char == ">" {
guard let indexOfPreviousChar = index(
current, offsetBy: -1, limitedBy: startIndex)
else { return self }
let previousChar = self[indexOfPreviousChar]
if previousChar == "-" {
// We've found the "->" arrow for a closure type. Ignore this ">".
} else { balance -= 1 }
}
current = self.index(after: current)
}
if balance == 0 {
return String(self[..<start]) + replacement +
String(self[current...]).replacingGenericParameters(replacement)
}
return self
}
}

// MARK: - Attributes lookup

@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
public extension Inspector {

/**
Use this function to lookup the struct content:
```
(lldb) po Inspector.print(view) as AnyObject
```
Use this function to lookup the struct content:
```
(lldb) po Inspector.print(view) as AnyObject
```
*/
static func print(_ value: Any) -> String {
let tree = attributesTree(value: value, medium: .empty, visited: [])
return typeName(value: value) + print(tree, level: 1)
}

fileprivate static func print(_ value: Any, level: Int) -> String {
let prefix = Inspector.newline(value: value)
if let array = value as? [Any] {
Expand All @@ -180,11 +241,11 @@ public extension Inspector {
}
return prefix + String(describing: value) + "\n"
}

fileprivate static func indent(level: Int) -> String {
return Array(repeating: " ", count: level).joined()
}

private static func newline(value: Any) -> String {
let needsNewLine: Bool = {
if let array = value as? [Any] {
Expand All @@ -194,7 +255,7 @@ public extension Inspector {
}()
return needsNewLine ? "\n" : ""
}

private static func attributesTree(value: Any, medium: Content.Medium, visited: [AnyObject]) -> Any {
var visited = visited
if type(of: value) is AnyClass {
Expand Down Expand Up @@ -261,23 +322,23 @@ fileprivate extension Array {

@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
internal extension Inspector {

static func viewsInContainer(view: Any, medium: Content.Medium) throws -> LazyGroup<Content> {
let unwrappedContainer = try Inspector.unwrap(content: Content(view, medium: medium.resettingViewModifiers()))
guard Inspector.isTupleView(unwrappedContainer.view) else {
return LazyGroup(count: 1) { _ in unwrappedContainer }
}
return try ViewType.TupleView.children(unwrappedContainer)
}

static func isTupleView(_ view: Any) -> Bool {
return Inspector.typeName(value: view, generics: .remove) == ViewType.TupleView.typePrefix
}

static func unwrap(view: Any, medium: Content.Medium) throws -> Content {
return try unwrap(content: Content(view, medium: medium))
}

// swiftlint:disable cyclomatic_complexity
static func unwrap(content: Content) throws -> Content {
switch Inspector.typeName(value: content.view, generics: .remove) {
Expand Down Expand Up @@ -310,9 +371,9 @@ internal extension Inspector {
}
}
// swiftlint:enable cyclomatic_complexity

static func guardType(value: Any, namespacedPrefixes: [String], inspectionCall: String) throws {

var typePrefix = typeName(type: type(of: value), namespaced: true, generics: .remove)
if typePrefix == ViewType.popupContainerTypePrefix {
typePrefix = typeName(type: type(of: value), namespaced: true)
Expand Down

0 comments on commit 1be47b7

Please sign in to comment.