5minho / DreamRecorder

mino & bran BoostCamp Project
6 stars 0 forks source link

데이터가 많을때 검색 처리 #61

Closed 5minho closed 6 years ago

5minho commented 6 years ago

꿈 데이터를 4500개 정도 넣고 검색 실험을 해봤는데 역시나 검색창에 키워드를 입력할때 끊김이 있었다. 이를 처리하기 위해 검색하는 로직을 globalQueue에서 실행시키고 검색 그 안에서 검색된 데이터를 reload 했더니. 끊김이 사라졌다

DispatchQueue.global().async {
            self.filteredDreams = DreamDataStore.shared.filter(searchText)
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
5minho commented 6 years ago

데이터가 많은 상태에서 검색 텍스트 필드 (Search Bar)에 검색 키워드를 계속 지웠다가 썼다가 했을때 (DB Like Query가 계속 실행) 어느 정도 Global Queue가 쌓이니까 App이 죽어버렸다. 이를 해결하기 위해 global Queue가 아닌 따로OperationQueue를 생성하고 검색을 실행하는 함수에 진입할때 만약 Queue의 work는 비우고 새로운 work 할당하는 방식으로 코드를 바꿨더니 app은 죽지 않았지만 검색 속도가 전에 비해서 떨어졌다

func filterContentForSearchText(_ searchText: String, scope: String = "All") {

        opearationQueue.cancelAllOperations()

        searchOperation = BlockOperation {
            self.filteredDreams = DreamDataStore.shared.filter(searchText)
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
        searchOperation.qualityOfService = .background
        opearationQueue.addOperation(searchOperation)

    }

현재 꿈이 4000여개 정도 되는데 이 데이터는 모두 '번째' 라는 문자열을 가지고 있다. search bar에 '번째' 라는 키워드를 계속 지웠다 썼다 반복했더니 app이 죽지는 않았지만 툭툭 끊기는 현상이 발생했다.

툭툭 끊길 때 xcode debug navigator

debugnavigator

조치가 필요하다.

5minho commented 6 years ago

위의 코드는 스레드를 자원을 해제, 생성을 계속 빠르게 반복 해서 시스템에 부하가 많이 가는 코드 같아서 flag를 이용하는 코드로 수정했다

func filterContentForSearchText(_ searchText: String, scope: String = "All") {

        guard isQueueEmpty else {
            return
        }

        concurrentQueue.async {
            self.isQueueEmpty = false

            DreamDataStore.shared.filter(searchText) {

                DispatchQueue.main.async {
                    self.tableView.reloadData()
                    self.isQueueEmpty = true
                }

            }
        }
    }

isQueueEmpty가 false이면 스레드를 만들지 않고, true 이면 filter 함수는 searchText를 키워드로 검색을 완료 한 후 tableView를 reload 시킨다 이렇게 하면 스레드를 마구 생성, 삭제 하지 않아서 검색도 빠르고 mainQueue에서 filtering을 하지 않아 화면 끊김도 없지만 마지막 검색 keyword를 빨리 바꾸면 마지막의 검색 keyword가 filtering 되지 않는다 (isQueueEmpty가 true상태여서 filter 메서드가 실행이 안됨) 임시방편으로 키보드의 검색버튼을 누르면 검색이 되게 했지만 UX 적으로 별로고 코드도 찝찝해서 다른 방안을 계속 생각해 볼것이다.

5minho commented 6 years ago

filterDreams 배열에 동시성 문제가 생긴거 같다

5minho commented 6 years ago

serial Queue로 바꿔주어서 해결

5minho commented 6 years ago

semaphore을 사용해 보는것은 어떨까

5minho commented 6 years ago

사용자가 0.25 초 동안 입력을 하지 않을때 저장해 둔 workItem을 실행, 빠르게 키보드를 치면 cancel() 함수가 호출되어 전에 queue에 저장한 연산이 실행되지 않을 것이다. 야곰님께서 말씀하신게 이런식으로 하는게 아닐까??

func filterContentForSearchText(_ searchText: String) {

        self.pendingFilterWorkItem?.cancel()

        let filterWorkItem = DispatchWorkItem {
            DreamDataStore.shared.filter(searchText)

            DispatchQueue.main.async { [unowned self] in
                self.tableView.reloadData()
            }
        }

        pendingFilterWorkItem = filterWorkItem

        serialDispatchQueue.asyncAfter(deadline: .now() + .milliseconds(250), execute: filterWorkItem)

    }
5minho commented 6 years ago

위의 코드와 비슷한 동작을 하는 코드가 있다.

func filterContentForSearchText(_ searchText: String) {

        NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(reload(_:)), object: nil)
        perform(#selector(reload(_:)), with: searchText, afterDelay: 0.25)
    }

    func reload(_ searchText: String) {

        serialDispatchQueue.async {

            DreamDataStore.shared.filter(searchText)

            DispatchQueue.main.async { [unowned self] in
                self.tableView.reloadData()
            }

        }

    }