https://www.acmicpc.net/problem/1600

 

1600번: 말이 되고픈 원숭이

첫째 줄에 정수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있

www.acmicpc.net

[4179] 불! 문제보다는 체감상 쉽게 다가왔다. 불! 문제에서 너무 고생해서 그랬나 보다. 물론 한 번에 통과하진 못했고, 결국 답을 찾아보았지만 조금만 더 깊이 생각했으면 답에 도달하지 않았을까 싶다.

 

풀이

늘 bfs문제를 풀듯이, 상하좌우 좌표의 확인과 말의 이동방식의 좌표 확인 과정을 추가해서 경로를 탐색하면 된다. 중요한 점은 말의 움직임은 k번까지만 가능하고, 말의 움직임과 원숭이의 움직임에 대해 순서가 뒤섞여서 움직일 수 있다는 점이다. 해당 부분에 대한 처리가 어설퍼서 처음에는 오답 판정을 받았다. 

 

큐에는 다음 좌표와 말의 움직임을 몇번 사용하였는지, 그리고 지금까지의 총 이동 횟수를 담아 확인했다. 이후 방문 여부를 확인하는 visited 배열을 3차원으로 생성하는 것이 핵심이다. visited[k][x][y] 배열을 통해 k번째 말의 움직임과 일반적인 움직임의 조합으로 경로를 탐색한다. 각 k번째 visited 배열에는 해당 횟수에서 이동 가능한 말의 움직임 + 원숭이의 움직임에 대한 방문 여부를 확인하고 경로탐색을 수행하면 된다. 설명보단 코드로 보는 것이 이해하기 더 쉬울 것이다.

 

오답 코드

해당 코드에서는 말의 움직임에 대해 깊이 고민하지 않고, 일반적인 visited배열을 사용하여 오답 판정을 받았다.

import Foundation

let k = Int(readLine()!)!
let wh = readLine()!.split(separator: " ").map{Int($0)!}
let w = wh[0]
let h = wh[1]
var ans = -1
var length = 0

var map = Array(repeating: Array(repeating: 0, count: w), count: h)
var visited = Array(repeating: Array(repeating: false, count: w), count: h)
for x in 0..<h{
    let land = readLine()!.split(separator: " ").map{Int($0)!}
    for y in 0..<w{
        map[x][y] = land[y]
    }
}
let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
let hx = [-1,-2,-2,-1,1,2,2,1]
let hy = [-2,-1,1,2,2,1,-1,-2]

func bfs(){
    var q = [[0,0,k,length]]
    var dq = [[Int]]()
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            let cnt = curr[2]
            let len = curr[3]
            if x==h-1 && y==w-1{
                ans = len
                return
            }
            
            if cnt>0{
                for i in 0..<8{
                    let nx = x+hx[i]
                    let ny = y+hy[i]
                    if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                    if !visited[nx][ny]{
                        visited[nx][ny] = true
                        q.append([nx,ny,cnt-1,len+1])
                    }
                }
            }
            for i in 0..<4{
                let nx = x+dx[i]
                let ny = y+dy[i]
                if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                if !visited[nx][ny]{
                    visited[nx][ny] = true
                    q.append([nx,ny,cnt,len+1])
                }
            }
        }
    }
}
visited[0][0] = true
bfs()
print(ans)

 

정답 코드

import Foundation

let k = Int(readLine()!)!
let wh = readLine()!.split(separator: " ").map{Int($0)!}
let w = wh[0]
let h = wh[1]
var ans = -1

var map = Array(repeating: Array(repeating: 0, count: w), count: h)
var visited = Array(repeating: Array(repeating: Array(repeating: false, count: 31), count: w), count: h)
for x in 0..<h{
    let land = readLine()!.split(separator: " ").map{Int($0)!}
    for y in 0..<w{
        map[x][y] = land[y]
    }
}
let dx = [-1,1,0,0]
let dy = [0,0,-1,1]
let hx = [-1,-2,-2,-1,1,2,2,1]
let hy = [-2,-1,1,2,2,1,-1,-2]

