Almat Kairatov
Almat Kairatov

Reputation: 1

Thread 2: EXC_BREAKPOINT (code=1, subcode=0x102816584) How to resolve this error

Could you explain how and why it acts like this and if you have some exact sources also please share.

I am following Youtube tutorial where he used Rick and Morty API, so I want to show Episode list and in each cell I want to display episode name and character list (this one I have to fetch from the list of character URLs). SO I am having trouble with fetching the character list

If I use DispatchGroup, I can easily fetch my initial episodes with characters of each episode, but when scroll down and trigger additional fetch it stops working.

protocol RMEpisodeViewDelegate: AnyObject {
    func initialEpisodesFetched()
    func didLoadAdditionalCharacters(with newIndexpath: [IndexPath])
}

class RMEpisodeViewModel: NSObject {
    private var episodeViewModels: [RMEpisodeViewCellViewModel] = []
    private var info: RMInfo?
    private var loadingMore = false
    weak var delegate: RMEpisodeViewDelegate?

    func fetchEpisodes(episodesURL: URL? = nil) {

        var rmRequest: RMRequest = .listEpisodesRequests
        if let episodesURL = episodesURL {
            guard let request = RMRequest(url: episodesURL) else { return }
            rmRequest = request
        }

        RMService.shared.execute(
            rmRequest, 
            expecting: RMResponse<RMEpisode>.self)
        { [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let response):
                self.info = response.info
                self.setupEpisodeViewModel(episodes: response.results) {
                    DispatchQueue.main.async {
                        if let episodesURL = episodesURL {
                            let indexPath = (self.episodeViewModels.count - response.results.count ..< self.episodeViewModels.count).map { IndexPath(row: $0, section: 0) }
                            self.delegate?.didLoadAdditionalCharacters(with: indexPath)
                            self.loadingMore = false
                        } else {
                            self.delegate?.initialEpisodesFetched()
                        }
                        self.loadingMore = false
                    }
                }
            case .failure(let error):
                print(error)
                self.loadingMore = false
            }
        }
    }

    private func setupEpisodeViewModel(episodes: [RMEpisode], completion: @escaping () -> Void) {
        let dispatchGroup = DispatchGroup()
        
        for episode in episodes {
            guard let characterURLs = episode.characters else { continue }
            dispatchGroup.enter()
            fetchEpisodeCharacters(characterURLs: characterURLs) { characters in
                self.episodeViewModels.append(RMEpisodeViewCellViewModel(episode: episode, characters: characters))
                dispatchGroup.leave()
            }
        }
        
        dispatchGroup.notify(queue: .main) {
            completion()
        }
    }

    private func fetchEpisodeCharacters(characterURLs: [String], completion: @escaping ([RMCharacter]) -> Void) {
        var characters: [RMCharacter] = []
        let dispatchGroup = DispatchGroup()
        
        for urlString in characterURLs {
            guard let url = URL(string: urlString) else { continue }
            dispatchGroup.enter()
            fetchCharacter(url: url) { result in
                switch result {
                case .success(let character):
                    characters.append(character)
                case .failure(let error):
                    print(error)
                }
                dispatchGroup.leave()
            }
        }
        
        dispatchGroup.notify(queue: .global()) {
            completion(characters)
        }
    }

    private func fetchCharacter(url: URL, completion: @escaping (Result<RMCharacter, Error>) -> Void) {
        guard let rmRequest = RMRequest(url: url) else {
            completion(.failure(NSError(domain: "Invalid URL", code: 0, userInfo: nil)))
            return
        }
        RMService.shared.execute(rmRequest, expecting: RMCharacter.self, completion: completion)
    }
}
extension RMEpisodeViewModel: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        episodeViewModels.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RMEpisodeViewCell.identifier, for: indexPath) as? RMEpisodeViewCell else {
            fatalError("Cell type mismatch")
        }
        cell.configure(viewModel: episodeViewModels[indexPath.row])
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let bounds = UIScreen.main.bounds
        return CGSize(width: bounds.width - 24, height: 0.2 * bounds.height)
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: RMFooterLoadingCollectionReusableView.identifier, for: indexPath) as? RMFooterLoadingCollectionReusableView else {
            fatalError("Footer type mismatch")
        }
        info?.next != nil ? footer.startAnimating() : footer.stopAnimating()
        
        return footer
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: info?.next != nil ? 100 : 0)
    }
}

