Swinject Tutorial for iOS

5 minute read Published: 2024-02-22

Dependancy Injection

종속성 주입이 필요한 이유

시작하기

DI와 커플링

Networking and Parsing

  private func requestPrice()  {
    let bitcoin = Coinbase.bitcoin.path
    
    // 1. Make URL request
    guard let url = URL(string: bitcoin) else { return }
    var request = URLRequest(url: url)
    request.cachePolicy = .reloadIgnoringCacheData
    
    // 2. Make networking request
    let task = URLSession.shared.dataTask(with: request) { data, _, error in
      
      // 3. Check for errors
      if let error = error {
        print("Error received requesting Bitcoin price: \(error.localizedDescription)")
        return
      }
      
      // 4. Parse the returned information
      let decoder = JSONDecoder()

      guard let data = data,
            let response = try? decoder.decode(PriceResponse.self,
                                               from: data) else { return }
      
      print("Price returned: \(response.data.amount)")
      
      // 5. Update the UI with the parsed PriceResponse
      DispatchQueue.main.async { [weak self] in
        self?.updateLabel(price: response.data)
      }
    }
{
  "data": {
    "base": "BTC",
    "currency": "USD",
    "amount": "15840.01"
  }
}

Formatting

private func updateLabel(price: Price) {
  guard let dollars = price.components().dollars,
        let cents = price.components().cents,
        let dollarAmount = standardFormatter.number(from: dollars) else { return }
  
  primary.text = dollarsDisplayFormatter.string(from: dollarAmount)
  partial.text = ".\(cents)"
}

Extracting Dependencies

Extracting Networking Logic

import Foundation

enum CoinBaseError: Swift.Error {
  case networkFailure(Swift.Error)
  case invalidUrl
  case invalidData
}

public extension URLSession {
  func dataTask(
    with url: URLRequest,
    handler: @escaping (Result<Data, Swift.Error>) -> Void
  ) -> URLSessionDataTask {
    dataTask(with: url) { data, _, error in
      if let error = error {
        handler(.failure(error))
      } else {
        handler(.success(data ?? Data()))
      }
    }
  }
}

protocol Networking {
  typealias CompletionHandler = (Result<Data, CoinBaseError>) -> Void
  func request(from: Endpoint, completion: @escaping CompletionHandler)
}

struct HTTPNetworking: Networking {
  func request(from: Endpoint, completion: @escaping CompletionHandler) {
    guard let url = URL(string: from.path) else { return }
    let request = createRequest(from: url)
    let task = createDataTask(from: request, completion: completion)
    task.resume()
  }

  private func createRequest(from url: URL) -> URLRequest {
    var request = URLRequest(url: url)
    request.cachePolicy = .reloadIgnoringCacheData
    return request
  }

  private func createDataTask(
    from request: URLRequest,
    completion: @escaping CompletionHandler
  ) -> URLSessionDataTask {
    return URLSession.shared.dataTask(with: request) { result in
      switch result {
      case .success(let data):
        completion(.success(data))
      case .failure(let error):
        completion(.failure(.networkFailure(error)))
      }
    }
  }
}


  let networking = HTTPNetworking()
  ...
  
  private func requestPrice()  {
    networking.request(from: Coinbase.bitcoin) { result in
      switch result {
      case .success(let data):
        // 4. Parse the returned information
        let decoder = JSONDecoder()
        guard let response = try? decoder.decode(PriceResponse.self, from: data) else { return }
        print("Price returned: \(response.data.amount)")

        // 5. Update the UI with the parsed PriceResponse
        DispatchQueue.main.async { [weak self] in
          self?.updateLabel(price: response.data)
        }
      case .failure(let error):
        debugPrint(error.localizedDescription)
      }
    }
  }

Extracting Parsing Logic


import Foundation

protocol PriceFetcher {
  func fetch(response: @escaping (PriceResponse?) -> Void)
}

struct BitcoinPriceFetcher: PriceFetcher {
  let networking: Networking

  // 1. Initialize the fetcher with a networking object
  init(networking: Networking) {
    self.networking = networking
  }

  // 2. Fetch data, returning a PriceResponse object if successful
  func fetch(response: @escaping (PriceResponse?) -> Void) {
    networking.request(from: Coinbase.bitcoin) { result in
      switch result {
      case .success(let data):
        // Parse data into a model object.
        let decoded = self.decodeJSON(type: PriceResponse.self, from: data)
        if let decoded = decoded {
          print("Price returned: \(decoded.data.amount)")
        }
        response(decoded)

      case .failure(let error):
        debugPrint(error.localizedDescription)
        response(nil)
      }
    }
  }

  // 3. Decode JSON into an object of type 'T'
  private func decodeJSON<T: Decodable>(type: T.Type, from: Data?) -> T? {
    let decoder = JSONDecoder()
    guard let data = from,
          let response = try? decoder.decode(type.self, from: data) else { return nil }

    return response
  }
}

  let fetcher = BitcoinPriceFetcher(networking: HTTPNetworking())
  ...
   private func requestPrice() {
    fetcher.fetch { response in
      guard let response = response else { return }

      DispatchQueue.main.async { [weak self] in
        self?.updateLabel(price: response.data)
      }
    }
  }

part 1 끝 to be continue