๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐ŸŽ iOS/Swift

[Swift] ์˜ต์…”๋„(Optional) ? ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ + String ๋นˆ๊ฐ’, ์˜ต์…”๋„ ๊ตฌ๋ถ„

728x90

๐Ÿ’ก ์Šค์œ„ํ”„ํŠธ์˜ ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ ๊ฐ€์šด๋ฐ ํ•˜๋‚˜์ธ ์˜ต์…”๋„(Optional)์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž!


[Contents]

  • ์˜ต์…”๋„ ์ด๋ž€?
  • ์˜ต์…”๋„ ์ถ”์ถœ 
    1. ๊ฐ•์ œ ์ถ”์ถœ(Forced unwrapping)
    2. ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ(Optional Binding)
      • 2.1. ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ค‘์ฒฉ
      • 2.2. nil ๊ฒฐํ•ฉ ์—ฐ์‚ฐ์ž
    3. ์•”์‹œ์  ์ถ”์ถœ ์˜ต์…”๋„(Implicitly Unwrapped Optional)
    4. ์˜ต์…”๋„ ์ฒด์ด๋‹(Optional Chaining)
  • String ๋นˆ๊ฐ’, ์˜ต์…”๋„ ๊ตฌ๋ถ„

๐ŸŽ ์˜ต์…”๋„(Optional) ์ด๋ž€?

  • ์ผ์ข…์˜ ์•ˆ์ „ ์žฅ์น˜๋กœ ๋ณ€์ˆ˜ ์•ˆ์— ๊ฐ’์ด ์žˆ์„ ์ˆ˜๋„, ์—†์„ ์ˆ˜๋„(nil) ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜ต์…”๋„ ๋ณ€์ˆ˜ ์„ ์–ธ ? ํ‚ค์›Œ๋“œ ์‚ฌ์šฉํ•˜์—ฌ ํ‘œํ˜„ํ•œ๋‹ค.
  • ์ฆ‰, nil์ด ๋  ์ˆ˜๋„ ์žˆ๋Š” ์ธ์Šคํ„ด์Šค๋Š” ๋ฐ˜๋“œ์‹œ ์˜ต์…”๋„ ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.
  • ์ฆ‰, ์˜ต์…”๋„๋กœ ์„ ์–ธ๋œ ๋ณ€์ˆ˜์—๋งŒ! nil ํ• ๋‹น ๊ฐ€๋Šฅ!
    • ์˜ต์…”๋„์ด ์ ์šฉ๋œ ์–ด๋–ค ํƒ€์ž…์˜ ์ธ์Šคํ„ด์Šค์—๋„ nil์ด ๋  ์ˆ˜ ์žˆ๋‹ค.
    • ๋ฐ˜๋ฉด์—, Objc๋Š” ๊ฐ์ฒด๋งŒ nil ์ง€์ • ๊ฐ€๋Šฅ
var city: String? = "Seoul"
print(city)    //Seoul

city = nil
print(city)    //nil
  • ์˜ต์…”๋„์ด ์•„๋‹Œ ๋ณ€์ˆ˜์—๋Š” ์˜ต์…”๋„ ๊ฐ’์ด ๋“ค์–ด๊ฐˆ ์ˆ˜ ์—†๊ณ  ์—ฐ์‚ฐ๋„ ๋ถˆ๊ฐ€!! ์ถ”์ถœํ•ด์„œ ํ• ๋‹นํ•ด์ฃผ์–ด์•ผ ํ•จ
var number1:Int? = 20
var number2:Int = 100

number1 = number2   //๊ฐ€๋Šฅ
//number2 = number1   //๋ถˆ๊ฐ€๋Šฅ
//let sum = number1 + number2   //๋ถˆ๊ฐ€๋Šฅ

๐ŸŽ ์˜ต์…”๋„(Optional) ์ถ”์ถœํ•˜๊ธฐ - ์˜ต์…”๋„ ํ•ด์ œ(Optional Unwrapping)

โœ”๏ธ 1. ๊ฐ•์ œ ์ถ”์ถœ(Forced unwrapping)

  • ์˜ต์…”๋„์˜ ๊ฐ’์„ ๊ฐ•์ œ ์ถ”์ถœํ•˜๋ ค๋ฉด ์˜ต์…”๋„ ๊ฐ’์˜ ๋’ค์— ๋Š๋‚Œํ‘œ(!)๋ฅผ ๋ถ™์—ฌ์ฃผ๋ฉด ๊ฐ•์ œ๋กœ ๊ฐ’์„ ์ถ”์ถœํ•˜์—ฌ ๋ฐ˜ํ™˜ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํ•˜์ง€๋งŒ, ๊ฐ•์ œ ์ถ”์ถœ์‹œ ์˜ต์…”๋„์— ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ(nil์ด ์•„๋‹Œ ๊ฒฝ์šฐ), ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ
