问题
When dealing with something like analytics I typically have an object that listens for Notification
s or calls methods on the object within business logic. This has always bugged me but I haven't been able to find a better pattern. For example, the code below has analytics scattered all over the place and clutters the business logic.
class SomeService {
private let analytics: AnalyticsGateway
func doAllTheThings() {
analytics.trackStart(with: context)
somethingApiClient.doSomething { error
guard error = nil else {
analytics.trackError(error)
analytics.trackFailure("doSomething", with: context)
}
analytics.trackSuccess("doSomething", with: context)
}
do {
try someOtherApiClient.doSomethingElse { [unowned self] mappedObject
guard let mappedObject = mappedObject else {
analytics.trackFailure("doSomethingElse", with: context)
return
}
guard validator.validate(mappedObject) else {
analytics.trackFailure("doSomethingElse", with: context)
return
}
self.notificationCenter.post(name: SomethingElseDidUpdateNotification,
object: self,
userInfo: info)
}
} catch {
analytics.trackError(error)
analytics.trackFailure("doSomethingElse", with: context)
}
}
}
What is a better approach to dealing with this kind of problem? I like the approach of listening to notifications/events, if they exist to update state throughout the app. However, adding notifications just for analytics seems almost as bad as calling into the analytics object directly (though still better since there's no coupling). There's still analytics-specific code scattered throughout your business logic.
I have done a little bit of aspect-oriented programming with Java in the past and it seems to solve some of this. Is there a way to do something similar in swift? I have found a couple of Objective-C AOP libraries, but neither appear to be maintained.
My current project uses RxSwift and I can do some of this with the events (onNext
, onError
, etc.)... when I'm using Observable
s. I'd prefer a better pattern that can be used everywhere, though. Example:
class SomeService {
func doSomethingElse() -> Observable<SomeObject> {
return Observable.create { observer in
someOtherApiClient.doSomethingElse { mappedObject
guard let mappedObject = mappedObject else {
throw ServiceError.mappedObjectIsNil
}
guard validator.validate(mappedObject) else {
throw ServiceError.validationFailed(mappedObject)
}
observer.onNext(mappedObject)
observer.onCompleted()
}
}
.instrument(for: "doSomethingElse", in: observable)
}
}
extension Observable where E == Mappable {
func instrument(for name: String, in observable: Observable<E>) -> Observable<E> {
return observable.do(onError: { (error) in
analytics.trackError(error)
analytics.trackFailure(name, with: context)
}, onCompleted: {
analytics.trackSuccess(name, with: context)
}, onSubscribed: {
analytics.trackStart(name, with: context)
})
}
}
来源:https://stackoverflow.com/questions/44853254/swift-approach-for-tangled-cross-cutting-code-logging-analytics-etc