extension RMEpisodeViewModel: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offset = scrollView.contentOffset.y
        let totalLength = (episodeViewModels.count - 1) * 150
        if Int(offset) - totalLength > 0, !loadingMore, info?.next != nil {
            loadingMore = true
            guard let url = URL(string: info!.next!) else {
                loadingMore = false
                return
            }
            fetchEpisodes(episodesURL: url)
        }
    }
}

Screen shot of the implementation with DispatchGroup

Thread 8 Queue : com.apple.CFNetwork.CacheDB-write (serial)

Thread 9 Queue : com.apple.NSURLSession-delegate (serial)

Thread 10 Queue : com.apple.NSURLSession-work (serial)

and now This code do not use DispatchGroup

final class RMEpisodeViewModel: NSObject {
    
    weak var delegate: RMEpisodeViewDelegate?
    
    var info: RMInfo? = nil
    var episodeViewModels: [RMEpisodeViewCellViewModel] = []
    var loadingMore = false
    
    func fetchEpisodes(episodesURL: URL? = nil) {
        var rmRequest: RMRequest = .listEpisodesRequests
        if let episodesURL = episodesURL {
            guard let request = RMRequest(url: episodesURL) else { return }
            rmRequest = request
        }
        RMService.shared.execute(rmRequest, expecting: RMResponse<RMEpisode>.self, completion: {
            [weak self] result in
            guard let self = self else { return }
            switch result {
            case .success(let response):
                self.info = response.info
                self.setupEpisodeViewModel(episodes: response.results) {
                    DispatchQueue.main.async {
                        if let episodesURL = episodesURL {
                            let indexPath = (self.episodeViewModels.count - response.results.count ..< self.episodeViewModels.count).map { IndexPath(row: $0, section: 0)}
                            self.delegate?.didLoadAdditionalCharacters(
                                with: indexPath)
                        } else {
                            self.delegate?.initialEpisodesFetched()
                        }
                        self.loadingMore = false
                    }
                }
            case .failure(let error):
                print(error)
            }
        })
    }
    
    func setupEpisodeViewModel(episodes: [RMEpisode], completion: @escaping () -> Void) {
        var indexPaths = [IndexPath]()
        let startCount = self.episodeViewModels.count
        episodes.forEach { episode in
            guard let characterURLs = episode.characters else { return }
            let characters = fetchEpisodeCharacters(characterURLs: characterURLs)
            
            episodeViewModels.append(RMEpisodeViewCellViewModel(episode: episode, characters: characters))
            let indexPath = IndexPath(row: startCount + indexPaths.count, section: 0)
            indexPaths.append(indexPath)
        }
        completion()
    }

    private func fetchEpisodeCharacters(characterURLs: [String]) -> [RMCharacter] {
        var characters: [RMCharacter] = []
        characterURLs.forEach { url in
            guard let url = URL(string: url) else { return }
            fetchCharacter(url: url) { result in
                switch result {
                case .success(let character):
                    characters.append(character)
                case .failure(let error):
                    print(error)
                }
            }
        }
        return characters
    }

    private func fetchCharacter(url: URL, completion: @escaping (Result<RMCharacter, Error>) -> Void) {
        guard let rmRequest = RMRequest(url: url) else { return }
        RMService.shared.execute(rmRequest, expecting: RMCharacter.self, completion: completion)
    }
}

Here it does not display character list for initial fetch, but for the additional fetching it works perfectly

I have used both of the ways and the results are totally different is there any way to do the fetching work in both ways

Upvotes: 0

Views: 77

Answers (0)

Related Questions