var food: String? = "pizza"

print(food)     //Optional("pizza")
print(food!)    //pizza

food = nil
//print(food!)    //๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!!!

โœ”๏ธ 2. ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ(Optional Binding)

  • ์˜ต์…”๋„์— ๊ฐ’์ด ์žˆ๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์šฉํ•œ ํŒจํ„ด์ด๋‹ค.
  • ์˜ต์…”๋„ ํƒ€์ž…์˜ ๊ฐ’์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ถ”์ถœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
  • ์˜ต์…”๋„์— ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์˜ต์…”๋„์—์„œ ์ถ”์ถœํ•œ ๊ฐ’์„ ์ƒ์ˆ˜๋‚˜ ๋ณ€์ˆ˜๋กœ ํ• ๋‹นํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์คŒ.
  • ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ์€ if ๋˜๋Š” while ๊ตฌ๋ฌธ ๋“ฑ๊ณผ ๊ฒฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
  • ๋ฐฉ๋ฒ•์—๋Š” ํฌ๊ฒŒ if let ๊ตฌ๋ฌธ๊ณผ guard let ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Œ
    • if let 
    • guard let
      • nil ์ฒดํฌ์™€ ํ•จ๊ป˜ ์กฐ๊ฑด๋ฌธ ์ž‘์„ฑ ๊ฐ€๋Šฅ
      • ๋ฐ˜๋“œ์‹œ return ์ด๋‚˜ throw ์ฒ˜๋Ÿผ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋‚˜ ํ–‰๋™์ด ์ข…๋ฃŒ๋˜๋Š” ๋ช…๋ น์ด ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค.
let tmpString : String? = "Hello"
//2-1. if let ์‚ฌ์šฉ
if let opt1 = tmpString {
    // ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ
    print(opt1)
}else{
    // ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ
    print("๊ฐ’ ์—†์Œ")
}

//2-2. guard let ์‚ฌ์šฉ
func bindingWithGuard() {
    guard let opt2 = tmpString else{
    	// ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ
        return
    }
    // ๊ฐ’์ด ์—†๋Š” ๊ฒฝ์šฐ
}

2.1. ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ค‘์ฒฉ

์˜ต์…”๋„ ๊ฐ’์ด ๋งŽ์€ ๊ฒฝ์šฐ, ์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ค‘์ฒฉ๋˜๋Š” ๊ฒฝ์šฐ๋“ค์ด ์žˆ๋Š”๋ฐ,

์ด์ค‘ ์ •๋„๋Š” ๊ดœ์ฐฎ์ง€๋งŒ ์‚ผ, ์‚ฌ์ค‘์œผ๋กœ ์ค‘์ฒฉ๋˜๋Š” ๊ฒฝ์šฐ, ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ง„๋‹ค.

var errorCodeString: String?
errorCodeString = "404"
if let theError = errorCodeString{
    if let errorCodeInteger = Int(theError){
    //Int(theError)๊ฐ€ ์ •์ˆ˜๊ฐ’์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ์˜ต์…”๋„ ๋ฆฌํ„ดํ•˜๋ฏ€๋กœ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ํ•„์š”
        printName("\(theError) : \(errorCodeInteger)")
    }
}

๋”ฐ๋ผ์„œ, ์•„๋ž˜์™€ ๊ฐ™์ด ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ์„ ํ•œ์ค„๋กœ๋„ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•˜๊ณ , ์ถ”๊ฐ€์ ์œผ๋กœ ์กฐ๊ฑด๋ฌธ ์ถ”๊ฐ€๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

var errorCodeString: String?
errorCodeString = "404"

if let theError = errorCodeString, let errorCodeInteger = Int(theError), errorCodeInteger == 404{
        printName("\(theError) : \(errorCodeInteger)")
}

2.2. nil ๊ฒฐํ•ฉ ์—ฐ์‚ฐ์ž(nil coalescing operator)

์•„๋ž˜์™€ ๊ฐ™์ด ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’์„ ์„ธํŒ…ํ•  ๋•Œ, ๋‹จ์ˆœํ•œ ์—ฐ์‚ฐ์ž„์—๋„ ์ฝ”๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ๊ธธ๋‹ค.

var errorCodeString: String?
errorCodeString = "404"
let description : String

if let theError = errorCodeString{
    description = theError
}else{
    description = "No error"
}โ€‹

์ด๋Ÿฐ ๊ฒฝ์šฐ, nil ๊ฒฐํ•ฉ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•˜๋‹ค.

let description =  errorCodeString ?? "No error"

nil ๊ฒฐํ•ฉ ์—ฐ์‚ฐ์ž ํ‘œํ˜„๋ฒ•์€ ์‰ฝ๊ฒŒ ํ‘œํ˜„ํ•˜๋ฉด,

์•„๋ž˜ ์ฝ”๋“œ์—์„œ

