[iOS] GCD#3

RudinP·2024년 7월 20일
0

Study

목록 보기
256/258

GCD를 이용하여 이미지를 다운로드 -> 필터 적용 -> 테이블뷰 리로드

  • 다운로드와 필터 적용은 백그라운드에서, 테이블뷰 리로드는 UI이므로 mainQueue에서 실행되어야 한다.

1. DispatchQueue 생성

let backgroundQueue = DispatchQueue(label: "BackgroundQueue", attributes: .concurrent) //백에서 실행
let mainQueue = DispatchQueue.main // 메인에서 실행

2. 이미지 다운로드 function 추가(workItem을 DispatchQueue에 그룹으로 추가)

func downloadImages(){
        guard !isCancelled else {return}
        
        for photoData in ds.list {
            let workItem = DispatchWorkItem {
                do{
                    guard !self.isCancelled else {return}
                    
                    let data = try Data(contentsOf: photoData.url)
                    
                    if let image = UIImage(data: data) {
                        let size = image.size.applying(CGAffineTransform(scaleX: 0.5, y: 0.5))
                        UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
                        let frame = CGRect(origin: .zero, size: size)
                        image.draw(in: frame)
                        
                        guard !self.isCancelled else {return}
                        
                        let resultImage = UIGraphicsGetImageFromCurrentImageContext()
                        UIGraphicsEndImageContext()
                        
                        guard !self.isCancelled else {return}
                        
                        photoData.data = resultImage
                    }
                }catch{
                    print(photoData.url, error.localizedDescription)
                }
            }
            
            backgroundQueue.async(group: downloadGroup, execute: workItem)
            
        }
        downloadGroup.notify(queue: .main){
            self.reloadAll()
        }//tableViewReload
    }
  • 각 photoData를 다운로드하므로 반복문을 통해 각 데이터 다운로드 workItem을 그룹을 이용하여 백그라운드 DispatchQueue에 추가한다.
  • for문이 끝나고 downloadGroup의 workItem들의 작업이 끝나면 main큐에 notify해서 테이블뷰를 리로드한다.

3. 필터 적용 workItem 추가

func applyFilter(){
        guard !self.isCancelled else {return}
        let context = CIContext(options: nil)
        
        for photoData in ds.list{
            backgroundQueue.async {
                guard let source = photoData.data?.cgImage else { fatalError() }
                let ciImage = CIImage(cgImage: source)
                
                guard !self.isCancelled else {return}
                
                let filter = CIFilter(name: "CIPhotoEffectNoir")
                filter?.setValue(ciImage, forKey: kCIInputImageKey)
                
                guard let ciResult = filter?.value(forKey: kCIOutputImageKey) as? CIImage else {
                    fatalError()
                }
                
                guard !self.isCancelled else {return}
                
                guard let cgImage = context.createCGImage(ciResult, from: ciResult.extent) else {
                    fatalError()
                }
                
                photoData.data = UIImage(cgImage: cgImage)
                
                guard !self.isCancelled else {return}
                
                if let index = self.ds.list.firstIndex(where: {$0.url == photoData.url}){
                    let indexPath = IndexPath(item: index, section: 0)
                    
                    self.mainQueue.async {
                        self.imageCollectionView.reloadItems(at: [indexPath])
                    }
                }
            }
        }
    }
  • 여기서는 workItem을 생성하지 않고 바로 async블럭 안에 코드를 작성했다.
  • mainQueue에서 각 photoData별로 따로따로 콜렉션뷰를 리로드한다.

4. Reload

    func reloadAll(){
        guard !self.isCancelled else {return}
        
        imageCollectionView.reloadData()
        applyFilter()
    }

5. Cancel 기능 추가

var isCancelled: Bool = false
@IBAction func cancelWorkItems(_ sender: Any) {
    isCancelled = true
}
...
- cancel되기 적절한 위치에 guard문을 추가하여 return하도록 한다.(전체 코드 참고)

전체 코드(GCD 관련)


import UIKit

class ImageListViewController: UIViewController {
    @IBOutlet weak var imageCollectionView: UICollectionView!
    
    let backgroundQueue = DispatchQueue(label: "BackgroundQueue", attributes: .concurrent) //백에서 실행
    let mainQueue = DispatchQueue.main // 메인에서 실행
    
    let downloadGroup = DispatchGroup()
    
    var ds = PhotoDataSource()
    
    var isCancelled: Bool = false
    
    @IBAction func cancelWorkItems(_ sender: Any) {
        isCancelled = true
    }
    
    @IBAction func startWorkItem(_ sender: Any) {
        isCancelled = false
        downloadImages()
    }
    
    func downloadImages(){
        guard !isCancelled else {return}
        
        for photoData in ds.list {
            let workItem = DispatchWorkItem {
                do{
                    guard !self.isCancelled else {return}
                    
                    let data = try Data(contentsOf: photoData.url)
                    
                    if let image = UIImage(data: data) {
                        let size = image.size.applying(CGAffineTransform(scaleX: 0.5, y: 0.5))
                        UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
                        let frame = CGRect(origin: .zero, size: size)
                        image.draw(in: frame)
                        
                        guard !self.isCancelled else {return}
                        
                        let resultImage = UIGraphicsGetImageFromCurrentImageContext()
                        UIGraphicsEndImageContext()
                        
                        guard !self.isCancelled else {return}
                        
                        photoData.data = resultImage
                    }
                }catch{
                    print(photoData.url, error.localizedDescription)
                }
            }
            
            backgroundQueue.async(group: downloadGroup, execute: workItem)
            
        }
        downloadGroup.notify(queue: .main){
            self.reloadAll()
        }//tableViewReload
    }
    
    func reloadAll(){
        guard !self.isCancelled else {return}
        
        imageCollectionView.reloadData()
        applyFilter()
    }
    
    func applyFilter(){
        guard !self.isCancelled else {return}
        let context = CIContext(options: nil)
        
        for photoData in ds.list{
            backgroundQueue.async {
                guard let source = photoData.data?.cgImage else { fatalError() }
                let ciImage = CIImage(cgImage: source)
                
                guard !self.isCancelled else {return}
                
                let filter = CIFilter(name: "CIPhotoEffectNoir")
                filter?.setValue(ciImage, forKey: kCIInputImageKey)
                
                guard let ciResult = filter?.value(forKey: kCIOutputImageKey) as? CIImage else {
                    fatalError()
                }
                
                guard !self.isCancelled else {return}
                
                guard let cgImage = context.createCGImage(ciResult, from: ciResult.extent) else {
                    fatalError()
                }
                
                photoData.data = UIImage(cgImage: cgImage)
                
                guard !self.isCancelled else {return}
                
                if let index = self.ds.list.firstIndex(where: {$0.url == photoData.url}){
                    let indexPath = IndexPath(item: index, section: 0)
                    
                    self.mainQueue.async {
                        self.imageCollectionView.reloadItems(at: [indexPath])
                    }
                }
            }
        }
    }
...
profile
iOS 개발자가 되기 위한 스터디룸...

0개의 댓글