Dynamic extensible protocols

Hello, I need your advice.

I try build analytics service supported extension by protocols.

import Foundation

// MARK: - Prototype

// Describe what service doing.
protocol AnalyticsServiceProtocol {
    func report(info: String)
}

// Extend our analytics service with "plugin" for send report about "requests".
protocol RequestsAnalyticsServiceProtocol {
    func reportRequestInfo(info: String)
}
extension AnalyticsServiceProtocol where Self: RequestsAnalyticsServiceProtocol {
    func reportRequestInfo(info: String) {
        report(info: info)
    }
}

// Concrete type which realize method report
struct AnalyticsService: AnalyticsServiceProtocol {
    func report(info: String) {
        print(info)
    }
}
// Without this extension AnalyticsService can't be casted to RequestsAnalyticsServiceProtocol
extension AnalyticsService: RequestsAnalyticsServiceProtocol {}


// MARK: - Usage

let analyticsService: AnalyticsServiceProtocol = AnalyticsService()
analyticsService.report(info: "some helpful information here")

let requestAnalyticsService: RequestsAnalyticsServiceProtocol? = analyticsService as? RequestsAnalyticsServiceProtocol
requestAnalyticsService?.reportRequestInfo(info: "some helpful information about request here")

I have trouble (may be this trouble in my imaginations about Protocols) with typecast to Specialized protocol analyticsService as? RequestsAnalyticsServiceProtocol this return nil :)

Swift does NOT conform types to protocols for you (with very few exceptions), even if they already have all the required functions. You need to explicitly spell the conformance.

That said, your model is better expressed by using one protocol with default implementation.

protocol AnalyticsServiceProtocol {
  // Required functions
  func report(info: String)

  // Default implementation provided
  func reportRequestInfo(info: String)
}

extension AnalyticsServiceProtocol {
  // Default implementation
  // Conforming types can override this by creating functions with the same signature.
  func reportRequestInfo(info: String) {
    report(info: info)
  }
}

I want create basic Protocol for AnalyticsService with report(info: String) and plug-in-able another protocols which support specific messages for this AnalyticsService

for example:

// In general:
// we want use DI / ServiceLocation
di.register(AnalyticsServiceProtocol.self, factory: { GoogleAnalyticsService() })

// we have default realisation of AnalyticsServiceProtocol
let analyticsService: AnalyticsServiceProtocol = di.resolve()
analyticsService.report(info: "Use Google Analytics")

// In Specific situtation
// in other place code We need to specified which plugin to be used
di.register(RequestAnalyticsServiceProtocol.self, factory: { 
    return $0.resolve(AnalyticsServiceProtocol.self) as? RequestAnalyticsServiceProtocol
})

// we have specified realisation  of AnalyticsServiceProtocol here
let requestAnalyticsService: RequestAnalyticsServiceProtocol = di.resolve()
requestAnalyticsService.reportRequestInfo(info: "request with url return something" )

That is what I mean :)

At best, you'd have RequestAnalyticsServiceProtocol as a wrapping struct, or have GoogleAnalyticsService conform to RequestAnalyticsServiceProtocol under the hood so the dynamic casting as? will succeed (as said, you need to explicitly type that somewhere).

Then again, if all AnalyticsServiceProtocol should conform RequestAnalyticsServiceProtocol, there's no reason to make them separate type (maybe, because they're in different modules?).

1 Like

Oh, I suppose that not all services should support RequestAnalyticsServiceProtocol.
If we decide use more than one systems we need to have selection of Specific messages for some of that system.
And, I thought use some of systems with Widget too. Where we don't need support all analytics messages.

.. And of course we write code not for only one situation, and if this will have better result, it can be moved to pluggable module.

.. At now I have already worked system with Concrete type conforming, but I want testable or replaceable variant. Where we can determine own realisation of AnalyticsServiceProtocol for test purpose for example.

Not sure I understand the requirement, but most likely it's not the job of protocols. I'd still suggest that you try to use a wrapper instead:

struct RequestsAnalyticsService {
  var base: AnalyticsServiceProtocol

  func reportRequestInfo(info: String) { ... }
}
1 Like

Thanks for advice, I will try to rethinking my architecture of this service )
But in this variant we can't provide custom realisation of RequestAnalyticsServiceProtocol if we want to do something specific with it.