a๋ณ€์ˆ˜์— b์— ๊ฐ’์ด ์žˆ์œผ๋ฉด(), b๋ฅผ ๋„ฃ๊ณ ,

var b : String? = "test"
let a = b ?? "Nothing"
print(a)
//test ์ถœ๋ ฅ

b์— ๊ฐ’์ด ์—†์œผ๋ฉด(var b : String?), "Nothing"์„ ๋„ฃ์„ ๊ฒƒ์ด๋‹ค.

var b : String?
let a = b ?? "Nothing"
print(a)
//Nothing ์ถœ๋ ฅ

 


โœ”๏ธ 3. ์•”์‹œ์  ์ถ”์ถœ ์˜ต์…”๋„(Implicitly Unwrapped Optional)

  • ์•”์‹œ์  ์ถ”์ถœ ์˜ต์…”๋„์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์„ ์–ธ์‹œ ํƒ€์ž… ๋’ค์— ๋Š๋‚Œํ‘œ(!)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • ์•”์‹œ์  ์ถ”์ถœ ์˜ต์…”๋„๋กœ ์ง€์ •๋œ ํƒ€์ž…์€ ์ผ๋ฐ˜ ๊ฐ’์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‚˜, ์˜ต์…”๋„์ด๊ธฐ ๋•Œ๋ฌธ์— nil๋„ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค.
//!์‚ฌ์šฉํ•˜์—ฌ ์„ ์–ธ
var myPhone: String! = "iPhone"
print(myPhone)    //iPhone
//nil ํ• ๋‹น ๊ฐ€๋Šฅ
myPhone = nil

 

  • nil์ด ํ• ๋‹น๋˜์–ด ์žˆ์„ ๋•Œ ์ ‘๊ทผ์„ ์‹œ๋„ํ•˜๋ฉด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  • ์ฆ‰, ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ๋กœ๋งŒ ์ œํ•œํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ตœ์„ ์ด๋‹ค!!!!
  • ๊ทธ๋ ‡๋‹ค๋ฉด, ์•„๋ž˜ ์ฝ”๋“œ๊ฐ€ ์ž˜ ์ž‘๋™ํ• ๊นŒ?
var errorCodeString2: String! = nil
let anotherErroCodeString: String = errorCodeString2 
let yetAnotherErroCodeString = errorCodeString2

์ •๋‹ต์€ 

  • ๋‘๋ฒˆ์งธ ๋ผ์ธ์—์„œ๋Š” ์˜ต์…”๋„๋กœ ์ง€์ •๋˜์ง€ ์•Š์€ ๋ณ€์ˆ˜์— ๋„ฃ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ!
  • ์„ธ๋ฒˆ์งธ ๋ผ์ธ์—์„œ๋Š” nil์ด ํ• ๋‹น๋˜๊ณ , yetAnotherErrorCodeString์€ String? ์ธ์Šคํ„ด์Šค๊ฐ€ ๋˜์–ด ์ฝ”๋“œ์˜ ์•ˆ์ •์„ฑ์€ ๋‹ด๋ณด๋œ๋‹ค. ํ•˜์ง€๋งŒ, ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ๋ช…์‹œ์„ฑ์„ ์š”๊ตฌํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์•”๋ฌต์ ์œผ๋กœ ์–ธ๋ž˜ํ•‘๋˜๋„๋ก ์„ ์–ธ์ด ํ•„์š”ํ•˜๋‹ค.
let yetAnotherErroCodeString: String! = errorCodeString2

โœ”๏ธ 4. ์˜ต์…”๋„ ์ฒด์ด๋‹(Optional Chaining) 

์˜ต์…”๋„ ์ฒด์ด๋‹๋„ ์˜ต์…”๋„ ๋ฐ”์ธ๋”ฉ ์ฒ˜๋Ÿผ ์˜ต์…”๋„ ๊ฐ’์— ์žˆ๋Š”์ง€ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค.

์ฒด์ธ์—์„œ ์˜ต์…”๋„์— ๊ฐ’์ด ์žˆ๋‹ค๋ฉด, ํ˜ธ์ถœ์€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ณ 

๊ฐ’์ด nil ์ด๋ผ๋ฉด, ์ฒด์ธ ์ž์ฒด๊ฐ€ nil์„ ๋ฆฌํ„ดํ•œ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์—์„œ errorDescription์— ๋ถ™์€ ?๋Š” ์˜ต์…”๋„ ๋ ˆ์ด๋‹ ๊ณผ์ •์˜ ์‹œ์ž‘์ž„์„ ์•Œ๋ฆฐ๋‹ค.

var errorCodeString: String?
errorCodeString = "404"