func bfs(){
    var q = [[0,0,0,0]]
    var dq = [[Int]]()
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            let cnt = curr[2]
            let length = curr[3]
            if x==h-1 && y==w-1{
                ans = length
                return
            }
            
            if cnt<k{
                for i in 0..<8{
                    let nx = x+hx[i]
                    let ny = y+hy[i]
                    if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                    if !visited[nx][ny][cnt+1]{
                        visited[nx][ny][cnt+1] = true
                        q.append([nx,ny,cnt+1,length+1])
                    }
                }
            }
            for i in 0..<4{
                let nx = x+dx[i]
                let ny = y+dy[i]
                if nx<0 || nx>=h || ny<0 || ny>=w || map[nx][ny]==1{continue}
                if !visited[nx][ny][cnt]{
                    visited[nx][ny][cnt] = true
                    q.append([nx,ny,cnt,length+1])
                }
            }
        }
    }
}
visited[0][0][0] = true
bfs()
print(ans)

'Problem Solving > BOJ' 카테고리의 다른 글

[17071] 숨바꼭질 5  (0) 2022.10.12
[2206] 벽 부수고 이동하기  (0) 2022.10.06
[4179] 불!  (0) 2022.09.29
[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20

https://www.acmicpc.net/problem/4179

 

4179번: 불!

입력의 첫째 줄에는 공백으로 구분된 두 정수 R과 C가 주어진다. 단, 1 ≤ R, C ≤ 1000 이다. R은 미로 행의 개수, C는 열의 개수이다. 다음 입력으로 R줄동안 각각의 미로 행이 주어진다.  각각의 문

www.acmicpc.net

정답에 닿을 듯 말듯해서 더욱 고생했던 문제다. 메모리 초과를 마주해서 고생하고, 이후 오답으로 인해 한번 더 고생했다.

 

풀이

문제를 보면 알겠지만 bfs를 두번 수행해야 한다. 본인도 거기까지는 생각했으나, 불의 경로와 지훈이의 경로를 어떻게 합칠 수 있을까에 대한 고민으로 시간을 소모했다. 항상 bfs문제를 만날 때면 Bool값을 통해 방문을 체크하는 visited 배열을 사용하는데, 이번 문제의 핵심은 해당 장소를 몇 초 후에 방문하는가를 저장하는 것이다. 불에 대한 경로탐색을 수행하고 나면 각 좌표에 몇 초 후에 불이 도달하는지 기록되어있다. 이후 지훈이의 경로탐색을 수행하여 불보다 먼저 해당 장소에 도착하는지를 확인하여 배열의 모서리까지 도달하면 된다.

메모리 초과가 일어났던 이유

지훈이의 경로탐색이 수행될 때, 해당 좌표의 방문 여부를 확인하지 않아서 방문했던 좌표도 큐에 담아버려 메모리 초과가 일어났다. 이후 지훈이의 경로탐색에 필요한 check:[Bool] 배열을 통해 방문하지 않았던 배열에 대해서만 경로탐색을 수행하도록 수정하였다.

오답 판정에 대한 이유

이후 71%에서 계속 오답이 발생했다. 원인은 또다시 지훈이의 경로탐색 부분에서 발생했는데, 단순히 불이 도달하는 시간보다 지훈이의 시간이 더 낮은 경우에 대해서만 경로탐색을 수행했으나( if visited[x][y] > current_time ), 애초에 해당 좌표에 불이 번지는 장소가 아닌 경우에는 visited[x][y]의 값은 0이다. 따라서 방문이 가능한 부분인데도 해당 부분에 대해서는 경로탐색을 수행하지 않아 올바르지 않은 답을 내놓게 된다. 해당 오류는 zero_woo님의 블로그를 보고 반례를 찾아 해결할 수 있었다. (해당글)

반례

3 4
###F
.J#.
###.

정답은 2가 출력되어야 하나, 고치기 전의 코드로는 "IMPOSSIBLE"이 출력된다. 불이 번지지 않는 위치에 대한 예외처리를 하지 않아서 오답처리를 받았던 것이다.

코드

import Foundation

let rc = readLine()!.split(separator: " ").map{Int($0)!}
let r = rc[0]
let c = rc[1]

var j = [[Int]]()
var f = [[Int]]()
var map = Array(repeating: Array(repeating: "", count: c), count: r)
var visited = Array(repeating: Array(repeating: 0, count: c), count: r)
var ans = Int.max
for i in 0..<r{
    let input = readLine()!.map{String($0)}
    for k in 0..<c{
        map[i][k] = input[k]
        if map[i][k] == "J"{
            j.append([i,k])
        }else if map[i][k] == "F"{
            f.append([i,k])
        }
    }
}

let dx = [-1,1,0,0]
let dy = [0,0,-1,1]

func bfs(j:[[Int]],f:[[Int]]){
    var level = 0
    var q = f
    var dq = [[Int]]()
    var v = Array(repeating: Array(repeating: false, count: c), count: r)

    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            v[x][y] = true
            visited[x][y] = level
            for i in 0..<4{
                let nx = dx[i] + x
                let ny = dy[i] + y
                if nx<0 || nx>=r || ny<0 || ny>=c || map[nx][ny]=="#"{continue}
                if visited[nx][ny]==0 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
            }
        }
        level += 1
    }
    
    level = 0
    q = j
    dq = [[Int]]()
    v = Array(repeating: Array(repeating: false, count: c), count: r)
    
    while !q.isEmpty{
        dq = q.reversed()
        q.removeAll()
        for _ in 0..<dq.count{
            let curr = dq.removeLast()
            let x = curr[0]
            let y = curr[1]
            v[x][y] = true
            if x==0||x==r-1||y==0||y==c-1{
                ans = min(ans, level+1)
            }
            for i in 0..<4{
                let nx = curr[0]+dx[i]
                let ny = curr[1]+dy[i]
                if nx<0 || nx>=r || ny<0 || ny>=c || map[nx][ny] == "#"{ continue }
                if visited[nx][ny] > level+1 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
                if visited[nx][ny]==0 && !v[nx][ny]{
                    v[nx][ny] = true
                    q.append([nx,ny])
                }
            }
        }
        level += 1
    }
}
bfs(j: j, f: f)
print(ans == Int.max ? "IMPOSSIBLE":ans)

