Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift India Conf 2019: Hacking 
Rule Systems & State Machines 
with Apple's GameplayKit Framework

Swift India Conf 2019: Hacking 
Rule Systems & State Machines 
with Apple's GameplayKit Framework

Speaker: Srikanth KV, Senior iOS Engineer, PhonePe
LinkedIn: https://www.linkedin.com/in/srikanthvkabadi/

Bio: Srikanth is a Senior iOS Engineer at PhonePe and has been building iOS apps since 2011. Started working on Swift right from its inception. A great admirer of Swift because of its power and intuitiveness. Very keen on learning and using new technologies. Has handson experience with building 3D apps with SceneKit, small 2D apps with SpriteKit, AR app with ARKit and VR app with Unity. Has worked extensively on cross-platform frameworks like React Native and JUCE.

Abstract: Don't go with the name. It has nothing to do with "Games". Solve problems which are as simple as Text Validator to as complex as Payments System which has "n" number of states and "m" number of rules to evaluate.

 Also provides better readability, scalability, and reusability.

Example Repo: https://github.com/SrikanthKabadi/GameplayKit

Eeb061c8b2816b771920da1b3e7904a3?s=128

Swift India

July 28, 2019
Tweet

Transcript

  1. Hacking 
 Rule Systems & State Machines 
 with GameplayKit

    - Nothing to do with "Games" Srikanth K V PhonePe Private Limited
  2. What does it solve? Solve problems which is as simple

    as Text Validator to as complex as Payments System which has "n" number of states and "m" number of rules to evaluate.
 
 Also provides better readability, scalability and reusability.
  3. Introduction - Rule System (GKRuleSystem) - State Machine (GKStateMachine) -

    Combining Rule System with State Machine - Usecases
  4. GKRuleSystem

  5. GKRuleSystem - Rules (GKRule)

  6. GKRuleSystem - Rules (GKRule) - Input data (Dictionary)

  7. GKRuleSystem - Rules (GKRule) - Input data (Dictionary) - Fact

    (Conclusion)
  8. GKRuleSystem - Rules (GKRule) - Input data (Dictionary) - Fact

    (Conclusion) - Evaluate
  9. Example enum PasswordError: Error { case passwordsDontMatch case notLongEnough case

    uppercaseMissing case lowerCaseMissing case noNumbers case spacesNotAllowed } enum ValidationResult { case valid case invalid(error: PasswordError) }
  10. Example enum PasswordError: Error { case passwordsDontMatch case notLongEnough case

    uppercaseMissing case lowerCaseMissing case noNumbers case spacesNotAllowed } enum ValidationResult { case valid case invalid(error: PasswordError) }
  11. Wrapper class Fact: NSObject { typealias EvaluationBlock = (_ system:

    GKRuleSystem) -> Bool let error: PasswordError let evaluationBlock: EvaluationBlock init(error: PasswordError, evaluationBlock: @escaping EvaluationBlock) { self.error = error self.evaluationBlock = evaluationBlock } }
  12. Creating Rule class FactRule: GKRule { let fact: Fact init(fact:

    Fact) { self.fact = fact super.init() } override func evaluatePredicate(in system: GKRuleSystem) -> Bool { return fact.evaluationBlock(system) } override func performAction(in system: GKRuleSystem) { system.assertFact(fact) } }
  13. class PasswordValidator { } private let lengthFact = Fact(error: .notLongEnough)

    { (system: GKRuleSystem) in guard let password = system.state["password"] as? String, password.count >= 8 else { return false } return true }
  14. private let passwordsMatchFact = Fact(error: .passwordsDontMatch) { system in guard

    let password = system.state["password"] as? String, let confirmedPass = system.state["confirmedPassword"] as? String, password == confirmedPass else { return false } return true }
  15. Password Validator

  16. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] Password Validator
  17. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] /* create rule system and provide all the facts which we want to evaluate */ let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) Password Validator
  18. func validatePasswords(password: String, confirmedPassword: String) -> ValidationResult { // create

    the facts let facts = [lengthFact, uppercaseLetterFact, lowercaseLetterFact, numberFact, noWhiteSpaceFact, passwordsMatchFact] /* create rule system and provide all the facts which we want to evaluate */ let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) // add the state to the rule system ruleSystem.state["password"] = password ruleSystem.state["confirmedPassword"] = confirmedPassword // evaluate ruleSystem.evaluate() } Password Validator
  19. // extract the errors from the facts after // evaluation

    let erroredFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 } let errors = erroredFacts.map { $0.error } if !errors.isEmpty { return .invalid(errors: errors) } else { return .valid } Extracting Result
  20. // extract the errors from the facts after // evaluation

    let erroredFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 } let errors = erroredFacts.map { $0.error } if !errors.isEmpty { return .invalid(errors: errors) } else { return .valid } Extracting Result
  21. GKStateMachine

  22. GKStateMachine - States (GKState)

  23. GKStateMachine - States (GKState) - Life Cycle

  24. State Machine Example - Red - Yellow - Green

  25. State Machine Example - Red - Yellow - Green

  26. class Red: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  27. class Red: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  28. class Green: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  29. class Green: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Yellow.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  30. class Yellow: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Red.Type || stateClass is Green.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  31. class Yellow: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass is Red.Type || stateClass is Green.Type } override func didEnter(from previousState: GKState?) { } override func willExit(to nextState: GKState) { } }
  32. Combo

  33. GKStateMachine Combo

  34. GKStateMachine Combo State 1 State 2 State 3

  35. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1
  36. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  37. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  38. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  39. GKStateMachine Combo State 1 State 2 State 3 Rule 1

    Rule 2 GKRuleSystem 1 Rule 1 Rule 2 GKRuleSystem 2
  40. Usecase

  41. Usecase Init 21/07/2019

  42. Usecase Init 21/07/2019

  43. Usecase Init 21/07/2019

  44. Usecase Init 21/07/2019

  45. Init State + Fraud Check System

  46. Init Init State + Fraud Check System

  47. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Init State + Fraud Check System
  48. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Init State + Fraud Check System
  49. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Init State + Fraud Check System
  50. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Yes Init State + Fraud Check System
  51. Init Amount Threshold Check Transaction Frequency Check Device
 Check Location


    Check Fraud Check System Pending Failed Yes No Init State + Fraud Check System
  52. class InitState: GKState { override func isValidNextState(_ stateClass: AnyClass) ->

    Bool { return stateClass == PendingState.Type || stateClass == FailedState.Type } override func didEnter(from previousState: GKState?) { } } Init Pending Failed Yes No
  53. override func didEnter(from previousState: GKState?) { let facts = [locationFact,

    deviceVerificationFact, amountThresholdFact, transactionRateFact] let ruleSystem = GKRuleSystem() ruleSystem.add(facts.map(FactRule.init)) ruleSystem.state["locationInfo"] = data.locationInfo ruleSystem.state["deviceInfo"] = data.deviceInfo ruleSystem.state["amountInfo"] = data.amountInfo ruleSystem.state["transactionRate"] = data.transactionRate ruleSystem.evaluate() } Amount Threshold Check Transaction Frequency Check Device
 Check Location
 Check Fraud Check System
  54. let erroredFraudFacts = facts.filter { ruleSystem.grade(forFact: $0) == 0 }

    if erroredFraudFacts.count > 0 { stateMachine?.enter(FailedState.self) } else { stateMachine?.enter(PendingState.self) } Reading the Result
  55. - Offer Rule System - Check for offer window -

    Minimum Amount Check - Other set of rules - Order Completion - Book a cab - Order food Other Usecases
  56. Advantages - Adding new rule - Adding new state -

    Reusing the same rule in multiple usecases/ states - App transition is always under control.
  57. Thank You Credits
 
 Abhijit K G
 Aditya Rohan