if let theError = errorCodeString, let errorCodeInteger = Int(theError), errorCodeInteger == 404{
//        printName("\(theError) : \(errorCodeInteger)")
    errorDecription = "\(errorCodeInteger + 200) : resource was not found"
}
//์˜ต์…”๋„ ์ฒด์ด๋‹์˜ ์‹œ์ž‘!!
var upCaseErrorDecription = errorDecription?.uppercased()
//604 : RESOURCE WAS NOT FOUND ์ถœ๋ ฅ
errorDecription

๋ฌธ์ œ)

์˜ต์…”๋„์ด nil์ผ ๋•Œ, ๊ทธ ๊ฐ’์— ์—‘์„ธ์Šคํ•˜๋ฉด ๋Ÿฐํƒ€์ž„์˜ค๋ฅ˜๊ฐ€ ์ผ์–ด๋‚œ๋‹ค. 

์ด ์ƒํ™ฉ์„ ๊ตฌํ˜„ํ•˜๊ณ , ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜์˜ ๋‚ด์šฉ์„ ํŒŒ์•…ํ•˜๋ผ. ๋‹จ, ๊ฐ•์ œ ์–ธ๋ž˜ํ•‘์„ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

sol)

//๊ฐ•์ œ ์–ธ๋ž˜ํ•‘์„ ์ ์šฉํ•œ ์˜ต์…”๋„์ด nil ์ธ ๊ฒฝ์šฐ
let optionalValue: String! = nil
//๊ฐ’์— ์•ก์„ธ์Šคํ•˜๋ฉด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜ ๋ฐœ์ƒ!
let runtimeValue: String = optionalValue

 

[์—๋Ÿฌ๋‚ด์šฉ]

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

๐Ÿ”ฅ String ๋นˆ๊ฐ’, ์˜ต์…”๋„ ๊ตฌ๋ถ„

์Šค์œ„ํ”„ํŠธ์˜ String ์˜ต์…”๋„์€ ์•ฝ๊ฐ„ ์ด์ƒํ•ฉ๋‹ˆ๋‹ค. 

String? ํƒ€์ž…์€ “๋ถˆ๊ฐ€๋Šฅ”ํ•œ ๊ฐ’์ด ๋‘ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค.

์™œ๋ƒ, ์˜ต์…”๋„ ํƒ€์ž…์˜ "๋นˆ" String์˜ ๊ฒฝ์šฐ๊ฐ€ nil ์ธ ๊ฒฝ์šฐ์™€, "" ์ธ ๊ฒฝ์šฐ ์ด 2๊ฐ€์ง€ ์ด๊ธฐ ๋•Œ๋ฌธ์ด์ฃ !!!!

์ด๊ฒƒ์€ ๋ฒ„๊ทธ์™€ ์˜ˆ์ƒ์น˜ ๋ชป ํ•œ ๋™์ž‘์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ตํžˆ๊ธฐ๊ฐ€ ๋ฒˆ๊ฑฐ๋กญ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ์ฒ˜๋ฆฌ๋„ ์•„์˜ˆ ๊ฐ’์ด ์—†๋Š” nil์˜ ๊ฒฝ์šฐ์ผ ๋•Œ, ๊ฐ’์€ ์žˆ์ง€๋งŒ ๋‚ด์šฉ์ด ์—†๋Š” "" ์˜ ๊ฒฝ์šฐ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

 

๋นˆ ๊ฐ’์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” isEmpty ์„ ์‚ฌ์šฉํ•ด์„œ ๋งŽ์ด ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” space ๊ฐ’๋„ ๊ฐ’์ด ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.

"Hello".isEmpty  // false
"".isEmpty       // true
"    ".isEmpty   // false

space ๊ณต๋ฐฑ์œผ๋กœ ์ „ํ™˜ํ•ด์„œ ๋นˆ ๊ฐ’์œผ๋กœ ์ธ์‹ํ•˜๊ธฐ

++์ถ”๊ฐ€ ์˜ˆ์ •

 

๐Ÿ”ฅ ๋”ฐ๋ผ์„œ nil๊ฐ’์ธ์ง€ ๊ณต๋ฐฑ์ธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ”์ธ๋”ฉ+isEmpty ์‚ฌ์šฉ

var optinalString: String?

if let result = optinalString, !optinalString!.isEmpty{
	print("๊ฐ’ ์žˆ์Œ")
}else{
	print("๊ฐ’ ์—†์Œ")
}

์ชผ๊ฐœ์„œ ์‚ฌ์šฉํ•˜๊ธฐ

var optinalString: String?

if let bindingString = optinalString{
	if !bindingString.isEmpty{
		print("๊ฐ’ ์žˆ์Œ")
	}else{
		print("๋นˆ ๊ฐ’")
	}
}else{
	print("nil ๊ฐ’")
}

 

*์ฐธ๊ณ ๋กœ, String.count == 0 ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

// Don't do this to test for empty
myString.count == 0

 

๋ฐ˜์‘ํ˜•