2์ 17์ผ (๋ชฉ)
- ๋๊ธฐํ ๋ฉ๋ชจ์ฅ STEP3 ์งํ
- SwiftyDropbox ์ฌ์ฉํด๋ณด๊ธฐ
- RxSwift - ๊ฐ๋ ์ก๊ธฐ
ย
[RxSwift - ๊ฐ๋ ์ก๊ธฐ]
- ๋น๋๊ธฐ๋ก ์๊ธด ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๋ฐํ๊ฐ์ผ๋ก ๋ง๋ค๊น?
- ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ฃผ๋ ์ ํธ๋ฆฌํฐ๊ฐ ๋์ค๊ฒ ๋๋ค.
-
- ๋น๋๊ธฐ์ ์ผ๋ก ์๊ธฐ๋ ๋ฐ์ดํฐ๋ฅผ completion ๊ฐ์ ํด๋ก์ ๋ฅผ ํตํด์ ์ ๋ฌํ๋ ๊ฒ์ด ์๋๋ผ ๋ฐํ๊ฐ์ผ๋ก ์ ๋ฌํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ์ ํธ๋ฆฌํฐ์ด๋ค.
- Observable ํ์ ์ผ๋ก ๊ฐ์ธ์ ๋ฐํํ๋ฉด ๋์ค์ ์๊ธฐ๋ ๋ฐ์ดํฐ๊ฐ ๋๋ค.
- ๋์ค์ ์๊ธฐ๋ ๋ฐ์ดํฐ(Observable)์ ์ฌ์ฉํ ๋์๋ subscribe๋ฅผ ํธ์ถํ๋ฉด ๋๋ค.
- ๊ทธ๋ผ ๊ฑฐ๊ธฐ์ ์ด๋ฒคํธ๊ฐ ์ค๋๋ฐ, ์ข
๋ฅ๋
next
,completed
,error
์ด 3๊ฐ์ง์ด๋ค. - ๋ฐ์ดํฐ๊ฐ ์ ๋ฌ๋์์ ๋๋
next
์ผ์ด์ค๋ก ์ค๊ณ , ๋ฐ์ดํฐ๊ฐ ์ ๋ฌ๋๊ณ ๋๋ฌ์ ๋๋completed
์ผ์ด์ค๋ก ์จ๋ค.
- subscribe๋
disposable
์ ๋ฐํํ๊ณ ์๋๋ฐ, ์ด๊ฒ ๋ญ๋๋ฉด ๋์์ ์ค๊ฐ์ ์ทจ์ ์ํฌ ์ ์๋dispose()
๋ฅผ ํธ์ถํ ์ ์๋ค. - ์์ฒ๋ผ ํธ์ถํด์ค๋ค๋ฉด, ๋ค์ด๋ก๋๋ฅผ ํ๋ผ๊ณ ์์ผ๋๊ณ dispose๋ฅผ ํ๊ฒ๋๋ ๋ฒํผ์ ๋๋ฌ๋ activityIndicator๋ง ๋์๊ฐ๊ณ , ๋ค์ด๋ก๋๋ ์ทจ์๋์ด ๋์ํ์ง ์๊ฒ ๋๋ค.
- โญ๏ธ ์ฌ๊ธฐ์
์ํ์ฐธ์กฐ ๋ฌธ์
๊ฐ ๋ฐ์ํ ์ ์๋๋ฐ,subscribe
๊ฐ์ ๊ฒฝ์ฐ์๋completed
๋error
์์ํด๋ก์ ๊ฐ ์ข ๋ฃ
๋๊ธฐ ๋๋ฌธ์, ์์ ํ ์์ ์ด ์๋ฃ๋์๋ค๊ณ ์๋ ค์ฃผ๊ฒ ๋๋ฉด(onCompleted()
) ํด๋น ๋ฌธ์ ๋ ํด๊ฒฐ๋๋ค.
[๋ฐฐ์ธ ๊ฒ]
- ๋น๋๊ธฐ๋ก ์๊ธฐ๋ ๋ฐ์ดํฐ๋ฅผ
Observable
๋ก๊ฐ์ธ์ ๋ฆฌํด
ํ๋ ๋ฐฉ๋ฒ
func rxswiftLoadImage(from imageUrl: String) -> Observable<UIImage?> {
return Observable.create { seal in
asyncLoadImage(from: imageUrl) { image in
seal.onNext(image)
seal.onCompleted()
}
return Disposables.create()
}
}
Observable.create()
๋ง๋ค๊ณ ๋ค์ด๊ฐ๋ ์ธ์๋ก ํด๋ก์ ๊ฐ ํ๋ ๋ค์ด๊ฐ๋๋ฐ, ๋ญ๊ฐ(emitter
)๊ฐ ๋ค์ด๊ฐ๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ ํด๋ก์ ๋ด๋ถ์onNext()
๋ฉ์๋๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ค. ๋ฐ์ดํฐ๋์ฌ๋ฌ๊ฐ๋ฅผ ์ ๋ฌ
ํ ์๋ ์๋ค.- ์ดํ
onCompleted()
๋ก ๋ฐ์ดํฐ ์ ๋ฌ์ด ๋๋ฌ๋ค๊ณ ์๋ฆฐ ํ ๋ฐํํ๋ค. - ๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก
Disposables.create()
๋ฅผํธ์ถํ์ฌ ๋ฐํ
ํ๋ฉด ๋๋ค.
Observabel.create()
๋ฅผ ํ๋ค.task
๋ฅผ ๋ง๋ค์ด์dataTask๋ฅผ ํธ์ถ
ํ๊ณ , ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉดonError๋ก ์๋ฌ๋ฅผ ์ ๋ฌ
ํ๊ณ , data๊ฐ ์ ์์ ์ผ๋ก ๋ฐ์ธ๋ฉ ๋๋ค๋ฉดonNext๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ
ํ๊ณonCompleted()๋ฅผ ์คํ
ํ๋ค.- ์ดํ return์์ Disposables.create()๋ฅผ ํธ์ถํ ๋
task.cancel()
์ ํธ์ถํด์ค๋ค.- ์๋ง dispose๋ฅผ ํ์ ๋ ํธ์ถ ๋๋ ๊ตฌํ๋ถ์ธ ๋ฏ ํ๋ค.
- Observable์ ์๋ช
์ฃผ๊ธฐ
create
subscribe
<- ์ด๋ Observable๋ก createํ๊ฒ ๋์ํ๋ค.onNext
onCompleted
/ onErrorDisposed
- ์ด๋ ๊ฒ ๋์์ด ๋๋
Observable
์ ๋ค์ โจ์ฌ์ฌ์ฉํ ์ ์๋ค.โจ
- ์ด๋ป๊ฒ ๋์ํ๋์ง๋ ์ค๊ฐ์ debug๋ฅผ ํธ์ถํด์ฃผ๋ฉด ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค.
- Observable๋ก ์ค๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ
subscribe
๋ ํด๋ก์ ๋ฅผ ๊ฐํํ๋ฉด์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌ
ํ ์ ์๊ณ , subscribe() ํธ์ถ๋ง ํ๋ค๋ฉด ๊ฐ๋ง ์ ๋ฌ๋ฐ์ ์๋ ์๋ค.
[SPM์ผ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐํ๊ธฐ]
Targets
->General
->Frameworks, Libraries, and Embedded Content
->+
Add Package Dependency...
๋ฅผ ํด๋ฆญ
์ฌ์ฉํ๊ณ ์ถ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฃผ์๋ฅผ ๊ธฐ์ ํ๋ค.
์ค์น ์ ์ํ๋ ๋ฒ์ , ๋ธ๋์น ๋ฐ ์ปค๋ฐ์ ์ค์ ํ ์ ์๋ค. ์ด ํ ์ํ๋ packge product๋ฅผ ๊ณจ๋ผ์ Finish ๊น์ง ํ๋ฉด...
SwiftyDropbox
๊ฐ ์ ์์ ์ผ๋ก ์ค์น๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
[SwiftyDropbox - ํ๋ก์ ํธ ์ค์ ํ๊ธฐ]
์๋ ํ๋ก์ ํธ ์ค์ ํ๋ ํํ ๋ฆฌ์ผ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์๋ค. https://github.com/dropbox/SwiftyDropbox#configure-your-project
๋จผ์ Info.plist ํ์ผ์ ์์ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ๊ทธ ์ ์ dropbox์ app์ ๋ฑ๋กํด์ผ ํ๋ค. ๋ก๊ทธ์ธ ํ apps์ ๋ค์ด๊ฐ๋ฉด ์๋์ ๊ฐ์ ๋ฒํผ์ด ์๋ค.
์ดํ ํ์ ๋ฌธํญ์ ์ ํ, ์ ๋ ฅ ํ create app ๋ฒํผ์ ๋๋ฌ ๋ง๋ค์ด์ฃผ๋ฉด ๋๋ค.
๊ทธ๋ฌ๋ฉด App key๊ฐ ๋ฐ๊ธ๋๋๋ฐ, ์ด๊ฑธ ์ด์ Info.plist๋ฅผ ์์ ํ๋๋ฐ ํ์ฉํ ๊ฒ์ด๋ค.
ํํ ๋ฆฌ์ผ์์ ํ๋ผ๋๋ฐ๋ก Info.plist
๋ฅผ ์์์ ๊ฐ์ด ์์ ํด์ค๋ค.
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-8-emm</string>
<string>dbapi-2</string>
</array>
์๊น ๋ง๋ค๊ณ ์ป์ App key๋ฅผ
db-
๋ค๋ถํฐ ๊ธฐ์ ํด์ฃผ๋ฉด ๋๋ค.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>db-<APP_KEY></string>
</array>
<key>CFBundleURLName</key>
<string></string>
</dict>
</array>
์ดํ ์ฝ๋๋ก ๋์๊ฐ์
AppDelegate
์DropboxClient
์ธ์คํด์ค๋ฅผ ์ด๊ธฐํ ํด์ค๋ค.
import SwiftyDropbox
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DropboxClientsManager.setupWithAppKey("<APP_KEY>")
return true
}
๊ทธ๋ฆฌ๊ณ
SceneDelegate
์ ์๋์ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ค. ์ธ์ฆ์ด ๋ชจ๋ ์๋ฃ๋ ํ redirection์ ์ฒ๋ฆฌํด์ค๋ค.
import SwiftyDropbox
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
let oauthCompletion: DropboxOAuthCompletion = {
if let authResult = $0 {
switch authResult {
case .success:
print("Success! User is logged into DropboxClientsManager.")
case .cancel:
print("Authorization flow was manually canceled by user!")
case .error(_, let description):
print("Error: \(String(describing: description))")
}
}
}
for context in URLContexts {
// stop iterating after the first handle-able url
if DropboxClientsManager.handleRedirectURL(context.url, completion: oauthCompletion) { break }
}
}
}
์ดํ ๋งจ์ฒ์์ ์์ํ๋ ๋ทฐ์ ๋ก๊ทธ์ธ์ ํด์ ์ธ์ฆ ํ ํฐ์ ๋ฐ์์ค๋ ์์ ์ ์ถ๊ฐํ๋ค. ์ด๋ฒ ํ๋ก์ ํธ ๊ฐ์ ๊ฒฝ์ฐ UISplitViewController๋ฅผ ์ฌ์ฉํ๋๋ฐ, rootView์ธ SplitViewController์์๋ ํด๋น ์์ ์ด ์ ์์ ์ผ๋ก ๋จ์ง์์๋ค. (์ด์ ๋ ์ฐพ์ง ๋ชปํ๋ค.) ๊ทธ๋์ ๋ค๋ฅธ UIViewController์์ ์งํํด์ผํ๋.. ์ถ์ด์ UITableViewController์ viewDidLoad()์์ ํด๋น ์์ ์ ์คํํด์ฃผ๋ ๋ก๊ทธ์ธ์ฐฝ์ด ์ ์์ ์ผ๋ก ๋ด๋ค.
import SwiftyDropbox
func myButtonInControllerPressed() {
// OAuth 2 code flow with PKCE that grants a short-lived token with scopes, and performs refreshes of the token automatically.
let scopeRequest = ScopeRequest(scopeType: .user, scopes: ["account_info.read"], includeGrantedScopes: false)
DropboxClientsManager.authorizeFromControllerV2(
UIApplication.shared,
controller: self,
loadingStatusDelegate: nil,
openURL: { (url: URL) -> Void in UIApplication.shared.open(url, options: [:], completionHandler: nil) },
scopeRequest: scopeRequest
)
// Note: this is the DEPRECATED authorization flow that grants a long-lived token.
// If you are still using this, please update your app to use the `authorizeFromControllerV2` call instead.
// See https://dropbox.tech/developers/migrating-app-permissions-and-access-tokens
DropboxClientsManager.authorizeFromController(UIApplication.shared,
controller: self,
openURL: { (url: URL) -> Void in
UIApplication.shared.open(url, options: [:], completionHandler: nil)
})
}
์ฌ๊ธฐ์ scopes๋ผ๋ ํ๋ผ๋ฏธํฐ๊ฐ ์๋๋ฐ, ์ด ๋ถ๋ถ์ ์ฑ์ด Dropbox ๊ณ์ ์ ๋ณด๋ฅผ ๋ณด๊ณ ๊ด๋ฆฌํ ์ ์๋๋ก
๊ถํ์ ๋ฒ์
๋ฅผ ๋ปํ๋ค. ์๊น App key๋ฅผ ์ป์๋ ๊ณณ์์Permissions
ํญ์ ํด๋ฆญํ๋ฉดAccount์ ์ ๋ณด
๊ฐ ๋์จ๋ค. ๋ฐ๋ผ์ ํ์ํ Account๋ฅผscopes
์ ๋ฃ์ด์ฃผ๋ฉด ๋๊ฒ ๋ค.
์ด ๋ค์์ API์ ํธ์ถํ
DropboxClient ์ธ์คํด์ค
๋ฅผ ์์ฑํ๋ค.
import SwiftyDropbox
// Reference after programmatic auth flow
let client = DropboxClientsManager.authorizedClient
client
๋ฅผ ํตํด์ ๋ก๋
์๋ค์ด๋ก๋
๋ฅผ ์งํํ ์ ์๋ค.
let fileData = "testing data example".data(using: String.Encoding.utf8, allowLossyConversion: false)!
let request = client.files.upload(path: "/test/path/in/Dropbox/account", input: fileData)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// in case you want to cancel the request
if someConditionIsSatisfied {
request.cancel()
}
// Download to URL
let fileManager = FileManager.default
let directoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destURL = directoryURL.appendingPathComponent("myTestFile")
let destination: (URL, HTTPURLResponse) -> URL = { temporaryURL, response in
return destURL
}
client.files.download(path: "/test/path/in/Dropbox/account", overwrite: true, destination: destination)
.response { response, error in
if let response = response {
print(response)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
// Download to Data
client.files.download(path: "/test/path/in/Dropbox/account")
.response { response, error in
if let response = response {
let responseMetadata = response.0
print(responseMetadata)
let fileContents = response.1
print(fileContents)
} else if let error = error {
print(error)
}
}
.progress { progressData in
print(progressData)
}
๋๊ฐ์ง์ ๊ณตํต์ ์ ํ์ผ์ ๋ค์ด๋ก๋ํ๊ณ , ์ ๋ก๋ํ ๋
๊ฒฝ๋ก
๊ฐ ํ์ํ๋ค๋ ์ ์ด๋ค. ๋ฉ๋ชจ์ฅ ํ๋ก์ ํธ์ ๊ฒฝ์ฐ CoreData๋ฅผ ํตํด์ ๋ฉ๋ชจ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๊ธฐ ๋๋ฌธ์๋ฐฑ์
์ ํํ๋ก CoreData์ ๊ฒฝ๋ก๋ฅผ ์ป์ด๋ด์.sqlite
,.sqlite-shm
,.sqlite-wal
์ด 3๊ฐ์ ํ์ผ์ ์ ๋ก๋ ๋ฐ ๋ค์ด๋ก๋ ํด์ฃผ๋๋ก ๊ตฌํํด์ฃผ์๋ค.
์ ๋ก๋, ๋ค์ด๋ก๋ ๋ชจ๋ ํ์ผ์ ๋ฎ์ด์ธ๊ฑด์ง์ ๋ํ ์ต์ ์ด ์์ผ๋ ์์ธํ๊ฑด ์๋ ๋ํ๋จผํธ์์ ๊ฒ์ํด๋ณด๋ฉด ๋๊ฒ ๋ค.
https://dropbox.github.io/SwiftyDropbox/api-docs/latest/index.html
[SwiftyDropbox - download๊ฐ ๋๋๋ ์์ ์ ๋ทฐ๋ฅผ ์ ๋ฐ์ดํธ ํ๊ธฐ]
๋ค์ด๋ก๋๊ฐ ๋๋ ํ CoreData๋ฅผ fetch๋ฅผ ํ๊ณ TableView๋ฅผ reload๋ฅผ ํด์ฃผ๊ณ ์ถ์์ผ๋ ์คํจํ์๋ค.
์ด์
ํ์ผ์ด ์ฌ๋ฌ๊ฐ๊ฐ ์กด์ฌํ์ฌ, ์ฌ๋ฌ๊ฐ์ ํ์ผ์ ๋ค์ด๋ก๋ ํ๊ธฐ ์ํด ๋ฐ๋ณต๋ฌธ์ ๋๋ฆฌ๊ณ ์์์ผ๋, fetch์ reload๋ฅผ for-in๋ฌธ ๋ด๋ถ์์ ํด์ฃผ๊ณ ์์ด์, ๋ทฐ๊ฐ ์ ๋ฐ์ดํธ ๋ ๋๊ฐ ์๊ณ , ์๋๊ธฐ๋ ํ๋ ํ์์ด ๋ํ๋ฌ๋ค.ํด๊ฒฐ
๊ทธ๋์for-in๋ฌธ์ด ์ข ๋ฃ๋ ์์
์fetch
๋ฅผ ํ๊ณ view๋ฅผ reload๋ฅผ ํด์ฃผ๊ธฐ ์ํด, ๋ค์ด๋ก๋๊ฐ ๋ชจ๋ ์๋ฃ๋๋ ์์ ์DispatchGroup
๋ฅผ ํ์ฉํ์ฌ์ถ์
ํ๊ณ , ๋ฐ๋ณต๋ฌธ์์ ์์๋์๋ ๋ค์ด๋ก๋ ์์ ์ด ๋ชจ๋ ๋๋๊ฒ ๋๋ฉด ์๋ ๋ทฐ๋ฅผ ๋ค์ ์ค์ ํ๋๋ก ์ฝ๋๋ฅผ ์์ ํ์๋ค.
func download(_ tableViewController: NotesViewController?) {
let group = DispatchGroup() // ๊ทธ๋ฃน ์์ฑ
for fileName in fileNames {
let destURL = applicationSupportDirectoryURL.appendingPathComponent(fileName)
let destination: (URL, HTTPURLResponse) -> URL = { _, _ in
return destURL
}
group.enter() // ์์
์์
client?.files.download(path: fileName, overwrite: true, destination: destination)
.response { _, error in
if let error = error {
print(error)
}
group.leave() // ์์
๋
}
}
group.notify(queue: .main) { // ๋ชจ๋ ์์
์ด ๋๋๋ค๋ฉด ...
PersistentManager.shared.setUpNotes()
tableViewController?.tableView.reloadData()
tableViewController?.stopActivityIndicator()
}
}