Search code examples
iosswiftdependency-inversion

Dependency Inversion in swift


Hi I have a question for developer in here, I was reading an iOS programming book from big nerd ranch. I was interested how to structure and technique to create an app. and I was trying to implement it, and it was a dependency inversion and the code is like this

this is the code in app delegate from the book

let rootViewController = window!.rootViewController as! UINavigationController
    let photosViewController = rootViewController.topViewController as! PhotoViewController
    photosViewController.store = PhotoStore()

and this is the photoViewController class

class PhotoInfoViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!

var photo: Photo! {
    didSet {
        navigationItem.title = photo.title
    }
}

var store: PhotoStore!

override func viewDidLoad() {
    super.viewDidLoad()

    store.fetchImage(for: photo) { (result) in
        switch result {
        case let .success(image):
            self.imageView.image = image
        case let .failure(error):
            print("Error fetching image for photo: \(error)")
        }
     }
  }
}

and this is the photo store class that use for dependency inversion code

enum ImageResult {
    case success(UIImage)
    case failure(Error)
}

enum PhotoError: Error {
    case imageCreationError
}

enum PhotoResult {
    case success([Photo])
    case failure(Error)
}

class PhotoStore {
    private let session: URLSession = {
        return URLSession(configuration: .default)
    }()

    let imageStore = ImageStore()

    func fetchInterestingPhoto(completion: @escaping (PhotoResult) -> Void) {
        let url = FlickerAPI.interestingPhotoURL
        let request = URLRequest(url: url)
        let task = session.dataTask(with: request) { (data, response, error) in
            let result = self.processPhotosRequest(data: data, error: error)

            OperationQueue.main.addOperation {
                completion(result)
            }
        }
        task.resume()
    }

    private func processPhotosRequest(data: Data?, error: Error?) -> PhotoResult {
        guard let jsonData = data else { return .failure(error!) }
        return FlickerAPI.photos(fromJSON: jsonData)
    }

    func fetchImage(for photo: Photo, completion: @escaping (ImageResult) -> Void) {
        let photoKey = photo.photoID
        if let image = imageStore.image(forKey: photoKey) {
            OperationQueue.main.addOperation {
                completion(.success(image))
            }
            return
        }

        let photoURL = photo.remoteURL
        let request = URLRequest(url: photoURL)

        let task = session.dataTask(with: request) { (data, response, error) in
            let result = self.processImageRequest(data: data, error: error)

            if case let .success(image) = result {
                self.imageStore.setImage(image, forKey: photoKey)
            }

            OperationQueue.main.addOperation {
                completion(result)
            }
        }
        task.resume()
    }

    private func processImageRequest(data: Data?, error: Error?) -> ImageResult {
        guard
            let imageData = data,
            let image = UIImage(data: imageData)
            else {
                // Couldn't create an image
                if data == nil {
                    return .failure(error!)
                } else {
                    return .failure(PhotoError.imageCreationError)
                }
        }
        return .success(image)
    }
}

What I'm asking is, if I have a tab bar controller and have lets say 2 view controller one of them is fetching a news data, and other controller fetching a weather data. how I can implemented this dependency inversion since the root view controller in app delegate will be tab bar controller that's why I confuse to implement it? since the book only use a single view controller.


Solution

  • Bear in mind this is a hack and in my opinion, it shouldn't be done like this as the dependencies should be declared in the init. But because you're probably creating the TabBar from the Storyboard. You only have this option which is getting the TabBar from the window. and the TabBarController has an array of the viewControllers that form the TabBar. You simply need to iterate over and try to cast them as a PhotoViewController. If it is, then you set the store:

    let rootViewController = window!.rootViewController as! UITabBarController
    
    for viewController in rootViewController.viewControllers {
        switch viewController {
        case let photoViewController as PhotoViewController:
            photoViewController.store = PhotoStore()
        default:
            break
        }
    }