How to proper inject dependency using swinject - ios

I'm trying to inject dependency using Swinject, and I have no clue what I'm doing wrong.
I have protocol, that handles registring user.
protocol AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) }
and a class that conforms to this protocol make all the logic:
class AuthService: AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) {
let lowerCaseMail = email.lowercased()
let body: [String: Any] = [
"email": lowerCaseMail,
"password" : password
]
Alamofire.request(URL_REGISTER, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseString { (response) in
if response.result.error == nil {
completion(true)
} else {
completion(false)
debugPrint(response.result.error as Any)
}
}
}
}
so, in AppDelegate we register container and it looks like:
let container = Container() { container in
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
return controller
}
}
but in CreateAccountVC authService is empty. Any ideas how can i do it?
CreateAccountVC is a subclass of ViewController, i have try'ed it by property, and constructors, but it's nil all the time.

Check your code:
var container : Container {
let container = Container()
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
print(r.resolve(AuthServiceProtocol.self))
return controller
}
return container
}
You have computed property and every time you call it, it creates a NEW Container object.
Refactor your code to have a single Container and I believe you will be good to go.
EDIT:
Here's a working code snippet.
Below is a small wrapper class to abstract concrete DI service (in case Swinject is one day replace by something else):
import Swinject
public class ConfigurationProvider {
// Currently using Swinject
private let backingService = Container()
// Singleton
public static let shared = ConfigurationProvider()
// Hidden initializer
private init() {}
// MARK: - Bind / Resolve
public func bind<T>(interface: T.Type, to assembly: T) {
backingService.register(interface) { _ in assembly }
}
public func resolve<T>(interface: T.Type) -> T! {
return backingService.resolve(interface)
}
}
// Extension methods to ignore 'shared.' call, like:
// ConfigurationProvider.bind(interface: IAssembly, to: Assembly())
// ConfigurationProvider.resolve(interface: IAssembly)
public extension ConfigurationProvider {
static func bind<T>(interface: T.Type, to assembly: T) {
ConfigurationProvider.shared.bind(interface: interface, to: assembly)
}
static func resolve<T>(interface: T.Type) -> T! {
return ConfigurationProvider.shared.resolve(interface: interface)
}
}
Usage:
class RSAuthLoginModuleAssembly: IAuthLoginModuleAssembly {
}
// Register:
ConfigurationProvider.bind(interface: IAuthLoginModuleAssembly.self, to: ConcreteAuthLoginModuleAssembly())
// Resolve:
guard let assembly = ConfigurationProvider.resolve(interface: IAuthLoginModuleAssembly.self) else {
throw NSError(domain: "Assembly cannot be nil", code: 999, userInfo: nil)
}

Related

Swift generic types in protocol issue. Protocol can only be used as a generic constraint

