Last active
August 13, 2018 15:16
-
-
Save marcpalmer/984048536937eb332851e87c3f862728 to your computer and use it in GitHub Desktop.
An example of how you might iterate over a bunch of discrete features that provide similar functionality, such as photo filters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Demo of iterating over subfeatures that all have a similar action and possibly performing that action. | |
/// Type-eraser for the action type so we can perform it irresepective of action type | |
struct AnyConditionalActionBinding<InputType, PresenterType> { | |
let _perform: (_ input: InputType, _ presenter: PresenterType) -> Bool | |
init<FeatureType, ActionType>(_ actionBinding: ConditionalActionBinding<FeatureType, ActionType>) where ActionType.InputType == InputType, ActionType.PresenterType == PresenterType { | |
_perform = { (_ input: InputType, _ presenter: PresenterType) -> Bool in | |
if let request = actionBinding.request() { | |
request.perform(input: input, presenter: presenter) | |
return true | |
} else { | |
return false | |
} | |
} | |
} | |
/// Call the closure we kept that we know accepts the args we receive here, without knowing the type | |
func performIfPossible(input: InputType, presenter: PresenterType) -> Bool { | |
return _perform(input, presenter) | |
} | |
} | |
/// This is whatever the filter actions need in order to perform | |
class ImageInfo: FlintLoggable { | |
/// let sourceImage: UIImage | |
} | |
/// This is whatever is needed to present the filter actions UI | |
protocol ImagePresenter { | |
} | |
/// The app's filter features group | |
class FiltersFeature: FeatureGroup { | |
static var subfeatures: [FeatureDefinition.Type] = [ | |
SolarizeFilterFeature.self, | |
NoirFilterFeature.self | |
] | |
} | |
protocol ImageFilterAction: Action where InputType: ImageInfo { | |
} | |
/// The protocol defining the way to uniformly access the apply action across any filter feature. | |
/// Filter features would have to conform to this instead of ConditionalFeature | |
protocol FilterFeature: ConditionalFeature { | |
/// We use this to access the type-erased apply action that takes specific arg types we define here | |
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> { get } | |
} | |
/// An example solarize filter feature | |
final class SolarizeFilterFeature: FilterFeature { | |
typealias ApplyActionType = ApplySolarizeFilterAction | |
static let description: String = "Solarize Filter" | |
static let apply = action(ApplySolarizeFilterAction.self) | |
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> = AnyConditionalActionBinding(apply) | |
static func constraints(requirements: FeatureConstraintsBuilder) { | |
// IAP or whatevs | |
} | |
static func prepare(actions: FeatureActionsBuilder) { | |
actions.declare(apply) | |
} | |
} | |
/// An example noir filter feature | |
final class NoirFilterFeature: FilterFeature { | |
typealias ApplyActionType = ApplyNoirFilterAction | |
static let description: String = "Noir Filter" | |
static let apply = action(ApplyNoirFilterAction.self) | |
static var applyAction: AnyConditionalActionBinding<ImageInfo, ImagePresenter> = AnyConditionalActionBinding(apply) | |
static func constraints(requirements: FeatureConstraintsBuilder) { | |
// IAP or whatevs | |
} | |
static func prepare(actions: FeatureActionsBuilder) { | |
actions.declare(apply) | |
} | |
} | |
/// An action to apply the solarize filter | |
final class ApplySolarizeFilterAction: ImageFilterAction { | |
typealias InputType = ImageInfo | |
typealias PresenterType = ImagePresenter | |
static func perform(context: ActionContext<ImageInfo>, presenter: ImagePresenter, completion: @escaping (ActionPerformOutcome) -> Void) { | |
// Do fancy filter stuff | |
} | |
} | |
/// An action to apply the noir filter | |
final class ApplyNoirFilterAction: ImageFilterAction { | |
typealias InputType = ImageInfo | |
typealias PresenterType = ImagePresenter | |
static func perform(context: ActionContext<ImageInfo>, presenter: ImagePresenter, completion: @escaping (ActionPerformOutcome) -> Void) { | |
// Do fancy filter stuff | |
} | |
} | |
/// A stub so we can fake calling the actions | |
class FakeImagePresenter: ImagePresenter { } | |
extension FeatureDefinition { | |
static var applyFilterWorkaround: AnyConditionalActionBinding<ImageInfo, ImagePresenter>? { return nil } | |
} | |
extension FeatureDefinition where Self: FilterFeature { | |
static var applyFilterWorkaround: AnyConditionalActionBinding<ImageInfo, ImagePresenter>? { return applyAction } | |
} | |
/// An example of how you'd iterate over these filter features and perform them | |
class FilterEnumeratorLogic { | |
func test() { | |
var filterInfos: [String:AnyConditionalActionBinding<ImageInfo, ImagePresenter>] = [:] | |
for feature in FiltersFeature.subfeatures { | |
guard let applyAction = feature.applyFilterWorkaround else { | |
fatalError("Can only have filter subfeatures") | |
} | |
print("Filter: \(feature.description)") | |
filterInfos[feature.description] = applyAction | |
} | |
// Show the filters, but we'll just pretend we choose one | |
let selectedFilter = filterInfos.first! | |
let filterActionToApply = selectedFilter.value | |
let possible = filterActionToApply.performIfPossible(input: ImageInfo(), presenter: FakeImagePresenter()) | |
print("Filter \(selectedFilter.key) was permitted: \(possible)") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment