GCD사용시 주의 해야할것

이진욱(JIN WOOK)·2024년 12월 26일
0

제비에관하여(Swift)

목록 보기
10/10

앞 글에서 GCD의 종류는 여러가지가 있다고 했다.
앞글 예시에서 네트워킹 처리를 main큐에 보내서 비동기가 아닌 동기처리를 했을때 버벅이는 현상이 발생할수 있다고 했는데 이런 상황을 방지하기 위해서 상황에 맞게 GCD를 구현해야만 최적의 사용자 경험을 만들수 있다.

1. UI작업은 반드시 메인스레드에서 작업해야한다.

물론, Xcode상에서 main스레드에서 작업을 시도하려는 경우 에러를 띄워줘서 잘못된 GCD사용을 방지할수 있긴하다.
BMW E30 이미지를 가져오는 코드를 작성해보겠다.

var imageView: UIImageView? = nil

let url = URL(string: "https://www.bmw-m.com/content/dam/bmw/marketBMW_M/www_bmw-m_com/topics/magazine-article-pool/2018/bmw-m3-e30/bmw-m3-e30-article-image-02.jpg")!
//URLSession은 기본적으로 비동기 작업을 하는 함수이다. 
//그러므로 절대로 main스레드에서 작업되지 않는다.
URLSession.shared.dataTask(with: url) { (data, response, error) in
    //에러 확인
    if error != nil {
        print("에러발생")
    }
    //이미지 데이터가 nil이 아닌경우 변수에 저장
    guard let imageData = data else {return}
    
    let bmwE30 = UIImage(data: imageData)
    
    //이미지 즉, UI설정을 다른스레드에서 작업하는 꼴

    imageView?.image = bmwE30
}

위와 같이 코드를 작성하면 아래와 같은 에러메세지가 출력된다.

UI 작업은 Main Actor (메인스레드) 에서 작업되기 때문에 다른 스레드에서 작업을 할수 없다라는 에러메서지 이다.
실제로 UIImageView 타입을 command를 눌러 정의를 확인해보면

MainActor 라고 선언이 되어있다.
이걸 해결하기 위해서는 작업 자체를 MainActor로 명시해주거나 (클래스나 구조체의 경우)
Main스레드로 보내는 방법이 있다.


이렇게 변경해주면 더이상 에러메세지가 출력되지 않는다.

아주 멋깔나게 잘 가져왔다. 🥵

2. 올바른 비동기 함수의 설계

이렇게 네트워크작업, 비동기 작업을 할때면 자주 사용되는게 클로저다.
이 클로저를 통해서 콜백 함수를 구현시켜 코드를 작성한다.
근데 왜 그럴까? 일반 함수로 작성하면 안될까?

func getImage(urlString: String) -> UIImage? {
    var image: UIImage? = nil
    let url = URL(string: urlString)!
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        //에러 확인
        if error != nil {
            print("에러발생")
        }
        //이미지 데이터가 nil이 아닌경우 변수에 저장
        guard let imageData = data else {return}
        image = UIImage(data: imageData)
        
    }.resume()
    return image
}

getImage(urlString: "https://www.bmw-m.com/content/dam/bmw/marketBMW_M/www_bmw-m_com/topics/magazine-article-pool/2018/bmw-m3-e30/bmw-m3-e30-article-image-02.jpg")

이렇게 UIImage를 리턴하는 함수를 작성하고 실행시켜 보면
리턴 값은 nil임을 확인 할수 있다.
이유는 URLSession이 비동기적 특성을 가지고 있기 때문에
getImage함수는 URLSession에서 작업이 끝나던 말던 상관없이 함수를 return (종료)시켜버리기 때문이다.

그렇기 때문에 우리는 비동기작업이 명확하게 끝나는 시점을 파악하고 데이터를 저장한다던지, UI작업을 한다던지의 추가작업을 할수 있게 설계를 해줘야한다.

그래서 존재하는게 completionHandler이다.

2-1. completionHandler

위 코드를 컴플리션 핸들러를 통해서 정상적으로 네트워크의 응답값을 받게 끔 작성해본다면

func getImage(with urlString: String, completionHandler: @escaping (UIImage?) -> Void) {
    var image: UIImage? = nil
    let url = URL(string: urlString)!
    
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if error != nil {
            print("에러발생")
        }
        guard let imageData = data else {return}
        
        image = UIImage(data: imageData)
        //이미지를 completionHandler라는 함수에 전달한다.
        //즉, 데이터 처리가 완료되면 함수에서 벗어나도 @escaping 키워드 덕에 외부에서 사용할수 있다.
        //이 시점은 비동기작업이 완료된 시점.
        completionHandler(image)
        
    }.resume()
}

이렇게 작업이 완료되는 시점에 컴플리션 핸들러 라는 함수를 호출 하게되면 클로저(함수)를 호출해서
비동기 작업이 완료 됐을때에 저장시켜야 하는 데이터를 저장시켜놓을수 있다.
@escaping 키워드 덕분에 외부서도 비동기 작업된 데이터를 사용할수 있게 된다.

profile
기술로부터 소외 되는 사람이 없도록 우리 모두를 위한 서비스를 만들고 싶습니다.

0개의 댓글

관련 채용 정보