비록 해답을 찾아보고 풀게 된 문제이지만, 그래프 탐색에 대한 시야가 아주 조금은 넓어지게 된 문제다.

'Problem Solving > BOJ' 카테고리의 다른 글

[2206] 벽 부수고 이동하기  (0) 2022.10.06
[1600] 말이 되고픈 원숭이  (2) 2022.10.05
[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[1890] 점프  (0) 2022.09.17

https://www.acmicpc.net/problem/2294

 

2294번: 동전 2

첫째 줄에 n, k가 주어진다. (1 ≤ n ≤ 100, 1 ≤ k ≤ 10,000) 다음 n개의 줄에는 각각의 동전의 가치가 주어진다. 동전의 가치는 100,000보다 작거나 같은 자연수이다. 가치가 같은 동전이 여러 번 주

www.acmicpc.net

직전에 풀었던 15988번 문제와 비슷하면서도 고민을 해봐야 하는 문제였다. 결국 다른 분의 풀이를 보고서 정답을 얻을 수 있었다.

 

풀이

배열 dp는 k개의 요소로 구성된 배열이며 dp[k]는 k원을 만들 수 있는 동전의 개수를 담고 있다.

dp[0]은 0원을 만들 수 있는 동전의 개수. 즉 0개부터 시작한다. 이후 동전을 담아놓은 배열을 순회하면서 dp를 갱신한다.

예제를 예시로 들겠다. 1원 5원 12원 동전이 있고, 목표 액수는 15원이다. 

우선 1원만 사용하여 dp배열을 갱신하면 아래와 같다.

0 0 0개
1 1개
2 1 + 1 2개
3 1 + 1 + 1 3개
4 1 + 1 + 1 + 1 4개
5 1 + 1 + 1 + 1 + 1 5개
... ... ...
15 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1  15개

다음으로 5원을 사용하여 dp배열을 갱신하면 다음과 같다.

0 0 0개
... ... ...
5 5 1개
6 1 + 5 2개
7 1 + 1 + 5 3개
8 1 + 1 + 1 + 5 4개
9 1 + 1 + 1 + 1 + 5 5개
10 5 + 5 2개
11 1 + 5 + 5 3개

사용할 동전의 가치가 coin원이고, 목표금액이 k원이라면 dp 배열의 요소 dp[k]는 dp[k-coin]+1로 갱신이 가능하며, 이를 통해 더 작은 값으로 갱신해주면 된다. 

즉 coin번째 배열부터 시작하여 갱신을 시작하면 되며, dp[0] = 0 인 상태에서 점화식은 다음과 같이 세울 수 있다.

점화식

dp[k] = min( dp[k], dp[k-coin]+1 )

 

코드

import Foundation

let nk = readLine()!.split(separator: " ").map{Int($0)!}
let n = nk[0]
var k = nk[1]

var dp = Array(repeating:10001, count:10001)
dp[0] = 0
var coins = [Int]()
for _ in 0..<n{
    let coin = Int(readLine()!)!
    if coin <= 10000{ coins.append(coin) }
}
//coins = coins.sorted(by: <)

for coin in coins{
    for i in coin...10000{
        dp[i] = min(dp[i], dp[i-coin]+1)
    }
}
if dp[k] == 10001{
    print(-1)
}else{
    print(dp[k])
}

문제를 풀 당시 coins배열을 오름차순 정렬했는데, 풀이를 적다 보니 coin의 오름차순 정렬은 할 필요가 없다는 것을 깨달았다.

'Problem Solving > BOJ' 카테고리의 다른 글

[1600] 말이 되고픈 원숭이  (2) 2022.10.05
[4179] 불!  (0) 2022.09.29
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[1890] 점프  (0) 2022.09.17
[6588] 골드바흐의 추측  (0) 2022.09.16

https://www.acmicpc.net/problem/15988

 

15988번: 1, 2, 3 더하기 3

각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 1,000,000,009로 나눈 나머지를 출력한다.

www.acmicpc.net

대표적인 동적 계획법 문제이다. 동적 계획법은 항상 마주할 때마다 느끼듯이 점화식을 고민하는 데에 너무 오랜 시간이 걸린다. 더 많은 문제들을 풀어봐야 하나 보다.

 

풀이

정답을 표현할 배열 dp는 n+1개의 요소를 가지는 배열이며, dp[n]은 "1,2,3을 사용하여 n을 만들 수 있는 식의 개수"라고 정의한다.

점화식을 사용하기 위해서 배열에 최소 3개의 요소가 담겨있어야 한다. 1부터 3까지의 경우의 수를 적어보면 다음과 같다.

 

n 식의 종류
0 표현 불가능(입력범위 아님)
1 1
2 1+1, 2
3 1+1+1, 2+1, 1+2, 3

이후 1,2,3을 이용해 4를 만드는 식을 만든다고 가정할 경우, 다음과 같은 생각을 해볼 수 있다.

  1. 1을 만들 수 있는 모든 식에 "+3"을 추가한다면 4를 만들 수 있다.
  2. 2를 만들 수 있는 모든 식에 "+2"를 추가한다면 4를 만들 수 있다.
  3. 3을 만들 수 있는 모든 식에 "+1"을 추가한다면 4를 만들 수 있다.

따라서 다음과 같은 점화식을 세울수있게된다.

dp[n] = dp[n-1] + dp[n-2] + dp[n-3]

다만 문제를 보면 알 수 있듯이 n의 크기가 1,000,000이므로 정답을 출력할 때 1,000,000,009로 나눈 나머지를 출력하라고 적혀있다.

모듈러 산술 연산법칙은 분배 법칙이 성립하므로 코드를 적을 때는 다음과 같이 적는다.

 

dp[n] = ((dp[n-1]%1,000,000,009) + (dp[n-2]%1,000,000,009) + (dp[n-3]%1,000,000,009))%1,000,000,009

 

정답 코드

import Foundation

var dp = Array(repeating: 0, count: 1000001)
dp[1] = 1
dp[2] = 2
dp[3] = 4
let mod = 1000000009

for i in 4...1000000{
    dp[i] = ((dp[i-1]%mod) + (dp[i-2]%mod) + (dp[i-3]%mod))%mod
}

for _ in 0..<Int(readLine()!)!{
    let n = Int(readLine()!)!
    print(dp[n])
}

'Problem Solving > BOJ' 카테고리의 다른 글

[4179] 불!  (0) 2022.09.29
[2294] 동전 2  (0) 2022.09.22
[1890] 점프  (0) 2022.09.17
[6588] 골드바흐의 추측  (0) 2022.09.16
[1158] 요세푸스 문제  (0) 2022.01.20

https://www.acmicpc.net/problem/1890

 

1890번: 점프

첫째 줄에 게임 판의 크기 N (4 ≤ N ≤ 100)이 주어진다. 그 다음 N개 줄에는 각 칸에 적혀져 있는 수가 N개씩 주어진다. 칸에 적혀있는 수는 0보다 크거나 같고, 9보다 작거나 같은 정수이며, 가장

www.acmicpc.net

풀이

동적 계획법 문제이다. 처음에는 깊이 우선 탐색으로 시도했으나 시간 초과가 발생. 동적 계획법 문제로 분류되어있는 걸 확인하고 난 후 고민했으나, 결국 다른 사람의 풀이과정을 이해하고 코드를 다시 작성하였다.

 

시간 초과 코드

import Foundation

let n = Int(readLine()!)!

var map = Array(repeating: [Int](), count: n)
for i in 0..<n{
    map[i] = readLine()!.split(separator: " ").map{Int($0)!}
}
var ans = 0

func dfs(x:Int, y:Int){
    if map[x][y]==0{
        if x==n-1 && y==n-1{
            ans += 1
        }
        return
    }
    let length = map[x][y]
    if x+length<n{
        dfs(x: x+length, y: y)
    }
    if y+length<n{
        dfs(x: x, y: y+length)
    }
}
dfs(x: 0, y: 0)
print(ans)

 

해결 코드

import Foundation

let n = Int(readLine()!)!

var map = Array(repeating: [Int](), count: n)
var dp = Array(repeating: Array(repeating: 0, count: n), count: n)
for i in 0..<n{
    map[i] = readLine()!.split(separator: " ").map{Int($0)!}
}

dp[0][0]=1
for x in 0..<n{
    for y in 0..<n{
        if map[x][y]==0 || dp[x][y]==0{continue}
        let nx = x + map[x][y]
        let ny = y + map[x][y]
        if nx<n{ dp[nx][y] += dp[x][y]}
        if ny<n{ dp[x][ny] += dp[x][y]}
    }
}
print(dp[n-1][n-1])

'Problem Solving > BOJ' 카테고리의 다른 글

[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[6588] 골드바흐의 추측  (0) 2022.09.16
[1158] 요세푸스 문제  (0) 2022.01.20
[1021] 회전하는 큐  (0) 2022.01.19

https://www.acmicpc.net/problem/6588

 

6588번: 골드바흐의 추측

각 테스트 케이스에 대해서, n = a + b 형태로 출력한다. 이때, a와 b는 홀수 소수이다. 숫자와 연산자는 공백 하나로 구분되어져 있다. 만약, n을 만들 수 있는 방법이 여러 가지라면, b-a가 가장 큰

www.acmicpc.net

풀이

소수 판별 문제이다. 문제를 보고서 소수 배열과 투 포인터를 사용하여 풀이를 제출했으나 시간 초과가 나왔다.

문제를 해결하기 위해서는 소수 배열의 작은 숫자부터 n의 차를 구한 수를 소수인지 판별하는 방법으로 해결해야 한다는 게시글을 보고서 다시 제출하였더니 통과했다.

 

시간 초과 코드

import Foundation

var numbers = Array(repeating: true, count: 1000001)
var primes = [Int]()
for i in 0..<1000001{
    if i <= 1{
        numbers[i] = false
        continue
    }
    if numbers[i]{
        if i != 2{
            primes.append(i)
        }
        for k in stride(from: i+i, to: 1000001, by: +i){
            numbers[k] = false
        }
    }
}

while let input = readLine(){
    let n = Int(input)!
    if n == 0{ break }
    
    var flag = false
    var start = 0
    var end = primes.count-1
    while start < end{
        let sum = primes[start] + primes[end]
        if sum > n{
            end -= 1
            continue
        }else if sum == n{
            flag = true
            break
        }else{
            start += 1
        }
    }
    if flag{
        print(n,"=",primes[start],"+",primes[end])
    }else{
        print("Goldbach's conjecture is wrong.")
    }
}

 

해결 코드

import Foundation

var primes = Array(repeating: true, count: 1000001)

for i in 0..<1000001{
    if i <= 1{
        primes[i] = false
        continue
    }
    if primes[i]{
        for k in stride(from: i+i, to: 1000001, by: +i){
            primes[k] = false
        }
    }
}

while let input = readLine(){
    let n = Int(input)!
    if n == 0{ break }
    
    var flag = false
    for i in 3..<1000001{
        if primes[i]{
            let chk = n-i
            if primes[chk]{
                flag = true
                print(n,"=",i,"+",chk)
                break
            }
        }
    }
    if !flag{
        print("Goldbach's conjecture is wrong.")
    }
}

 

심지어 문제를 풀고나서 알아낸 점은 같은 소수의 구성으로도 n을 만들 수 있다면 정답이 될 수 있다는 것이다. 투 포인터를 사용한 코드를 적을 때에 당연히 서로 다른 소수라는 가정을 한 채로 작성했었다. 대표적인 예시가 6을 입력할 경우 투 포인터를 사용한 코드는 불가능한 조합이라는 결과를 출력하지만, 통과했던 코드는 3 + 3 조합을 출력하게 된다.

'Problem Solving > BOJ' 카테고리의 다른 글

[2294] 동전 2  (0) 2022.09.22
[15988] 1, 2, 3 더하기 3  (0) 2022.09.20
[1890] 점프  (0) 2022.09.17
[1158] 요세푸스 문제  (0) 2022.01.20
[1021] 회전하는 큐  (0) 2022.01.19

 

 

observeOn(_ scheduler: ImmediateSchedulerType )

rxSwift의 scheduler는 stream 속의 연산들을 어느 스레드에서 동작하게 해 줄지 rxSwift에서 결정해주는 역할을 하고 있다.

observeOn() 오퍼레이터를 통해 결정해줄 수 있으며, 매개변수로 schedular를 입력받는다. observeOn() 오퍼레이터의 다음 연산부터 지정된 스레드로 연산을 수행한다.

@IBAction func exMap3() {
    Observable.just("800x600")
        .observeOn(ConcurrentDispatchQueueScheduler(qos: .default))    //ConcurrentDispatchQueueScheduler 동작
        .map { $0.replacingOccurrences(of: "x", with: "/") }    
        .map { "https://picsum.photos/\($0)/?random" }          
        .map { URL(string: $0) }                                
        .filter { $0 != nil }
        .map { $0! }                                            
        .map { try Data(contentsOf: $0) }                       
        .map { UIImage(data: $0) }                              
        .observeOn(MainScheduler.instance)    //MainScheduler 동작
        .subscribe(onNext: { image in
            self.imageView.image = image
        })
        .disposed(by: disposeBag)
}

url을 통해 이미지를 불러오는 함수다. 요청과 응답의 대기시간이 있기에, 스레드를 분리해서 작업해야 응답 대기시간 동안에 대기하는 동작을 막을 수 있다. 첫 번째 observeOn() 오퍼레이터를 통해서 concurrent 큐에서 요청 작업을 하게 된다. 응답에 대한 데이터를 UIImage 형태로 변환 후 이미지를 사용자에게 보여주기 위한 UI작업은 두 번째 observeOn( )을 통해 MainScheduler로 전환하여 작업하게 된다.


subscribeOn(_ scheduler: ImmediateSchedulerType )

observeOn() 오퍼레이터의 경우 Observable이 생성된 이후부터 적용할 수 있다. Observable이 생성되는 부분부터 특정 스레드를 지정하고 싶다면, subscribeOn() 오퍼레이터로 해결이 가능하다.

.subscribeOn(_ scheduler: ImmediateSchedulerType)
//Wraps the source sequence in order to run its subscription and unsubscription logic on the specified scheduler.
특정 스케줄러에서 구독 및 구독취소 로직을 실행하기 위해 소스 시퀀스(스트림)를 감쌉니다.

 

 

설명을 이해하기 전에 우선 Observable에 대해 알아두어야 할 점이 있다.

 

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

Observable just defines how the sequence is generated and what parameters are used for element generation. Sequence generation starts when subscribe method is called.

rx의 공식문서에 따르면 Observable은 subscribe()가 호출되는 순간에 생성된다. subscribe()가 호출되기 전까지는 스트림 생성에 관한 요소들을 정의하는 것일 뿐, 실제로 Observable은 생성된 상태가 아닌 것이다.

 

요약을 하자면 Observable의 생성은 subscribe()가 호출되는 순간이고, Observable이 어떤 스레드에서 생성될지를 결정해주는 것이 바로 subscribeOn(_ scheduler) 오퍼레이터라는 것이다. 

subscribeOn(_ scheduler) -> Observable을 생성할 스레드를 지정
ObserveOn(_ shcedular) -> Observable이 생성된 이후 스레드 변경

 

let observable = Observable<Int>.create { observer in
    observer.onNext(1)
    observer.onNext(2)
                                                
    print("[.create] in mainThread? \(Thread.isMainThread)")

    observer.onCompleted()
    return Disposables.create()
}
.observeOn(MainScheduler.instance)
.map{ next -> Int in
    print("[.map]",next, "in mainThread? \(Thread.isMainThread)")
    return next*2
}

print("[subscribe start]")    //구독시작
observable
    .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
    .subscribe(onNext: { event in
    print("[.subscribe] onNext \(event) in mainThread? \(Thread.isMainThread)")
}, onDisposed: {(
    print("[.disposed] in mainThread? \(Thread.isMainThread) \n---------------------------")
)})
.disposed(by: disposeBag)

//[subscribe start]
//[.map] 1 in mainThread? true
//[.create] in mainThread? false
//[.subscribe] onNext 2 in mainThread? true
//[.map] 2 in mainThread? true
//[.subscribe] onNext 4 in mainThread? true
//[.disposed] in mainThread? true 
//---------------------------
//[subscribe start]
//[.map] 1 in mainThread? true
//[.subscribe] onNext 2 in mainThread? true
//[.map] 2 in mainThread? true
//[.subscribe] onNext 4 in mainThread? true
//[.create] in mainThread? false
//[.disposed] in mainThread? true 
//---------------------------

두 번 수행한 출력 결과를 보면 알 수 있듯이 create 영역의 출력은 subscribe() 오퍼레이터가 호출된 이후에 등장하며, concurrent Queue에서 작업이 실행되기 때문에 mainThread 와의 출력 순서를 보장하지 않는다. 반면 ObserveOn() 오퍼레이터 이후에는 mainThread로 변경되어 동작하기에 출력 순서가 보장되는 모습을 확인할 수 있다.

 

subscribeOn()의 경우 다음 라인부터 영향을 주는 observeOn()과 달리 스트림 속 아무 라인에 적어놓아도 관계없이 적용되며, 여러 번 작성하더라도, 가장 첫 번째 subscribeOn()으로만 적용이 된다고 한다.

 

참고 영상 및 블로그

[유튜브] 곰튀김 rxswift 4시간에 끝내기 - 8    

[블로그] 이승연 님 글 [RxSwift] Scheduler 제대로 알아보기

'iOS > RxSwift' 카테고리의 다른 글

next, error, complete  (0) 2022.08.19
operator  (0) 2022.07.21
subscribe, dispose  (0) 2022.06.15
Observable stream  (0) 2022.06.08
ReactiveX  (0) 2022.05.30

subscribe( on: (Event<type>) -> Void)

Rx의 여러 오퍼레이터로 이루어진 스트림의 마지막엔 subscribe()을 통해 가공된 데이터를 확인하게 된다. 여기서 Event<타입>형태의 데이터가 들어오게 되고, Event는 next, error, completed 3가지의 값을 가질 수 있다. 

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next:
            break
        case .error:
            break
        case .completed:
            break
        }
    })
    .disposed(by: disposeBag)

