https://rhammer.tistory.com/98


비동기 처리 -asynchronous operation 또는 concurrent라고도 할 수 있겠습니다.

소요시간이 오래 걸리는 작업을 백그라운드에서 돌린 뒤 메인스레드에 반영하는 용도로 많이 사용되죠?


제가 아는 3가지 기법을 간략히 소개할까합니다.


1. NSObject객체의 - performSelectorInBackground:withObject: 메소드 입니다.

NSObject객체에 구현되어 있으므로 사실상 모든 객체에서 호출이 가능합니다. 말그대로 백그라운드에서 실행하고 싶은 메소드를 SEL타입으로 파라미터에 전달하면 백그라운드에서 실행이 됩니다.

추가로- performSelectorOnMainThread:withObject:waitUntilDone: 메소드는 SEL타입을 메인스레드에 추가하는 역할을 하게 됩니다.


주로 이런 방식으로 사용이 되겠네요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    [self performSelectorInBackground:@selector(myMethod:) withObject:@{@"param1":@"param1"}];
 
}
 
-(void)myMethod:(NSDictionary *)param{
    [self heavyOperation];
    [self performSelectorOnMainThread:@selector(updateUI:)
                           withObject:@{@"result1":@"data1"}
                        waitUntilDone:YES];
}
 
-(void)heavyOperation{
}
 
-(void)updateUI:(NSDictionary *)param{
}
@end

myMethod메소드내에서는 heavyOperation이 실행되고 그 결과에 따라 UI변경을 해주는 메소드를 메인 스레드에서 실행하도록 지시하고 있습니다.

참 올바른 형식으로 비동기를 처리하고 있네요.

2. NSOperation - Objective-C기반으로 멀티스레드를 지원합니다.

메인큐와 서브큐를 가져오거나 객체를 만들어작업을 추가할 수 있습니다. 코드로 보는게 더 이해가 빠를듯 하네요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "ViewController.h"
 
@interface ViewController()
@property (strong, nonatomic) NSOperationQueue *queue;
@end
 
@implementation ViewController
 
- (NSOperationQueue *)queue{
    //큐 객체를 하나 만듭니다.
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc] init];
    }
    return _queue;
}
 
- (void)viewDidLoad {
    [super viewDidLoad];
     
    //시간이 오래 걸리는 작업을 만들어 백그라운드 큐에 돌려줍시다.
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [self heavyOperation];
         
        //오래 걸리는 작업의 결과로 UI업데이트를 진행시켜줍니다.
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [self updateUI:nil];
        }];
    }];
    [_queue addOperation:operation];
}
 
-(void)heavyOperation{
}
 
-(void)updateUI:(NSDictionary *)param{
}
@end


스레드를 백그라운드에 밀어넣는 만큼 백그라운드 큐가 만들어지는 것은 아닙니다. 이는 시스템 상황에 따라 알아서 시스템이 처리할 부분입니다.

다만 동시에 추가되는 최대 스레드의 개수를 만들 수는 있습니다.

p12번 줄에서 큐를 최초에 생성할 때 [_queue setMaxConcurrentOperationCount:최대 큐 카운트]를 추가하여 지정할 수가 있습니다.

 

3. GCD

예제를 보시면

시스템이 제공하는 백그라운드 큐를 가져온 뒤 heavyOperation을 추가해주고 그 heavyOperation이 종료되면 결과에 따라 분기처리가 되네요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#import "ViewController.h"
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
     
    // 여기서부터 비동기 코드 시작.
    // dispatch_async 함수는 내부블럭의 코드 실행에 영향을 받지 않고 바로 실행이 끝난다.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 작업이 오래 걸리는 API를 백그라운드 스레드에서 실행한다.
        BOOL res = [self heavyOperation];
         
        dispatch_async(dispatch_get_main_queue(), ^{
            // 이 블럭은 메인스레드(UI)에서 실행된다.
            if (res) {
                [self updateUI:nil];
            } else {
                [self alertFail];
            }
        });
    });
}
 
-(BOOL)heavyOperation{
    return YES;
}
 
-(void)updateUI:(NSDictionary *)param{
}
 
-(void)alertFail{
}
@end

가장 많이 사용되는 GCD입니다. GCD는 NSOperation과 유사합니다. GCD는 C언어 기반이라는 차이점을 갖습니다.


4.공통

모든 비동기 처리에 대해서 공통적으로 적용되는 부분이 있겠죠. 비동기처리를 하는 가장 큰 목적은 UI의 흐름에 방해를 주지 않기 위해서입니다.

따라서 heavyOperation이 실행되는동안 사용자에게 잠깐의 기다림이 있을 수 있다는 UI(모래시계나 동그라미가 뺑뺑도는 등)를 구현하여 heavyOperation이 종료될때까지만 실행시켜주신다면 좀 더 부드러운 UX를 제공하실 수 있습니다.



출처: https://rhammer.tistory.com/98 [고무망치의 Dev N Life]