From 5f7cd978e833404a43a0fe260d52690a55b97bc2 Mon Sep 17 00:00:00 2001 From: antranapp Date: Mon, 16 Sep 2024 17:05:06 +0800 Subject: [PATCH 1/4] Optimise getting list of scenarios --- Sources/Scenarios/Scenario/Scenario.swift | 4 +- Sources/Scenarios/Scenario/ScenarioId.swift | 59 +++++++++++++++++---- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Sources/Scenarios/Scenario/Scenario.swift b/Sources/Scenarios/Scenario/Scenario.swift index 96c9977..5372d23 100644 --- a/Sources/Scenarios/Scenario/Scenario.swift +++ b/Sources/Scenarios/Scenario/Scenario.swift @@ -5,6 +5,8 @@ import ObjectiveC import UIKit +@objc public protocol ScenarioMarker {} + public protocol IdentifiableType: AnyObject { static var id: String { get } } @@ -27,7 +29,7 @@ public struct ScenarioInfo { } } -public protocol BaseScenario: IdentifiableType { +public protocol BaseScenario: IdentifiableType, ScenarioMarker { static var name: String { get } static var nameForSorting: String { get } static var kind: ScenarioKind { get } diff --git a/Sources/Scenarios/Scenario/ScenarioId.swift b/Sources/Scenarios/Scenario/ScenarioId.swift index fd91b46..5dc4a22 100644 --- a/Sources/Scenarios/Scenario/ScenarioId.swift +++ b/Sources/Scenarios/Scenario/ScenarioId.swift @@ -37,20 +37,57 @@ public struct ScenarioId: CaseIterable, Hashable, Identifiable, RawRepresentable public static func == (lhs: ScenarioId, rhs: ScenarioId) -> Bool { lhs.scenarioType.id == rhs.scenarioType.id } - + public static let allCases: [ScenarioId] = { - var count: UInt32 = 0 - let classes = objc_copyClassList(&count) - let buffer = UnsafeBufferPointer(start: classes, count: Int(count)) - return Array( - (0 ..< Int(count)) - .lazy - .compactMap { buffer[$0] as? Scenario.Type } - .map { ScenarioId(withType: $0) } - ) - .sorted { $0.scenarioType.name < $1.scenarioType.name } + Array(_allCases) + }() + + public static let _allCases: some Sequence = { +// public static let allCases: [ScenarioId] = { +// var count: UInt32 = 0 +// let classes = objc_copyClassList(&count) +// let buffer = UnsafeBufferPointer(start: classes, count: Int(count)) +// return Array( +// (0 ..< Int(count)) +// .lazy +// .compactMap { buffer[$0] as? Scenario.Type } +// .map { ScenarioId(withType: $0) } +// ) +// .sorted { $0.scenarioType.name < $1.scenarioType.name } + // AnyClass.init seems to register new objective-C class the first time it is called. + // + // In order for the count variable to reserve enough capacity, we call this method once + // so that any new classes are registered to the runtime. + _ = [AnyClass](unsafeUninitializedCapacity: Int(1)) { buffer, initialisedCount in + initialisedCount = 0 + } + + // Improved thanks to some hints from https://stackoverflow.com/a/54150007 + let count = objc_getClassList(nil, 0) + let classes = [AnyClass](unsafeUninitializedCapacity: Int(count)) { buffer, initialisedCount in + let autoreleasingPointer = AutoreleasingUnsafeMutablePointer(buffer.baseAddress) + initialisedCount = Int(objc_getClassList(autoreleasingPointer, count)) + } + + return classes + .lazy + // The `filter` is necessary. Without it we may crash. + // + // The cast using `as?` calls some objective-c methods on the type to check for conformance. But certain + // system types do not implement that method and would cause a crash (possible bug in the runtime?). + // + // `class_conformsToProtocol` is safe to call on all types, so we use it to filter down to “our” classes + // we try to cast them. + .filter { class_inherited_conformsToProtocol($0, ScenarioMarker.self) } + .compactMap { $0 as? Scenario.Type } + .map { ScenarioId(withType: $0) } }() +} +private func class_inherited_conformsToProtocol(_ cls: AnyClass, _ p: Protocol) -> Bool { + if class_conformsToProtocol(cls, p) { return true } + guard let sup = class_getSuperclass(cls) else { return false } + return class_inherited_conformsToProtocol(sup, p) } public protocol ScenarioCategory { From 5060fadb924b3b9e7ce0f63d72967bc439476f1c Mon Sep 17 00:00:00 2001 From: antranapp Date: Mon, 16 Sep 2024 17:10:37 +0800 Subject: [PATCH 2/4] Fix github automation --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bccc2be..24c6e0b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: # xcrun simctl list devices 15.4 - name: Boot simulator - run: xcrun simctl boot "iPhone 13 Pro" + run: xcrun simctl boot "iPhone 15 Pro" - name: Run Internal tests run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Internal" -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" From ad8a9fc4088c6952efc1fe83656acadccbd2c7ae Mon Sep 17 00:00:00 2001 From: antranapp Date: Mon, 16 Sep 2024 17:45:00 +0800 Subject: [PATCH 3/4] Fix github action --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24c6e0b..3b7813d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,13 +30,13 @@ jobs: run: xcrun simctl boot "iPhone 15 Pro" - name: Run Internal tests - run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Internal" -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" + run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Internal" -destination "platform=iOS Simulator,name=iPhone 15 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO - name: Run Production tests - run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Production" -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" + run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Production" -destination "platform=iOS Simulator,name=iPhone 15 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO - name: Run Internal (SwiftUI) tests - run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Internal" -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" + run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Internal" -destination "platform=iOS Simulator,name=iPhone 15 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO - name: Run Production (SwiftUI) tests - run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Production" -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" + run: xcodebuild clean build test -workspace Scenarios.xcworkspace -scheme "Sample-Production" -destination "platform=iOS Simulator,name=iPhone 15 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO From b215b057f66baeddcc20e8053d263de808c39b40 Mon Sep 17 00:00:00 2001 From: antranapp Date: Mon, 16 Sep 2024 17:50:57 +0800 Subject: [PATCH 4/4] Remove unsused code --- Sources/Scenarios/Scenario/ScenarioId.swift | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Sources/Scenarios/Scenario/ScenarioId.swift b/Sources/Scenarios/Scenario/ScenarioId.swift index 5dc4a22..9255c2c 100644 --- a/Sources/Scenarios/Scenario/ScenarioId.swift +++ b/Sources/Scenarios/Scenario/ScenarioId.swift @@ -6,6 +6,7 @@ import Foundation import ObjectiveC import SwiftUI +// Highly inspried from https://github.com/zuhlke/Support public struct ScenarioId: CaseIterable, Hashable, Identifiable, RawRepresentable, Codable { public var scenarioType: Scenario.Type @@ -43,17 +44,6 @@ public struct ScenarioId: CaseIterable, Hashable, Identifiable, RawRepresentable }() public static let _allCases: some Sequence = { -// public static let allCases: [ScenarioId] = { -// var count: UInt32 = 0 -// let classes = objc_copyClassList(&count) -// let buffer = UnsafeBufferPointer(start: classes, count: Int(count)) -// return Array( -// (0 ..< Int(count)) -// .lazy -// .compactMap { buffer[$0] as? Scenario.Type } -// .map { ScenarioId(withType: $0) } -// ) -// .sorted { $0.scenarioType.name < $1.scenarioType.name } // AnyClass.init seems to register new objective-C class the first time it is called. // // In order for the count variable to reserve enough capacity, we call this method once