next

스트림에서 넘어온 자료가 있는 경우, next에 해당한다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")    //자료 출력
            break
        case .error(let err):
            break
        case .completed:
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// next: In
// next: 4
// next: Hours

error

스트림에서 에러가 발생한 경우, error의 값을 가지게 된다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .single()    //하나의 데이터만 들어와야함.
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")
            break
        case .error(let err):
            print("err: \(err.localizedDescription)")
            break
        case .completed:
            print("completed")
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// err: The operation couldn’t be completed. (RxSwift.RxError error 5.)

 

single()오퍼레이터의 경우 하나의 데이터만 입력을 받아야 하는데, 스트림의 앞쪽에 위치한 from() 오퍼레이터의 경우 이벤트가 여러번 발생되기 때문에 에러가 발생하게된다. 이렇게 고의로 에러를 발생시켜보면 에러의 내용이 출력되는 모습을 확인할 수 있고, completed 될 수 없다고 나온다. 즉 에러가 발생하면 completed 상태에 들어가지 않고 스트림은 마무리된다.


completed

해당 스트림이 에러없이 모든 이벤트들을 내보냈을경우 completed 값을 가지게 된다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe({event in
        switch event{
        case .next(let str):
            print("next: \(str)")
            break
        case .error(let err):
            print("err: \(err.localizedDescription)")
            break
        case .completed:
            print("completed")
            break
        }
    })
    .disposed(by: disposeBag)
    
