Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/ios/DivKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
8C4DC3E82892946F000C0963 /* RuntimeTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4DC3E72892946F000C0963 /* RuntimeTestCase.swift */; };
8C54D9C728C0DBD60080826C /* regression_test_data in Resources */ = {isa = PBXBuildFile; fileRef = 8C54D9C628C0DBD60080826C /* regression_test_data */; };
8C54D9CE28C0E46D0080826C /* RegressionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C54D9CD28C0E46D0080826C /* RegressionView.swift */; };
8C64FEE52A5D7373008B1909 /* DivVariableTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C64FEE42A5D7373008B1909 /* DivVariableTrackerTests.swift */; };
8C6E40BE28AAA4F30038E107 /* LayoutKitSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6E40BA28AAA4F30038E107 /* LayoutKitSnapshotTest.swift */; };
8C6E40BF28AAA4F30038E107 /* SwitchableContainerSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6E40BB28AAA4F30038E107 /* SwitchableContainerSnapshotTest.swift */; };
8C6E40C028AAA4F30038E107 /* AnchorBlockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6E40BC28AAA4F30038E107 /* AnchorBlockTests.swift */; };
Expand Down Expand Up @@ -372,6 +373,7 @@
8C4DC3E72892946F000C0963 /* RuntimeTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeTestCase.swift; sourceTree = "<group>"; };
8C54D9C628C0DBD60080826C /* regression_test_data */ = {isa = PBXFileReference; lastKnownFileType = folder; name = regression_test_data; path = ../../../test_data/regression_test_data; sourceTree = "<group>"; };
8C54D9CD28C0E46D0080826C /* RegressionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegressionView.swift; sourceTree = "<group>"; };
8C64FEE42A5D7373008B1909 /* DivVariableTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DivVariableTrackerTests.swift; sourceTree = "<group>"; };
8C6DEA3F2866128C009956A1 /* . */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = .; sourceTree = "<group>"; };
8C6E40B128AAA47B0038E107 /* LayoutKitSnapshotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LayoutKitSnapshotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8C6E40BA28AAA4F30038E107 /* LayoutKitSnapshotTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutKitSnapshotTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -879,6 +881,7 @@
children = (
8CB960E328883B8300D16E47 /* DivTriggerTests.swift */,
8C7B1BE52865C01C0036EF4C /* DivVariablesStorageTest.swift */,
8C64FEE42A5D7373008B1909 /* DivVariableTrackerTests.swift */,
);
path = Variables;
sourceTree = "<group>";
Expand Down Expand Up @@ -1517,6 +1520,7 @@
8CB960E628883B8E00D16E47 /* Platform.swift in Sources */,
8C23F41829DDB3810069F3F7 /* EntityWithArrayOfNestedItems.swift in Sources */,
8C23F43729DDB3810069F3F7 /* EntityWithoutProperties.swift in Sources */,
8C64FEE52A5D7373008B1909 /* DivVariableTrackerTests.swift in Sources */,
8C23F44529DDB3810069F3F7 /* PropertyTests.swift in Sources */,
8C23F44629DDB3810069F3F7 /* SimplePropertiesTests.swift in Sources */,
8C23F43E29DDB3810069F3F7 /* DefaultValueTests.swift in Sources */,
Expand Down
27 changes: 17 additions & 10 deletions client/ios/DivKit/DivBlockModelingContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,27 @@ public struct DivBlockModelingContext {
public let stateInterceptors: [String: DivStateInterceptor]
private let variables: DivVariables
public let layoutDirection: LayoutDirection
public let debugParams: DebugParams
public let playerFactory: PlayerFactory?
public var childrenA11yDescription: String?
public weak var parentScrollView: ScrollView?
public let errorsStorage: DivErrorsStorage
private let variableTracker: DivVariableTracker?

var overridenWidth: DivOverridenSize?
var overridenHeight: DivOverridenSize?

public var expressionResolver: ExpressionResolver {
ExpressionResolver(
variables: variables,
errorTracker: { [weak errorsStorage] error in
errorsStorage?.add(DivBlockModelingRuntimeError(error, path: parentPath))
},
variableTracker: { [weak variableTracker] variables in
variableTracker?.onVariablesUsed(cardId: cardId, variables: variables)
}
)
}
public let debugParams: DebugParams
public let playerFactory: PlayerFactory?
public var childrenA11yDescription: String?
public weak var parentScrollView: ScrollView?
public let errorsStorage: DivErrorsStorage
var overridenWidth: DivOverridenSize?
var overridenHeight: DivOverridenSize?

public init(
cardId: DivCardID,
Expand All @@ -60,7 +66,8 @@ public struct DivBlockModelingContext {
childrenA11yDescription: String? = nil,
parentScrollView: ScrollView? = nil,
errorsStorage: DivErrorsStorage = DivErrorsStorage(errors: []),
layoutDirection: LayoutDirection = .system
layoutDirection: LayoutDirection = .system,
variableTracker: DivVariableTracker? = nil
) {
self.cardId = cardId
self.cardLogId = cardLogId
Expand All @@ -74,14 +81,14 @@ public struct DivBlockModelingContext {
self.divCustomBlockFactory = divCustomBlockFactory
self.flagsInfo = flagsInfo
self.fontProvider = fontProvider ?? DefaultFontProvider()
self.variables = variables
self.playerFactory = playerFactory
self.debugParams = debugParams
self.childrenA11yDescription = childrenA11yDescription
self.parentScrollView = parentScrollView
self.errorsStorage = errorsStorage
self.layoutDirection = layoutDirection

self.variables = variables
self.variableTracker = variableTracker

var extensionsHandlersDictionary = [String: DivExtensionHandler]()
extensionHandlers.forEach {
Expand Down
37 changes: 23 additions & 14 deletions client/ios/DivKit/DivKitComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public final class DivKitComponents {

private let timerStorage: DivTimerStorage
private let updateAggregator: RunLoopCardUpdateAggregator
private let updateCard: DivActionURLHandler.UpdateCardAction
private let variableTracker = DivVariableTracker()
private let disposePool = AutodisposePool()

public init(
Expand Down Expand Up @@ -64,6 +66,9 @@ public final class DivKitComponents {

safeAreaManager = DivSafeAreaManager(storage: variablesStorage)

updateAggregator = RunLoopCardUpdateAggregator(updateCardAction: updateCardAction ?? { _ in })
updateCard = updateAggregator.aggregate(_:)

let requestPerformer = requestPerformer ?? URLRequestPerformer(urlTransform: nil)

self.imageHolderFactory = imageHolderFactory
Expand All @@ -72,11 +77,6 @@ public final class DivKitComponents {
self.patchProvider = patchProvider
?? DivPatchDownloader(requestPerformer: requestPerformer)

let updateAggregator = RunLoopCardUpdateAggregator(updateCardAction: updateCardAction ?? { _ in
})
self.updateAggregator = updateAggregator
let updateCard: DivActionURLHandler.UpdateCardAction = updateAggregator.aggregate(_:)

weak var weakTimerStorage: DivTimerStorage?
weak var weakActionHandler: DivActionHandler?

Expand Down Expand Up @@ -123,13 +123,8 @@ public final class DivKitComponents {
weakActionHandler = actionHandler
weakTimerStorage = timerStorage

variablesStorage.changeEvents.addObserver { change in
switch change.kind {
case .global:
updateCard(.variable(.all))
case let .local(cardId, _):
updateCard(.variable(.specific([cardId])))
}
variablesStorage.changeEvents.addObserver { [weak self] event in
self?.onVariablesChanged(event: event)
}.dispose(in: disposePool)
}

Expand Down Expand Up @@ -193,7 +188,8 @@ public final class DivKitComponents {
debugParams: DebugParams = DebugParams(),
parentScrollView: ScrollView? = nil
) -> DivBlockModelingContext {
DivBlockModelingContext(
variableTracker.onModelingStarted(cardId: cardId)
return DivBlockModelingContext(
cardId: cardId,
stateManager: stateManagement.getStateManagerForCard(cardId: cardId),
blockStateStorage: blockStateStorage,
Expand All @@ -208,7 +204,8 @@ public final class DivKitComponents {
playerFactory: playerFactory,
debugParams: debugParams,
parentScrollView: parentScrollView,
layoutDirection: layoutDirection
layoutDirection: layoutDirection,
variableTracker: variableTracker
)
}

Expand All @@ -235,6 +232,18 @@ public final class DivKitComponents {
public func setTimers(divData: DivData, cardId: DivCardID) {
timerStorage.set(cardId: cardId, timers: divData.timers ?? [])
}

private func onVariablesChanged(event: DivVariablesStorage.ChangeEvent) {
switch event.kind {
case let .global(variables):
let cardIds = variableTracker.getAffectedCards(variables: variables)
if (!cardIds.isEmpty) {
updateCard(.variable(.specific(cardIds)))
}
case let .local(cardId, _):
updateCard(.variable(.specific([cardId])))
}
}
}

func makeImageHolderFactory(requestPerformer: URLRequestPerforming) -> ImageHolderFactory {
Expand Down
18 changes: 2 additions & 16 deletions client/ios/DivKit/Expressions/ExpressionLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,10 @@ public struct ExpressionLink<T> {
let validator: ExpressionValueValidator<T>?
let errorTracker: ExpressionErrorTracker?

init?(
expression: String,
validator: ExpressionValueValidator<T>?,
errorTracker: ExpressionErrorTracker? = nil,
resolveNested: Bool = true
) throws {
try self.init(
rawValue: "@{\(expression)}",
validator: validator,
errorTracker: errorTracker,
resolveNested: resolveNested
)
}

@usableFromInline
init?(
rawValue: String,
validator: ExpressionValueValidator<T>?,
validator: ExpressionValueValidator<T>? = nil,
errorTracker: ExpressionErrorTracker? = nil,
resolveNested: Bool = true
) throws {
Expand Down Expand Up @@ -63,10 +49,10 @@ public struct ExpressionLink<T> {
let value = String(currentValue[start...end])
if resolveNested, let link = try ExpressionLink<String>(
rawValue: value,
validator: nil,
errorTracker: errorTracker
) {
items.append(.nestedCalcExpression(link))
variablesNames.append(contentsOf: link.variablesNames)
} else {
let parsedCalcExpression = CalcExpression.parse(value)
items.append(.calcExpression(parsedCalcExpression))
Expand Down
67 changes: 34 additions & 33 deletions client/ios/DivKit/Expressions/ExpressionResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ public typealias ExpressionValueValidator<T> = (T) -> Bool
public typealias ExpressionErrorTracker = (ExpressionError) -> Void

public final class ExpressionResolver {
public typealias VariableTracker = (Set<DivVariableName>) -> Void

@usableFromInline
let variables: DivVariables
private let errorTracker: ExpressionErrorTracker
let variableTracker: VariableTracker

public init(
variables: DivVariables,
errorTracker: ExpressionErrorTracker? = nil
errorTracker: ExpressionErrorTracker? = nil,
variableTracker: @escaping VariableTracker = { _ in }
) {
self.variables = variables
self.errorTracker = {
DivKitLogger.error($0.description)
errorTracker?($0)
}
self.variableTracker = variableTracker
}

public func resolveString(expression: String) -> String {
Expand Down Expand Up @@ -60,6 +65,7 @@ public final class ExpressionResolver {
case let .value(val):
return resolveEscaping(val)
case let .link(link):
variableTracker(Set(link.variablesNames.map(DivVariableName.init(rawValue:))))
return evaluateStringBasedValue(
link: link,
initializer: initializer
Expand All @@ -69,7 +75,29 @@ public final class ExpressionResolver {
}
}

func resolveEscaping<T>(_ value: T?) -> T? {
func resolveNumericValue<T>(
expression: Expression<T>?
) -> T? {
switch expression {
case let .value(val):
return val
case let .link(link):
variableTracker(Set(link.variablesNames.map(DivVariableName.init(rawValue:))))
return evaluateSingleItem(link: link)
case .none:
return nil
}
}

@inlinable
func getVariableValue<T>(_ name: String) -> T? {
guard let value: T = variables[DivVariableName(rawValue: name)]?.typedValue() else {
return nil
}
return value
}

private func resolveEscaping<T>(_ value: T?) -> T? {
guard var value = value as? String, value.contains("\\") else {
return value
}
Expand Down Expand Up @@ -101,24 +129,7 @@ public final class ExpressionResolver {
return value as? T
}

@inlinable
func resolveNumericValue<T>(
expression: Expression<T>?
) -> T? {
switch expression {
case let .value(val):
return val
case let .link(link):
return evaluateSingleItem(
link: link
)
case .none:
return nil
}
}

@usableFromInline
func evaluateSingleItem<T>(link: ExpressionLink<T>) -> T? {
private func evaluateSingleItem<T>(link: ExpressionLink<T>) -> T? {
guard link.items.count == 1,
case let .calcExpression(parsedExpression) = link.items.first
else {
Expand Down Expand Up @@ -148,8 +159,7 @@ public final class ExpressionResolver {
}
}

@usableFromInline
func evaluateStringBasedValue<T>(
private func evaluateStringBasedValue<T>(
link: ExpressionLink<T>,
initializer: (String) -> T?
) -> T? {
Expand Down Expand Up @@ -186,8 +196,7 @@ public final class ExpressionResolver {
initializer: { $0 }
) {
let link = try? ExpressionLink<String>(
expression: expression,
validator: nil,
rawValue: "@{\(expression)}",
errorTracker: link.errorTracker,
resolveNested: false
)
Expand All @@ -211,14 +220,6 @@ public final class ExpressionResolver {
return validatedValue(value: result, validator: link.validator, rawValue: link.rawValue)
}

@inlinable
func getVariableValue<T>(_ name: String) -> T? {
guard let value: T = variables[DivVariableName(rawValue: name)]?.typedValue() else {
return nil
}
return value
}

private func validatedValue<T>(
value: T?,
validator: ExpressionValueValidator<T>?,
Expand All @@ -239,7 +240,7 @@ public final class ExpressionResolver {
expression: String,
initializer: (String) -> T?
) -> T? {
guard let expressionLink = try? ExpressionLink<T>(rawValue: expression, validator: nil) else {
guard let expressionLink = try? ExpressionLink<T>(rawValue: expression) else {
return nil
}
return resolveStringBasedValue(
Expand Down
9 changes: 5 additions & 4 deletions client/ios/DivKit/Expressions/Functions/ValueFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum ValueFunctions: String, CaseIterable {
extension ExpressionResolver {
fileprivate func getValueFunction<T>() -> GetOrDefault<T> {
{ name, fallbackValue in
self.variableTracker([DivVariableName(rawValue: name)])
guard let value = self.getValue(name) else {
return fallbackValue
}
Expand All @@ -81,11 +82,11 @@ extension ExpressionResolver {
}
}

fileprivate func getValueFunctionWithTransform<
T,
U
>(transform: @escaping (U) throws -> T) -> GetOrDefaultWithTransform<U, T> {
fileprivate func getValueFunctionWithTransform<T, U>(
transform: @escaping (U) throws -> T
) -> GetOrDefaultWithTransform<U, T> {
{ name, fallbackValue in
self.variableTracker([DivVariableName(rawValue: name)])
guard let value = self.getValue(name) else {
return try transform(fallbackValue)
}
Expand Down
4 changes: 2 additions & 2 deletions client/ios/DivKit/Variables/DivTriggersStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ extension DivTrigger {
extension Expression {
fileprivate var variablesNames: Set<DivVariableName> {
switch self {
case let .link(resolver):
return Set(resolver.variablesNames.map(DivVariableName.init(rawValue:)))
case let .link(link):
return Set(link.variablesNames.map(DivVariableName.init(rawValue:)))
case .value:
return []
}
Expand Down
Loading