I have created a protocol for my services:
protocol SaveCoreDataObjectServiceProtocol {
associatedtype T
func getPending() -> T?
func deletePending(with completion: #escaping (ResultInfo<Bool>) -> Void)
}
I have a base class that will be a parent class of any child services:
class BaseSaveCoreDataObjectService<T: DynamicObject>: SaveCoreDataObjectServiceProtocol {
func getPending() -> T? {
let managedObject = try? CoreStore.fetchOne(
From<T>()
.where(
format: "%K == %#",
#keyPath(BaseMO.pendingToSave),
NSNumber(value: true))
)
return managedObject
}
func deletePending(with completion: #escaping (ResultInfo<Bool>) -> Void) {
let managedObject = try? CoreStore.fetchOne(
From<T>()
.where(
format: "%K == %#",
#keyPath(BaseMO.pendingToSave),
NSNumber(value: true))
)
if let unwrappedManagedObject = managedObject {
CoreStore.perform(
asynchronous: { (transaction) -> Void in
transaction.delete(unwrappedManagedObject)
print("Deleted pending object id: ", (unwrappedManagedObject as? BaseMO)?.id ?? "Deleting pending object with no id issue.")
},
completion: { _ in
completion(ResultInfo.data(true))
}
)
}
} }
I have few service which just empty and inherited from BaseSaveCoreDataObjectService
class SaveWorkoutService : BaseSaveCoreDataObjectService<WorkoutEntity> {
}
class SaveExerciseService: BaseSaveCoreDataObjectService<ExerciseEntity> {
}
When I try to iterate them I get an error Protocol type 'DynamicObject' cannot conform to 'DynamicObject' because only concrete types can conform to protocols here:
func removeAllPendingCoreDataObjectsToSave() {
let saveObjectServices = [SaveExerciseService(), SaveWorkoutService()] as [BaseSaveCoreDataObjectService<DynamicObject>]
for saveObjectService in saveObjectServices {
saveObjectService.deletePending { (result) in
}
}
}
The idea is just to have basic parent service which includes some reusable function like getPending() deletePending() and etc and can return generic type.
If I change this
let saveObjectServices = [SaveExerciseService(), SaveWorkoutService()] as [BaseSaveCoreDataObjectService<DynamicObject>]
to this:
let saveObjectServices = [SaveExerciseService(), SaveWorkoutService()] as [SaveCoreDataObjectServiceProtocol]
I get this error:
Protocol 'SaveCoreDataObjectServiceProtocol' can only be used as a generic constraint because it has Self or associated type requirements

How to use one callback from singleton in multiple views

how to use onCompleted in multiple classes views.
UPDATED FULL CODE !!!
import Foundation
import Alamofire
class AudioSyncManager {
//var onDownloadStart: (()->())?
var onDownloadFinished: ((_ isSuccess: Bool)->())?
var onDownloadProgress: ((_ progress: Float)->())?
static let shared = AudioSyncManager()
private var downloadRequest: DownloadRequest?
private var isDownloading = false
var listData: [MainModel] = []
func doDownloding(onStarted: #escaping ()->()) {
if listData.count == 0 || isDownloading {
return
}
let firstModel = listData.first
if checkMp3FileExists(model: firstModel!) {
self.isDownloading = false
self.listData.removeFirst()
if self.listData.count > 0 {
self.doDownloding {}
}
return
}
let mp3URLString = MyHelper.MEDIA_URL_PREFIX + (firstModel?.link)!
let url = URL(string: mp3URLString)
let destination = DownloadRequest.suggestedDownloadDestination(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
//isDownloading = true
onStarted()
downloadRequest = Alamofire.download(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil, to: destination)
.downloadProgress { (progress) in
self.onDownloadProgress?(Float(progress.fractionCompleted))
}.response { (response) in
self.isDownloading = false
self.onDownloadFinished?(true)
if self.listData.count > 0 {
self.listData.removeFirst()
}
if self.listData.count > 0 {
self.doDownloding{}
}
}
}
func addSingleTask(mainModel: MainModel) {
listData.append(mainModel)
doDownloding{}
}
func addListTask(newList: [MainModel]) {
listData.append(contentsOf: newList)
doDownloding{}
}
}
POINT 1
You should be getting error in the below line
let static shared = Service()
because static keyword should come first then declaration.
static let shared = Service()
POINT 2
Implement the onDownload function with Completion Handler
func doDownload(onCompleted: #escaping ()->()) {
onCompleted()
}
Call the function as below
let service = Service.shared
service.doDownload { () in
print("Called in completion Handler")
}
for more detail about Closures go through the below link.
Closures
An example of remove the property onCompleted and make it a parameter to doDownload method:
class Service {
let static shared = Service()
func doDownload(onCompleted: (()->())?) {
//...
onCompleted?()
}
}
Your code in viewDidLoad looks good but could you re visit your singleton class and could you try running your code after adding a private init method calling super.init and see if your code works.

Using an enum in generic function

I want to create a generic function with an enum as generic parameters.
I have the following generic function:
func buildProvider<T>(service: T) -> RxMoyaProvider<T> {
let endpointClosure = { (target: T) -> Endpoint<T> in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
let endpoint = Endpoint<T>(URL: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
return endpoint
}
return RxMoyaProvider<T>(endpointClosure: endpointClosure)
}
I call it like this:
self.userProvider = buildProvider(service: UserService)
Below you see the declaration of userProvider:
var userProvider: RxMoyaProvider?
And below you see an example of UserService:
import Foundation
import Moya
enum UserService {
case login(qrKey: String, language: String, fcmToken: String)
}
extension UserService: TargetType {
var baseURL: URL {
let appConfig = AppConfig()
let hostname = try! appConfig.hostname()
return URL(string: hostname)!
}
var path: String {
switch self {
case .login(_,_,_):
return "/v1/login"
}
}
var method: Moya.Method {
switch self {
case .login(_,_,_):
return .post
}
}
var parameters: [String: Any]? {
switch self {
case .login(let qrKey, let language, let fcmToken):
let params = ["qr-key": qrKey, "language": language, "os": "ios", "fcm-token": fcmToken]
return params
}
}
var sampleData: Data {
switch self {
case .login(_, _, _):
return "".data(using: .utf8)!
}
}
var task: Task {
return .request
}
}
And I get the following error:
Cannot convert value of type 'UserService.Type' to expected argument type 'UserService'
Can someone help me with this?
Thanks !
I have no experience with Moya and can not know for sure how it works and what you're trying to achieve, but it seems like you're trying to pass type into function. If so you must do something like:
func buildProvider<T>(service: T.Type) -> RxMoyaProvider<T> {
...
return RxMoyaProvider<T>(endpointClosure: endpointClosure)
}
and call it with .self:
self.userProvider = buildProvider(service: UserService.self)
but it's not your only problem. How do you know without context that type <T> would have baseURL property:
let endpointClosure = { (target: T) -> Endpoint<T> in
let url = target.baseURL.appendingPathComponent(target.path).absoluteString
?
Seems like you've messed up with overall architecture, and I would strongly advice to reconsider it.

Mocking a class with Rx extensions

Im trying to achieve dependency injection with protocols in Swift :)
My system under test calls .rx.signIn() on the dependency, meaning that it has extended Reactive where Base : ConcreteObject.
Is it possible to setup a protocol ready to receive .rx calls?
Any small example or alternative would be greatly appreciated.
I had the same problem during the upgrade from RxSwift2 to RxSwift3. In RxSwift2 I simply used a protocol for the rx-methods in my production code (ex. rx_JSON()) and injected a simple mock object adhering to that protocol in my unit tests.
After a short detour into generics, associated type and type erasure I settled with the following setup, which closely matches my RxSwift2-setup:
For example to hide URLSession as the concrete implementation of my HTTP-calls, I created a protocol RemoteClient:
protocol RemoteClient {
func json(url: URL) -> Observable<Any>
}
Then in my production code I reference only that protocol:
class SomeService {
var remoteClient: RemoteClient
init(remoteClient: RemoteClient) {
self.remoteClient = remoteClient
}
func getSomeData(_ id: String) -> Observable<JSONDictionary> {
let urlString = "..."
return remoteClient
.json(url: URL(string: urlString)!)
.map { data in
guard let show = data as? JSONDictionary else {
throw ParsingError.json
}
return show
}
}
}
In my unit tests I created mock objects which adhere to the RemoteClient protocol:
class RemoteClientMock: RemoteClient {
var output: Any?
var verifyInput: ((URL) -> Void)?
func json(url: URL) -> Observable<Any> {
verifyInput?(url)
return Observable.create { observer in
if let data = self.output {
observer.on(.next(data))
}
observer.on(.completed)
return Disposables.create()
}
}
}
And inject them in the system under test:
class SomeServiceTest: TestCase {
let disposeBag = DisposeBag()
func testGetSomeData() {
// setup
let expectation = self.expectation(description: "request succeeded")
let remoteClientMock = RemoteClientMock()
remoteClientMock.verifyInput = { url in
XCTAssertEqual(URL(string: "some url"), url)
}
remoteClientMock.output = ["id": 4711]
let service = SomeService(remoteClient: remoteClientMock)
// exercise
let observable = service.getSomeData("4711")
// verify
observable
.subscribe(onNext: { data in
XCTAssertEqual(4711, data["id"] as? Int)
expectation.fulfill()
})
.addDisposableTo(disposeBag)
waitForExpectations(timeout: 1, handler: nil)
}
}
So I simply hide the rx-namespace behind my protocol. I do that for now by using an adapter:
let remoteClient = URLSessionRemoteClientAdapter(urlSession: URLSession.shared)
let service = SomeService(remoteClient: remoteClient)
struct URLSessionRemoteClientAdapter: RemoteClient {
let urlSession: URLSession
init(urlSession: URLSession) {
self.urlSession = urlSession
}
func json(url: URL) -> Observable<Any> {
return urlSession.rx.json(url: url)
}
}

Swift Generic Class

I have a class like this
// a.swift
public class ComServiceString<Result>: ComService<Result> {
override func execute() {
if (method == Method.GET) {
manager.GET(getUrl(),
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
self.onSuccessListener?(self.parseResult(responseObject.description))
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
println("Error: " + error.localizedDescription)
var errorSd = ComServiceError(error.localizedDescription)
if (operation.response != nil) {
errorSd.errorCode = operation.response.statusCode
}
self.onFailedListener?(errorSd)
}
)
}
}
func parseResult(result: String) -> Result? {
fatalError("This method need to be implemented")
}
}
and I extend it to a new class like this
// b.swift:
public class BranchListService<T>: ComServiceString<BranchListModel> {
override func parseResult(result: String) -> BranchListModel? {
let branchListMode = BranchListModel(stringResult: result)
return branchListMode
}
}
my BranchListModel looks like this
public class BranchListModel {
public var total = 0
public var stringResult = ""
init() {}
init (stringResult result: String) {
self.stringResult = result
if let jsonArray = JSONUtil.stringToJSONArray(result) {
if let resultObject = jsonArray[0] as? NSDictionary {
if let total = resultObject["total"] as? NSString {
self.total = total.integerValue;
}
}
}
}
}
but I got BAD_ACCESS error in this line:
let branchListMode = BranchListModel(stringResult: self.resultString)
inside parseResult function on b.swift. I'm still learning this language with trying to convert my java code to swift. Do you guys know what's wrong from the code?
I think there is a bug of the Swift compiler. I've tested a relatively-simplified code below and that crashed in the same way.
class ResultParser<T> {
func parse(result: String) -> T? { abort() }
}
class MyResultParser<T>: ResultParser<Int> {
override func parse(result: String) -> Int? {
return result.toInt()
}
}
let parser: ResultParser<Int> = MyResultParser<()>()
parser.parse("2525")
At the moment, the Swift compiler cannot correctly treat virtual functions directly receiving and/or returning values, whose type is generic. I guess the compiler regards T as a pointer (object) value whatever an actual substituted type is.
I've found a workaround to do nearly the same thing.
class Box<T> {
let raw: T
init(_ raw: T) { self.raw = raw }
}
class ResultParser<T> {
func parse(result: String) -> Box<T?> { abort() }
}
class MyResultParser<T>: ResultParser<Int> {
override func parse(result: String) -> Box<Int?> {
return Box(result.toInt())
}
}
In the code above, the returned value is wrapped in the Box<>. Box<> is an object, so that works no matter whether or not the compiler can tell value types from object types.

Resources