// next: RxSwift
// next: In
// next: 4
// next: Hours
// completed

해당 코드를 실행하게되면 from에 의해 생성된 4개의 자료들이 출력되고, 마지막으로 "completed"가 출력되는 모습을 볼 수 있다. 

즉 에러가 발생하지 않는다면, 발생되는 모든 이벤트를 처리하고 마지막으로 completed 상태가 되면서 마무리가 된다.


subscribe(onNext:  , onError: , onCompleted:  , onDisposed:  )

subscibe(_ on: )의 경우, switch문을 통해 event의 모든 조건에 대한 처리를 해주어야 했다면, subscribe(onNext:  ,onError:  ,onCompleted:  ,onDisposed:  )의 경우 원하는 경우의 분기만 골라서 작성이 가능하게 해주는 오퍼레이터이다.

또 다른 차이점은 onDisposed가 추가되었다는 점이다. 

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)
    
// RxSwift
// In
// 4
// Hours

모든 경우의 값에 대해 작성해야 하는 subscribe( _ on:  )과 달리 필요한 부분만 이벤트 처리하여 더 간결한 코드 작성이 가능하다.

Observable.from(["RxSwift", "In", "4", "Hours"])
    .single()
    .subscribe(onNext: { str in
        print(str)
    }, onError: { err in
        print(err.localizedDescription)
    }, onCompleted: {
        print("completed")
    }, onDisposed: {
        print("disposed")
    })
    .disposed(by: disposeBag)
    
