title | author | translator | category | excerpt | status | ||
---|---|---|---|---|---|---|---|
Optional, throws, Result, ๊ทธ๋ฆฌ๊ณ async/await |
jemmons |
๊นํ๊ถ |
Swift |
์ค์ํํธ ์๋ฌ ํธ๋ค๋ง์ ๊ณผ๊ฑฐ, ํ์ฌ, ๋ฏธ๋. |
|
Swift 1 ์์ ์ ์๋ฌ ํธ๋ค๋ง์ ํ ์ ์๋ ๋ฐฉ๋ฒ์ด ๊ทธ๋ ๊ฒ ๋ง์ง ์์์ง๋ง Optional
์ ๊ทธ๋๋ ์์์ต๋๋ค. ๊ฒ๋ค๊ฐ ์ ๋ง ๋ฉ์ก์ต๋๋ค!
๋(null) ํ์ธ์ ๋ช
์์ ์ผ๋ก ๋ง๋ฆ์ผ๋ก์จ, ํจ์์์ nil
์ ๋ฐํํ๋ ํ๋์ด ๋ ๊บผ๋ ค์ง๊ณ ์ธ์ด์ ๊ธฐ๋ฅ์ฒ๋ผ ๋๊ปด์ง๊ฒ ๋์์ต๋๋ค.
๋ค์์ Keychain ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ ์ด๋ ํ ์๋ฌ๋ nil
์ ๋ฐํํ๋ ์ฝ๋์
๋๋ค. ์ด๋ ต์ง ์๊ฒ ๋ง๋๋ณผ ์ ์๋ ์ฝ๋์ฃ .
func keychainData(service: String) -> Data? {
let query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecReturnData: true
]
var ref: CFTypeRef? = nil
switch SecItemCopyMatching(query, &ref) {
case errSecSuccess:
return ref as? Data
default:
return nil
}
}
์ด ์ฝ๋์์ ์ฟผ๋ฆฌ๋ฅผ ์ค์ ํ๊ณ ,
SecItemCopyMatching
์ ๋น inout
์ฐธ์กฐ๋ฅผ ๋๊ฒผ์ผ๋ฉฐ,
๋ฐํ๋ ์คํ
์ดํฐ์ค ์ฝ๋์ ๊ธฐ๋ฐํ์ฌ,
์ ๋๋ก ๋ ์ฐธ์กฐ๋ฅผ ๋ฐ์ดํฐ๋ก ๋ฐํํ๊ฑฐ๋ ์๋ฌ๋ฅผ nil
๋ก ๋ฐํํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ํธ์ถํ๋ ๊ณณ์์ ์ต์ ๋์ ๋ฒ๊ฒจ๋ธ ๊ฒฐ๊ณผ๋ฅผ ์๋ ค์ฃผ๋ฉด ๋ฉ๋๋ค.
if let myData = keychainData(service: "My Service") {
<#do something with myData...#>
} else {
fatalError("Something went wrong with... something?")
}
๊ฒฐ๊ณผ๊ฐ ์์ผ๋ก ๋์ค๋ ๊ฒฝ์ฐ์ ์์์ ์ค๋ช
ํ ๊ฒ์ฒ๋ผ ์ฐ์ํ๊ฒ ํด๊ฒฐํ ์ ์์ง๋ง, ์น๋ช
์ ์ธ ๋จ์ ์ด ์์ต๋๋ค.
์ต์
๋์ ์์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
Optional
์ ๊ทธ์ ์ด๋ค ๊ฐ์ ๊ฐ์ธ๊ณ ์๊ฑฐ๋ ์๋ฌด๊ฒ๋ ์๋ ๊ฒ์ enum์
๋๋ค.
enum Optional<Wrapped> {
case some(Wrapped)
case none
}
๋ชจ๋ ์ผ์ด ์ ํ๋ฆฌ๋ ๊ฒฝ์ฐ์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ๋๊ตฌ์์๋ ๊ทธ์ ๊ทธ์ ๋ง๋ ๊ฐ์ ๋ฐํํ๋ฉด ๋ฉ๋๋ค.
ํ์ง๋ง I/O ์์
์์๋ ์ฌ๋ฌ ๊ฐ์ง ์ด์ ๋ก ์ผ์ด ํ์ด์ง ์ ์๋๋ฐ, Optional
์ ์ผ์ด ํ์ด์ก๋ค๋ ๊ฒ๋ง ์๋ ค์ค ์ ์์ต๋๋ค.
์ด ๊ฒฝ์ฐ์ ์ฐ๋ฆฌ๊ฐ ํ ์ ์๋ ๊ฒ์ ๊ทธ์ .none
์ ๋ฐํํ๋ ๊ฒ์
๋๋ค.
(์๋ฅผ ๋ค์๋ฉด, SecItemCopyMatching
์ ์์ฃผ ์์ฃผ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์ผ์ด ํ์ด์ง ์ ์์ต๋๋ค)
๊ทธ๋ฌ๋ฉด ํธ์ถํ๋ ๋ถ๋ถ์ ์ผ์ด ํ์ด์ง ์ด์ ๋ฅผ ์๊ณ ์ถ์ด๋ ๊ทธ์ ๋น .none
๊ฐ๋ง ๋ฐ๊ฒ ๋ ๊ฒ์
๋๋ค.
๋ชจ๋ ์ต์
์ ๊ฒฝ์ฐ๊ฐ ยฏ\_(ใ)_/ยฏ
๊ฐ์ ํ๋์ ์ด์ ๋ก ํฉ์ณ์ง๋ฉด ๊ฐ๋ ฅํ ์ํํธ์จ์ด๋ฅผ ๋ง๋ค๊ธฐ๋ ์ ์ ์ด๋ ค์์ง๋๋ค.
์ด๋ฌํ ์ํฉ์ ์ด๋ป๊ฒ ํด๊ฒฐํ ์ ์์๊น์?
ํ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ธ์ด ๋จ์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ธ ํจ์๊ฐ ์๋ฌ์ ๋ฐํ ๊ฐ๊น์ง ๋ฐ์ํ๋๋ก(throws) ํ๋ ๊ฒ์
๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ด ๋ฐฉ๋ฒ์ด ๋ฐ๋ก Swift 2์์ ์ถ๊ฐ๋ throws/throw
์ do/catch
๋ฌธ๋ฒ์
๋๋ค.
Optional
์ ์ฆ๋ช
์ ๋ํด ์กฐ๊ธ ๋ ์๊ฐํด๋ด
์๋ค. Optional
์ ๊ฐ ๋๋ nil
์ ๊ฐ์ง๋๋ฐ ์ด nil
์ ์๋ฌ ํํ๋ ฅ์ด ๋ถ์กฑํ ๊ฒ์ด ๋ฌธ์ ๋ผ๋ฉด, ํด๋น ์ด์๊ฐ ๋ฐ์ํ๋ ์ด์ ๊น์ง ๋ค์ด์๋ ์๋ฌ์ ๊ธฐ์กด๊ณผ ๋๊ฐ์ ๊ฐ์ ๊ฐ์ง๋ ์๋ก์ด Optional
์ ์ด๋จ๊น์?
๋ฐ๋ก ๊ทธ๊ฒ๋๋ค!
๊ทธ ์๋ก์ด Optional
์ ์๋ก์ด ์ด๋ฆ์ ๋ฐ์์ Result
ํ์
์ผ๋ก ์ธ์์ ๋์์ต๋๋ค.
๊ฒ๋ค๊ฐ Swift 5 ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค!
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
Result
๋ ์ฑ๊ณตํ ๊ฒฝ์ฐ์ ๊ฐ์, ์คํจํ ๊ฒฝ์ฐ์ ์๋ฌ๋ฅผ ๊ฐ์ง๋ ํ์
์
๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์์์ ๋ง๋ค์๋ ํค์ฒด์ธ ์ฝ๋๋ฅผ ์กฐ๊ธ ๋ ๋ฐ์ ์์ผ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๋จผ์ ์ปค์คํ
Error
ํ์
์ ์ ์ํด์ nil
์ ํ๋ถํ๊ฒ ํํํ ์ ์๋๋ก ๋ง๋ญ๋๋ค.
enum KeychainError: Error {
case notData
case notFound(name: String)
case ioWentBad
<#...#>
}
๋ค์์ keychainData
์ ์ ์์ ์๋ Data?
๋์ ์ Result<Data, Error>
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ผ์ด ํ์ด์ง์ง ์๊ณ ๋๋ฐ๋ก ์คํ๋๋ค๋ฉด .success
์ ์ฐ๊ด ๊ฐ๋ฅผ ๋ฐ๊ฒ ๋ ๊ฒ์
๋๋ค.
SecItemCopyMatching
์ ๋ง๊ณ ๋ค์ํ ์ฌ์์ด ๋ค์ด๋ฅ์น๋ค๋ฉด ์ด๋จ๊น์?
์ด์ nil
์ ๋ฐํํ๋ ๊ฒ ๋์ ์ ๊ฐ๋ณ ์๋ฌ๋ค์ด .failure
์ ๊ฐ์ธ์ ธ์ ๋์ค๋ ๊ฒ์ ๋ณด๊ฒ ๋ ๊ฒ์
๋๋ค.
func keychainData(service: String) -> Result<Data, Error> {
let query: NSDictionary = [...]
var ref: CFTypeRef? = nil
switch SecItemCopyMatching(query, &ref) {
case errSecSuccess:
guard let data = ref as? Data else {
return .failure(KeychainError.notData)
}
return .success(data)
case errSecItemNotFound:
return .failure(KeychainError.notFound(name: service))
case errSecIO:
return .failure(KeychainError.ioWentBad)
<#...#>
}
}
์ด์ ํธ์ถ ๋ถ๋ถ์์ ์ป์ ์ ์๋ ์ ๋ณด๊ฐ ๋ง์์ก๋ค์!
๊ทธ๋ฌ๋ฉด switch
๋ฌธ์ ๋ฐํ๋ ๊ฐ์ ์ฌ์ฉํด์ ์ฑ๊ณต๊ณผ ์คํจ ๋ชจ๋ ๊ฐ๋ณ์ ์ผ๋ก ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
switch keychainData(service: "My Service") {
case .success(let data):
<#do something with data...#>
case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")
case .failure(KeychainError.io):
print("Error reading from the keychain.")
case .failure(KeychainError.notData):
print("Keychain is broken.")
<#...#>
}
๋ชจ๋ ๊ฒฝ์ฐ๋ฅผ ๋ค๋ฃฐ ์ ์๋ค๋ ์ ์์ Result
๋ Optional
์ ์์ ํธํ์ด๋ผ๊ณ ๋ ํ ์ ์์ต๋๋ค. ์ด๋ป๊ฒ ์ด ๊ธฐ๋ฅ์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ค์ด๊ฐ๋ ๋ฐ 5๋
์ด๋ ๊ฑธ๋ ธ์๊น์?
์์ฝ๊ฒ๋ Result
๋ํ ์ ์ฃผ๋ฅผ ๋ฐ์ ์น๋ช
์ ์ธ ๋จ์ ์ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ง๊ธ๊น์ง๋ ํ๋์ ํจ์์ ํ๋์ ํธ์ถ๋ง ์์ด์ ๋์ค์ง ์์์ต๋๋ค. Result
๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ๋ ๊ฐ ๋ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
func makeAvatar(from user: Data) -> Result<UIImage, Error> {
<#Return avatar made from user's initials...#>
<#or return failure...#>
}
func save(image: UIImage) -> Result<Void, Error> {
<#Save image and return success...#>
<#or returns failure...#>
}
{% info %}
save(image:)
์ ๋ฐํ ํ์
์ค ์ฑ๊ณตํ ํ์
์ด Void
์ธ ๊ฒ์ ๊ธฐ์ตํ์ธ์.
์๊ฐํด๋ณด๋ฉด ๋น์ฐํ์ง๋ง ์ฑ๊ณตํ ๋๋ง๋ค ํน์ ํ ๊ฐ์ ๋ฐํํด์ผ ํ ํ์๋ ์์ต๋๋ค.
๊ทธ์ ์ฑ๊ณต ์ฌ๋ถ๋ง ์๋ ๊ฒ์ด ์ถฉ๋ถํ ๋๋ ์๊ฑฐ๋ ์.
{% endinfo %}
์ ์์ ๋ฅผ ๋ณด๋ฉด, ์ฒซ ๋ฒ์งธ ํจ์๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์๋ฐํ๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๊ฒ์ด๊ณ , ๋ ๋ฒ์งธ ํจ์๋ ๋์คํฌ์ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๋ ๊ฒ์
๋๋ค.
๊ตฌํ ๋ฐฉ์์ ์ฐ๋ฆฌ์ ๋ชฉ์ ์ ๊ทธ๋ ๊ฒ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. ๊ทธ์ ๊ทธ๋ค์ด Result
ํ์
์ ๋ฐํํ๋ค๋ ๊ฒ๋ง ์๋ฉด ๋ฉ๋๋ค.
์ด์ ๋ ํค์ฒด์ธ์์ ๊ฐ์ ธ์จ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก ์๋ฐํ๋ฅผ ๋ง๋ค๊ณ ๊ทธ ์๋ฐํ๋ฅผ ๋์คํฌ์ ์ ์ฅํ๋ ์์ ์ ํด๋ณด๊ฒ ์ต๋๋ค. ๋ฌผ๋ก ๊ทธ ๊ณผ์ ์์ ๋ฐ์ํ๋ ์๋ฌ๋ค๋ ๋ชจ๋ ์ฒ๋ฆฌํด์ผ๊ฒ ์ฃ ?
๋ค์๊ณผ ๊ฐ์ด ์๋ํด ๋ณผ ์ ์๊ฒ ์ต๋๋ค.
switch keychainData(service: "UserData") {
case .success(let userData):
switch makeAvatar(from: userData) {
case .success(let avatar):
switch save(image: avatar) {
case .success:
break
case .failure(FileSystemError.readOnly):
print("Can't write to disk.")
<#...#>
}
case .failure(AvatarError.invalidUserFormat):
print("Unable to generate avatar from given user.")
<#...#>
}
case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")
<#...#>
}
์ด๊ฒ ๋ณด์ธ์. ๋ ํจ์๋ฅผ ์ถ๊ฐํ์ ๋ฟ์ธ๋ฐ ์ค์ฒฉ๋ฌธ์ด ์์ฒญ๋๊ฒ ๋ง์์ก์ต๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์ ์๋ฌ ํธ๋ค๋ง์ด ์ฌํ๊ณ ๋ณต์กํด์ง๋๋ค
๋คํํ๋ Optional
๊ณผ ๊ฐ์ด Result
๋ด๋ถ์ flatMap
์ด ๊ตฌํ๋์ด ์๊ธฐ ๋๋ฌธ์ ํด๊ฒฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
Result
์ flatMap
์ด ์ ์ฉ๋๋ฉด .success
์ ๊ฒฝ์ฐ์ ์ฃผ์ด์ง ๊ฐ์ ํฉ์ณ์ ์๋ก์ด Result
๋ฅผ ๋ฐํํฉ๋๋ค. ๊ทธ๋ฌ๋ .failure
์ ๊ฒฝ์ฐ์ flatMap
์ด ์์ ์์ด .failure
์ ๋ฐํํฉ๋๋ค.
{% info %}
flatMap
๊ณผ ๊ฐ์ ๊ตฌํ๋ค์ ๋๋๋ก ๋ชจ๋๋๋ผ๊ณ ๋ถ๋ฆ
๋๋ค.
์ด๊ฒ์ ๋ชจ๋๋๊ฐ ๋ชจ๋ ๊ณตํต๋ ์์ฑ์ ๊ณต์ ํ๋ค๋ ๊ฒ์ ์๋๋ฐ ๋์์ ์ค๋๋ค.
๋จ์ด ์์ฒด๊ฐ ์น์ํ์ง ์๊ณ ๋ฌด์์ธ ์ ์์ง๋ง ๊ด์ฐฎ์ต๋๋ค.
์ค๋ ์ดํดํด์ผ ํ ๊ฒ์ flatMap
๋จ ํ๋๋๊น์.
{% endinfo %}
{% warning %}
Swift 4.1์์ flatMap
์ด compactMap
์ผ๋ก ๋์ฒด๋์๋ค๋ ์คํด๊ฐ ์์ต๋๋ค.
๊ทธ๋ ์ง ์์ต๋๋ค!
Sequence
์์ Optional
์์๋ฅผ flatMap
ํ๋ ๊ฒ์ฒ๋ผ ํน๋ณํ ์ผ์ด์ค๋ง ์ญ์ ๋์์ต๋๋ค.
{% endwarning %}
์ด๋ฌํ ๊ท์น์ ์งํค๋ฉฐ ์๋ฌ๋ฅผ ๋ณด๋ด๊ธฐ ๋๋ฌธ์ ๊ฐ ๋จ๊ณ์ .failure
๋ฅผ ์๊ฐํ์ง ์๊ณ flatMap
์ ์ฌ์ฉํด์ ์ฐ์ฐ์ ํฉ์ฑํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋ฐฉ์์ ์ฐ๋ฆฌ์ ์ฝ๋์ ์ค์ฒฉ์ ์ต์ํํด์ฃผ๊ณ ์๋ฌ ํธ๋ค๋ง๊ณผ ์ฐ์ฐ์ ๋ถ๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
let result = keychainData(service: "UserData")
.flatMap(makeAvatar)
.flatMap(save)
switch result {
case .success:
break
case .failure(KeychainError.notFound(let name)):
print(""\(name)" not found in keychain.")
case .failure(AvatarError.invalidUserFormat):
print("Unable to generate avatar from given user.")
case .failure(FileSystemError.readOnly):
print("Can't write to disk.")
<#...#>
}
์ฝ๋ ํ์ง์ด ํฅ์๋๋ค๋ ๊ฒ์ ์์ฌ์ ์ฌ์ง๊ฐ ์๋ค์. ํ์ง๋ง ์ด๋ ์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์ด .flatMap
์ ์์์ผ ํด์ ๋ ์ง๊ด์ ์ผ๋ก ๋ ๊ฒ์
๋๋ค.
{% warning %}
์ง๊ธ๊น์ง์ ๋ด์ฉ์ ์๋ฒฝํ ๊ฒฐํฉ์ฑ(Composability)์ ์ต์ ํ๋ ์๋๋ฆฌ์ค์
๋๋ค. (์ฒซ ์ฐ์ฐ์ ์ถ๋ ฅ์ด ๋ ๋ฒ์งธ ์ฐ์ฐ์ ์
๋ ฅ์ด ๋๊ณ , ๋ ๋ฒ์งธ ์ฐ์ฐ์ ์ถ๋ ฅ์ด ์ธ ๋ฒ์งธ ์ฐ์ฐ์ ์
๋ ฅ์ด ๋๋ ๊ฒ์ฒ๋ผ ์ด์ด์ง๋ ์์ฑ)
๋ง์ฝ ์ฐ์ฐ์๊ฐ ์๋ฌด๊ฒ๋ ๋ฐ์ง ์๋๋ค๋ฉด ์ด๋จ๊น์?
์๋๋ฉด ํ๋ ์ด์์ ๊ฐ์ ํ์๋ก ๋ฐ๋๋ค๋ฉด ์ด๋จ๊น์?
๋๋ ๊ฐ ์ฐ์ฐ์ด ์
๋ ฅ์ผ๋ก ๋ฐ๋ ๊ฐ๊ณผ ์ถ๋ ฅ์ผ๋ก ๋ด๋ณด๋ด๋ ๊ฐ๋ค์ด ์๋ก ํ์
์ด ๋ค๋ฅธ ๊ฒฝ์ฐ์ ์ด๋จ๊น์?
์ฐ์ํ์ง ์์ ์ ์์ง๋ง ์ด๋ฐ ๊ฒฝ์ฐ์ flatMap
์ ํ๋ฉด ํด๊ฒฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
{% endwarning %}
๋น๊ต๋ฅผ ์ํด ์์ ์ง ์ฝ๋๋ฅผ Swift 2์ ์ถ๊ฐ๋ do/catch
๋ฐฉ์์ผ๋ก ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
do {
let userData = try keychainData(service: "UserData")
let avatar = try makeAvatar(from: userData)
try save(image: avatar)
} catch KeychainError.notFound(let name) {
print(""\(name)" not found in keychain.")
} catch AvatarError.invalidUserFormat {
print("Not enough memory to create avatar.")
} catch FileSystemError.readOnly {
print("Could not save avatar to read-only media.")
} <#...#>
์ฒซ ๋ฒ์งธ๋ก ์๊ฐํด์ผ ํ ๋ด์ฉ์ ๋ ์ฝ๋๊ฐ ์ผ๋ง๋ ๋น์ทํ์ง ์ดํด๋ณด๋ ๊ฒ์ ๋๋ค. ๋ ๋ค ์์ชฝ์ ์ฐ์ฐํ๋ ์ฝ๋๊ฐ ์๊ณ ์๋์ชฝ์ ์๋ฌ ํธ๋ค๋งํ๋ ๋ถ๋ถ์ด ์๋ ๊ฒ์ด ๋น์ทํ๋ค์.
{% info %}
๋ ์ฝ๋๊ฐ ๋น์ทํ๊ฒ ์๊ธด ๊ฒ์ ์ฐ์ฐ์ด ์๋๋๋ค. Swift์ ์๋ฌ ํธ๋ค๋ง์ ๋๋ถ๋ถ์ด Result
์ ๋น์ทํ ํ์
์ ๋ฐํํ๊ฑฐ๋ ๋ฒ๊ฒจ๋ด๋ ์ผ์ ์ข ๋ ์ฝ๊ฒ ํด์ฃผ๋ ๊ฒ์ด์์ต๋๋ค.
๊ณ์ ์ดํด๋ณด์์ฃ ...
{% endinfo %}
Result
๋ฒ์ ์ flatMap
์ ํตํด์ ์ฐ์ฐ์๋ค๋ผ๋ฆฌ ์ฐ๊ฒฐ์ด ๋ผ ์๋ ๋ฐ๋ฉด์ do/catch
์ฝ๋๋ ์๋ฌ ํธ๋ค๋ง์ด ์ผ์ด๋๋๋ก ํ๊ธฐ ์ํด try
๋ฅผ ๊ณ์ ์ฌ์ฉํ์ต๋๋ค.
Result
๋ฒ์ ์ ๋ด๋ถ ์ด๊ฑฐํ์ ์์์ผ ํ๊ณ ๊ทธ๋ฅผ ํตํด switch
๋ฌธ์ ์ฌ์ฉํด์ผ ํ์ง๋ง do/catch
๋ฒ์ ์ ์๋ฌ ์์ฒด์ ๋ ์ง์คํ ์ ์๋๋ก ํด์ค๋๋ค.
์๋ก ์๊ธด ์๋ฌ ํธ๋ค๋ง ๋ฌธ๋ฒ์ Result
์ ๊ด๋ จ๋ ๋ณต์กํ ๋ด์ฉ์ ํจ๊ณผ์ ์ด๊ฒ ์์ ๊ธด ํ์ง๋ง ์ด๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์ด๊ฑฐํ, ์ฐ๊ด ๊ฐ, ์ ๋ค๋ฆญ, flatMap, ๋ชจ๋๋ ๋ฑ์ ํ์ตํด์ผ ํฉ๋๋ค...
Swift 2์ ์ถ๊ฐ๋ ์๋ฌ ํธ๋ค๋ง ๋ฐฉ๋ฒ์ Result
์ ๊ทธ ์ธ๋ค์ ํ์ตํ์ง ์์๋ ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ง๋ 5๋ ๊ฐ ๊ทธ๊ฒ๋ค์ ๋ค ์ตํ์ จ๋ค๋ฉด ์ถ๊ฐํ์ง ์์ ์ด์ ๊ฐ ์๊ฒ ์ฃ ?
๋ค๋ฅธ ๋ฐฉ๋ฒ๋ค๋ ๊ทธ๋ฌ๋ฏ์ด do/catch
๋ฐฉ์์๋ ์น๋ช
์ ์ธ ๋จ์ ์ด ์กด์ฌํฉ๋๋ค...
throw
๋ ๋ง์น ํ ๋ฐฉํฅ์ผ๋ก๋ง ์๋ํ๋ return
๊ฐ์ต๋๋ค. ์ฐ๋ฆฌ๋ ํธ์ถํ ๊ณณ์๋ค๊ฐ ์๋ฌ๋ฅผ ๋ณด๋ด๋ ๊ฒ(up)๋ง ํ ์ ์๊ณ ๋ค๋ฅธ ํจ์์ ์๋ฌ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ณด๋ด๋ ๊ฒ(down)์ ํ ์ ์์ต๋๋ค.
์ด๋ฌํ "up"-only ๋ฐฉ์์ ์ผ๋ฐ์ ์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๋ด์ฉ์
๋๋ค.
์์ ์์ฑํ ํค์ฒด์ธ ๋๊ตฌ์ ์๋ฌ ํธ๋ค๋ง์ ์ดํด๋ณด๋ฉด ๋ฐ์ดํฐ ํน์ ์๋ฌ๋ฅผ ๋ณด๋ด๊ธฐ ์ํด์ return
๊ณผ throw
๊ฐ ๋๋ฌดํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
func keychainData(service: String) throws -> Data {
let query: NSDictionary = [...]
var ref: CFTypeRef? = nil
switch SecItemCopyMatching(query, &ref) {
case errSecSuccess:
guard let data = ref as? Data else {
throw KeychainError.notData
}
return data
case errSecItemNotFound:
throw KeychainError.notFound(name: service)
case errSecIO:
throw KeychainError.ioWentBad
<#...#>
}
}
ํ์ง๋ง ๋ง์ฝ ํค์ฒด์ธ์์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ ์ ํด๋ผ์ฐ๋ ์๋น์ค์์ ๊ฐ์ ธ์จ๋ค๋ฉด ์ด๋จ๊น์? ๋น ๋ฅด๊ณ ๋ฏฟ์ ์ ์๋ ์ฐ๊ฒฐ์ด ์ ๊ณต๋๋ค ํด๋ ๋คํธ์ํฌ์์ ๋ฐ์ดํฐ ๋ก๋ฉ์ ๋์คํฌ์์ ์ฝ์ด์ค๋ ๊ฒ๊ณผ ๋น๊ตํ๋ค๋ฉด ๋๋ฆด ์๋ฐ์ ์์ต๋๋ค. ๋น์ฐํ ๋ฐ์ดํฐ๊ฐ ์ค๋ ๋์ ์ฑ์ด ๋ฉ์ถ๋ ๊ฒ์ ์ํ์ง ์์ผ์๊ฒ ์ฃ ? ๊ทธ๋ฌ๋ ๋น๋๊ธฐ๋ก ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
ํ์ง๋ง ๋น๋๊ธฐ๋ก ๋ง๋ ๋ ๊ฒ์ ํธ์ถํ ๊ณณ์ผ๋ก ๋ฐํํ๋ ๊ฒ(up)์ ํ์ง ์๋๋ค๋ ๋ป์ ๋๋ค. ๋์ ์ ์๋ฃ ์์ ํด๋ก์ ๋ฅผ ๋ค์์ผ๋ก(down) ํธ์ถํ ๊ฒ์ ๋๋ค.
func userData(for userID: String, completion: (Data) -> Void) {
<#get data from the network#>
// ์๊ฐ์ด ์ง๋ ํ ์คํ
completion(myData)
}
์ค๋๋ ์ ๋คํธ์ํฌ ์ฐ์ฐ์ ๋ค์ํ ์ข
๋ฅ์ ์๋ฌ๋ก ์ธํด ์คํจํ ์ ์์ต๋๋ค.
ํ์ง๋ง ์ด ๋ชจ๋ ์๋ฌ๋ฅผ completion
์ throw
๋ก ๋๊ธธ ์๋ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ๊ฐ์ฅ ์ข์ ์ ํ์ง๋ ์๋ฌ๋ฅผ ์ต์
๋ ํ๋ผ๋ฏธํฐ๋ก ๋๊ธฐ๋ ๊ฒ์
๋๋ค.
func userData(for userID: String, completion: (Data?, Error?) -> Void) {
<# Fetch data over the network... #>
guard myError == nil else {
completion(nil, myError)
}
completion(myData, nil)
}
ํ์ง๋ง ์ด๋ ๊ฒ ๋๋ฉด ํธ์ถํ๋ ๋ถ๋ถ์์ ์ฐ๋ฆฌ๊ฐ ์์ํ๋ ์๋๋ฆฌ์ค๋ฟ๋ง ์๋๋ผ ์คํ ๋ถ๊ฐ๋ฅํ๋ค๊ณ ์๊ฐ๋๋ ์๋๋ฆฌ์ค์ ๊ดํด์๋ ์ค๋ช ํด์ผ ํฉ๋๋ค.
userData(for: "jemmons") { maybeData, maybeError in
switch (maybeData, maybeError) {
case let (data?, nil):
<#do something with data...#>
case (nil, URLError.timedOut?):
print("Connection timed out.")
case (nil, nil):
fatalError("๐คHmm. This should never happen.")
case (_?, _?):
fatalError("๐ฑWhat would this even mean?")
<#...#>
}
}
์๋ ๊ฐ์์ผ๋ฉด "๋ฐ์ดํฐ ๋๋ nil"๊ณผ "์๋ฌ ๋๋ nil"๊ฐ ๋ค์์ธ ๊ฒ์ ๋ณด๋ ๋์ ์ ๊ทธ์ ๋ฐ์ดํฐ ๋๋ ์๋ฌ์ผ ๊ฒฝ์ฐ๋ง ๊ด๋ฆฌํ ์ ์์ด์ ๋์์ด ๋ง์ด ๋ฉ๋๋ค.
๋ฐ์ดํฐ ๋๋ ์๋ฌ์?
๋๊ฒ ์น์ํ ๊ตฌ๋๋ค์.
๊ทธ๋ ๋ค๋ฉด Result
๋ฅผ ์จ๋ณด๋ ๊ฒ์ ์ด๋จ๊น์?
func userData(for userID: String, completion: (Result<Data, Error>) -> Void) {
// ๋ชจ๋ ๊ฒ์ด ์ ํ๋ ธ์ ๊ฒฝ์ฐ:
completion(.success(myData))
// ์ผ์ด ์ ์ ํ๋ฆฐ ๊ฒฝ์ฐ:
completion(.failure(myError))
}
ํธ์ถ ๋ถ๋ถ์ ๋ค์์ฒ๋ผ ๋ ๊ฒ์ ๋๋ค
userData(for: "jemmons") { result in
switch (result) {
case (.success(let data)):
<#do something with data...#>
case (.failure(URLError.timedOut)):
print("Connection timed out.")
<#...#>
}
์!
์ฝ๋์์ ๋ณผ ์ ์๋ฏ์ด Result
ํ์
์ "ํจ์๊ฐ throws
๋ก ํ์๋ ๋ ๋ฆฌํด๋๋ ๊ฒ"์ ๋ํ Swift์ ์ถ์์ ์ธ ์์ด๋์ด๋ฅผ ๊ตฌ์ฒดํํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ฅผ ์ปดํ๋ฆฌ์
ํธ๋ค๋ฌ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ตฌ์ฒด์ ์ธ ํ์
์ผ๋ก ๋ฐ๋ ๋น๋๊ธฐ ์ฐ์ฐ์ ๋ค๋ฃฐ ๋ ์ฌ์ฉํ ์ ์์ ๊ฒ์
๋๋ค
{% info %}
์ถ์์ ์ธ ์๋ฌ ํธ๋ค๋ง๊ณผ ๊ตฌ์ฒด์ ์ธ Result
์ฌ์ด์ ์ด์ค์ฑ์ ๋์ ์ ์ ๋ฉด์ฒ๋ผ ์๋ก ๋ฐ๊พธ๋ ๊ฒ์ด ์์ฃผ ์ฌ์ํฉ๋๋ค.
Result { try somethingThatThrows() }
์ด ์ฝ๋๋ catch ํ ์ ์๋ ์ถ์์ ์ธ ๋ฌด์ธ๊ฐ๋ฅผ ์ด๋๊ฐ์ ๋๊ธธ ์ ์๋ ๊ตฌ์ฒด์ ์ธ result ํ์ ์ผ๋ก ๋ณ๊ฒฝํฉ๋๋ค.
try someResult.get()
์ด ์ฝ๋๋ ๊ตฌ์ฒด์ ์ธ result ํ์ ์ catch ํ ์ ์๋ ์ถ์์ ์ธ ๋ฌด์ธ๊ฐ๋ก ๋ง๋ค์ด์ค๋๋ค. {% endinfo %}
Result
์ ํํ๋ Swift 2 ์ดํ๋ก ์๋ฌ ํธ๋ค๋ง์ผ๋ก ํจ์ถ๋์๋ ๋ฐ๋ฉด์, ์ด์ ๋ ๊ณต์์ ์ผ๋ก Swift 5 ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฃผ๋ก ๋น๋๊ธฐ ์๋ฌ๋ฅผ ๋ค๋ฃจ๋ ๊ณณ์์ ์ฐ๋๋ก ์ถ๊ฐ๋์ต๋๋ค.
(๊ณต์์ ์ผ๋ก ๋ฐํ๋๊ธฐ ์ ์๋ ๋ช๋ช ๊ฐ๋ฐ์๋ค์ด ๊ทธ๋ค๋ง์ ๋ฒ์ ์ ๊ฐ๋ฐํ์ต๋๋ค)
์์์ ๋ณด์๋ฏ์ด ์ต์
๋ ๋ ๊ฐ (Value?, Error?)
๋ฅผ ๋๊ธฐ๋ ๊ฒ๋ณด๋ค ๋ซ๋ค๋ ๊ฒ์ ์์ฌํ ์ฌ์ง๊ฐ ์์ต๋๋ค.
ํ์ง๋ง ์์ง ๋ณด์ฌ๋๋ฆฌ์ง ์์ ๊ฒฝ์ฐ์ธ ๋ ์ด์์ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ ํธ์ถ์์ Result
๋ฅผ ์ฌ์ฉํด์ ์๋ฌ๋ฅผ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ณด์ฌ๋๋ฆฌ๊ฒ ์ต๋๋ค.
flatMap
์ ๋์์ ๋ณํ์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ด๊ฒ์ ๋น๋๊ธฐ ๊ณต๊ฐ์์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ๋น๋๊ธฐ ์ฐ์ฐ์ ํฉ์ฑํ ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
userData(for: "jemmons") { userResult in
switch userResult {
case .success(let user):
fetchAvatar(for: user) { avatarResult in
switch avatarResult {
case .success(let avatar):
cloudSave(image: avatar) { saveResult in
switch saveResult {
case .success:
// ๋ชจ๋ ์ฑ๊ณต!
case .failure(URLError.timedOut)
print("Operation timed out.")
<#...#>
}
}
case .failure(AvatarError.invalidUserFormat):
print("User not recognized.")
<#...#>
}
}
case .failure(URLError.notConnectedToInternet):
print("No internet detected.")
<#...#>
}
{% info %}
์ค์ ๋ก flatMap
๊ณผ ๊ฐ์ ๋ฐฉ์์ ํธ๋ค๋ง๋ ํ ์ ์๋๋ฐ, ์ด๋ Continuation Monad๋ผ๊ณ ๋ถ๋ฆฝ๋๋ค.
์ด๋ ์ง๊ธ ์ตํ๊ธฐ์ ์ถฉ๋ถํ ๋ณต์กํด์ ์ด๊ฒ ํ๋๋ง์ผ๋ก๋ ๋ช ๊ฐ์ ๋ธ๋ก๊ทธ ํฌ์คํ
์ด ๋์ฌ ๊ฒ์
๋๋ค.
{% endinfo %}
Swift๊ฐ ๋์ ์๋ฌ ์ฒ๋ฆฌ์์ Result
์ค์ฒฉ ๋ฌธ์ ๋ฅผ ์ ์ํ๊ธฐ ์ํด์ do/catch
๋ฌธ๋ฒ์ ์ฌ์ฉํ ๊ฒ์ฒ๋ผ, ๋น๋๊ธฐ ์๋ฌ ์ฒ๋ฆฌ์ ๋ํด์๋ ๋น์ทํ ์ ์์ด ๋ง์ต๋๋ค.
async/await ์ ์์ ๊ทธ์ค ํ๋์ ๋๋ค. ์ด ์ ์์ ์ฐ๋ฆฌ ์ฝ๋์ ์ ์ฉํด๋ณธ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋ง๋ค ์ ์์ต๋๋ค.
do {
let user = try await userData(for: "jemmons")
let avatar = try await fetchAvatar(for: user)
try await cloudSave(image: avatar)
} catch AvatarError.invalidUserFormat {
print("User not recognized.")
} catch URLError.timedOut {
print("Operation timed out.")
} catch URLError.notConnectedToInternet {
print("No internet detected.")
} <#...#>
์ฌ๊ธฐ๊น์ง Swift ์๋ฌ ํธ๋ค๋ง์ ์ด๋์ด ๊ณผ๋๊ธฐ์ ๊ธธ์ ๋ฐํ์ค Result
ํ์
์ ๋ํด ์์๋ณด์์ต๋๋ค.