// RxSwift
// The operation couldn’t be completed. (RxSwift.RxError error 5.)
// disposed
Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe(onNext: { str in
        print(str)
    }, onError: { err in
        print(err.localizedDescription)
    }, onCompleted: {
        print("completed")
    }, onDisposed: {
        print("disposed")
    })
    .disposed(by: disposeBag)
    
// RxSwift
// In
// 4
// Hours
// completed
// disposed

예제 코드에서 알 수 있듯, 스트림은 마지막으로 disposed 되면서 마무리된다. 강의에서 말하길, 많은 개발자들이 흔히 하는 실수로 프로세스의 종료처리를 onCompleted 부분에서 작성하게 되는데, 이렇게 되면 에러 발생시에 onCompleted 상태에 들어가지 않게 되어 프로세스의 종료처리가 일어나지 않게 된다. 따라서 프로세스의 마무리 처리를 하려면 onDisposed 부분에서 작성해야 한다.

 

[참고영상]

[유튜브] 곰튀김 RxSwift 4시간에 끝내기 - 7

'iOS > RxSwift' 카테고리의 다른 글

scheduler  (0) 2022.08.31
operator  (0) 2022.07.21
subscribe, dispose  (0) 2022.06.15
Observable stream  (0) 2022.06.08
ReactiveX  (0) 2022.05.30

+ Recent posts