๊ณต๋ถ€ ์—ฐ์Šต์žฅ :-)

TIL_20180723 ~ 20180730

|

2018.07.23

  • ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ reloadํ• ๋•Œ์˜ async๋ฌธ์ œ
    private func resetTableView(indexPaths: [IndexPath]) {
      DispatchQueue.main.sync { [weak self] in // async๋กœ ํ•˜๋ฉด ์—๋Ÿฌ๋‚จ
          self?.tableView.beginUpdates()
          self?.tableView.insertRows(at: indexPaths, with: .automatic)
          self?.tableView.endUpdates()
      }
    }
    
  • beginUpdates()์™€ endUpdates() ์‚ฌ์ด์˜ ์‹œ์ ์—์„œ๋Š” ๋ชจ๋ธ์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ๋จ.
  • ํ…Œ์ด๋ธ”๋ทฐ ๋ฐ์ดํ„ฐ์†Œ์Šค๋Š” reload๋‚˜ insert๋ฅผ ํ• ๋•Œ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„(ํ…Œ์ด๋ธ” ๋ทฐ ๋‚ด์˜ ํŠน์ • ์„น์…˜์ด๋‚˜ ์…€)์„ ๋‹ด๋‹นํ•˜๋Š” ๋ชจ๋ธ์ด ๊ฐ™์€ ์ˆ˜์ธ์ง€ ๋‚ด๋ถ€์ ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค.
    • ์ด๋Š” ๋ณ€๊ฒฝ๋™์ž‘์ด ํ•„์š”์—†๋Š” ๊ณณ์—๋Š” ๋™์ž‘์„ ํ•˜์ง€ ์•Š๊ณ  ๋‚ญ๋น„๋ฅผ ๋ง‰์œผ๋ ค๊ณ  ์ด๋ ‡๊ฒŒ ๋™์ž‘ํ•จ.
    • ์œ„์˜ ์ฝ”๋“œ์—์„œ ์•„์˜ˆ ์•ฑ์ด ์ฃฝ๋Š” ์—๋Ÿฌ ๋ฐœ์ƒ
    • insertSection/insertRows, ํ˜น์€ reloadSection/rows๋กœ ์ธํ•ด์„œ ๋ณ€๊ฒฝ๋œ ํ…Œ์ด๋ธ”๋ทฐ์˜ ๋ฐ์ดํ„ฐ ์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ ์ „๊ณผ ๋‹ค๋ฅด๋‹ค๋Š” ์—๋Ÿฌ.
      Terminating app due to uncaught exception 'NSInternalInconsistencyException',
      reason: 'Invalid update: invalid number of sections.  
      The number of sections contained in the table view after the update (0) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
      
    • ์—๋Ÿฌ์ž๋ฃŒ ๋งํฌ 1- Error โ€˜Invalid update: invalid number of rows in section 0โ€™ attempting to delete row in table
    • ์—๋Ÿฌ์ž๋ฃŒ ๋งํฌ 2 - Insert rows to UITableView crash
  • ํ•ด๊ฒฐ:
    • ํ™•์ธํ•ด๋ณด๋‹ˆ insertRows์— ์ „๋‹ฌ๋˜๋Š” IndexPath๋Š” ๋ชจ๋‘ ์ž˜ ๋งŒ๋“ค์–ด์ ธ ์ „๋‹ฌ๋˜์—ˆ๋‹ค.
    • ํ•˜์ง€๋งŒ update๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  insertํ•˜๊ณ  update๊ฐ€ ๋๋‚˜๋Š” ๋™์ž‘์ด async๋กœ ์ž‘๋™ํ•˜์—ฌ ๋ชจ๋ธ ๋ณ€๊ฒฝ์ด ์–ธ์ œ ๋˜๋Š”์ง€ ๋ชจ๋ฅด๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.
    • URLSession์œผ๋กœ ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ(async) - 0๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ด๋ธ”๋ทฐ ๊ทธ๋ฆผ - ๋ชจ๋ธ ์—…๋ฐ์ดํŠธ(async) noti๋กœ ํ…Œ์ด๋ธ”๋ทฐ ์—…๋ฐ์ดํŠธ ์‹œ์ž‘ - ๋‹ค๋ฅธ ๋ชจ๋ธ๋„ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ์ค‘
    • ํ•ด๋‹น ๋™์ž‘์„ asyncํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฉด, URLSession์—์„œ ๋ชจ๋ธ์„ ๋ฐ›์•„์˜ค๋Š”๊ฒŒ ์ „๋ถ€ ์™„๋ฃŒ๋˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ๋‹ค๋ฅธ ์„น์…˜์„ ๋‹ด๋‹นํ•˜๋Š” ๋ชจ๋ธ์ด ์—…๋ฐ์ดํŠธ๋˜๊ณ ์žˆ๋Š” ์ƒํƒœ์—์„œ ๋‹ค๋ฅธ ์„น์…˜์˜ ์—…๋ฐ์ดํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์š”์ฒญํ•˜๊ณ , ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ ค๊ณ ํ•˜๋‹ˆ๊นŒ ์—…๋ฐ์ดํŠธ ์ „ํ›„์˜ ๋ฐ์ดํ„ฐ ์ˆ˜๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (1์„น์…˜์˜ ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ ์žˆ๋Š”๋ฐ 2์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŒ๋“ค์–ด์ ธ์„œ ๋˜ 2 ์„น์…˜์„ ์—…๋ฐ์ดํŠธํ•˜๋ผ๊ณ  noti๊ฐ€ ์˜ค๋Š” ์ƒํ™ฉ)
    • beginUpdates()์™€ endUpdates()์‚ฌ์ด์—์„œ ๋ชจ๋ธ๊ณผ ํ…Œ์ด๋ธ”๋ทฐ๊ฐ€ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ์˜ ์ˆ˜๊ฐ€ ๊ฐ™์•„์•ผํ•˜๋Š”๊ฒŒ ํฌ์ธํŠธ! (์—…๋ฐ์ดํŠธ ์ „์˜ ๋ฐ์ดํ„ฐ๊ฐ€ 0๊ฐœ์ด๋ฉด ํ…Œ์ด๋ธ”๋ทฐ์˜ rows๋„ 0, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์„œ 3๊ฐœ๊ฐ€ ๋˜๋ฉด rows๋„ 3๊ฐœ์—ฌ์•ผํ•˜๋ฉฐ, ์—๋Ÿฌ๋ฉ”์‹œ์ง€์—์„œ๋„ ์–ธ๊ธ‰ํ•˜๊ณ  ์žˆ๋Š” ๋‚ด์šฉ์ด๋‹ค.)
    • ๋”ฐ๋ผ์„œ ๋ชจ๋ธ์€ ๋น„๋™๊ธฐ๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค๊ณ  ํ•˜๋”๋ผ๋„, ํ…Œ์ด๋ธ”๋ทฐ ์—…๋ฐ์ดํŠธ๋Š” syncํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ํ•˜์—ฌ ๋ฉ”์ธ์Šค๋ ˆ๋“œ์—์„œ ์ง๋ ฌ์ ์œผ๋กœ(์ฐจ๋ก€๋Œ€๋กœ) ์—…๋ฐ์ดํŠธ๋˜๊ฒŒํ•˜์—ฌ ๋ชจ๋ธ์—…๋ฐ์ดํŠธ์™€ ํ…Œ์ด๋ธ”๋ทฐ ์—…๋ฐ์ดํŠธ์˜ ํƒ€์ด๋ฐ์„ ๋งž์ถ˜๋‹ค!

2018.07.24

  • ์ฝ์–ด๋ณด๊ธฐ- tableview editing

  • debug

    // DEBUG
    Path1: /Users/jeonmijin/Library/Developer/CoreSimulator/Devices/DE1DE4FA-2208-4062-8C55-0673E3019F6C/data/Containers/Data/Application/34D0E5CC-B577-49E2-913A-82DDBA91CB59/tmp/CFNetworkDownload_nEtNzv.tmp
    Path2: /Users/jeonmijin/Library/Developer/CoreSimulator/Devices/DE1DE4FA-2208-4062-8C55-0673E3019F6C/data/Containers/Data/Application/34D0E5CC-B577-49E2-913A-82DDBA91CB59/Library/Caches/fdb0d5fcfb86e332505785225a6d9ade.jpg
    ์•„์ดํ…œ์ด๋ฏธ์ง€: https://cdn.bmf.kr/_data/product/HBBCC/fdb0d5fcfb86e332505785225a6d9ade.jpg
    

2018.07.25

downloadTask() ๋™์ž‘๋ฐฉ์‹

  • func downloadTask(with url: URL, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask
  • ์ด๋ ‡๊ฒŒ URLSession์—์„œ downloadTask๋งŒ๋“ค๋•Œ์š”, ์ผ๋ฐ˜ dataTask๋ž‘์€ ๋‹ค๋ฅด๊ฒŒ ์ปดํ”Œ๋ฆฌ์…˜ ํ•ธ๋“ค๋Ÿฌ์—์„œ data๊ฐ€ ์•„๋‹Œ location(URL)์„ ๋ฐ›๋˜๋ฐ, ์ €๋Š” ์ด task๋ฅผ resume()ํ–ˆ์„๋•Œ ์–ด๋–ป๊ฒŒ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋œ๊ฑด์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹น. ์ปดํ”Œ๋ฆฌ์…˜ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ฑฐ์น˜๋ฉด์„œ ์ด๋ฏธ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค์šด๋กœ๋“œ๋Š” location์— ๋œ๊ฑฐ๊ณ  location์ด ๋‹ค์šด๋กœ๋“œ ๋ฐ›์€ ์ž„์‹œํŒŒ์ผ์ด ์ €์žฅ๋œ ๊ฒฝ๋กœ์ธ๊ฑด๊ฐ€์š”? ๊ทธ๋Ÿฌ๋ฉด data๋กœ ๋ฐ”๋กœ ์ ‘๊ทผํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ location์ธ ์ž„์‹œ๊ฒฝ๋กœ๋กœ ๊ฐ€์„œ ๋‹ค์šด๋ฐ›์•„์ง„ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๊ฐ€์ ธ์™€์•ผํ•˜๋Š”๊ฑด๊ฐ€์š”?
  • Answer: ํŒŒ์ผ๋กœ ์ €์žฅ๋˜๊ณ  ์œ„์น˜๊ฐ€ ๋„˜์–ด์˜ค๋ฉฐ, ํ•ด๋‹น ์œ„์น˜์—๋Š” ์ด๋ฏธ url๋กœ๋ถ€ํ„ฐ ๋‹ค์šด๋ฐ›์•„์ง„ ์ด๋ฏธ์ง€ํŒŒ์ผ์ด ์กด์žฌํ•จ.
  • ์ž„์‹œ๊ฒฝ๋กœ์—๋Š” /Users/jeonmijin/Library/Developer/CoreSimulator/Devices/DE1DE4FA-2208-4062-8C55-0673E3019F6C/data/Containers/Data/Application/34D0E5CC-B577-49E2-913A-82DDBA91CB59/tmp/CFNetworkDownload_nEtNzv.tmp ์ด๋Ÿฐ์‹์œผ๋กœ ์ž„์‹œ ํŒŒ์ผ์ด ์ €์žฅ๋จ
// ์ž„์‹œ ํŒŒ์ผ location์œผ๋กœ ๋ฐ”๋กœ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์™€๋ด„
func test() {
       let url = URL(string: "https://cdn.bmf.kr/_data/product/HCCFE/757878b14ee5a8d5af905c154fc38f01.jpg")!

       URLSession.shared.downloadTask(with: url) { (location, response, error) in
           if let error = error {
               print("\(error)")
           }
           if let location = location {
               let img = UIImage(contentsOfFile: location.path)
               DispatchQueue.main.sync {
                   self.view.addSubview(UIImageView(image: img))
               }
           }
       }.resume()
   }

์บ์‹œํด๋”๋กœ ํŒŒ์ผ ์˜ฎ๊ธฐ๊ธฐ

  1. downloadTask์˜ ์ปดํ”Œ๋ฆฌ์…˜ํ•ธ๋“ค๋Ÿฌ์—์„œ location - tmp์ €์žฅ
  2. tmp path์—์„œ cache path๋กœ move
  3. cache์˜ jpgํŒŒ์ผ ์ €์žฅํ•œ ๊ฒฝ๋กœ๋ฅผ UIImage(contentsOf:)๋กœ ๋„˜๊น€
  4. cache ํŒŒ์ผ ์•ˆ์— ์žˆ๋Š”๊ฑฐ ์•ˆ์ง€์šฐ๋ฉด moveError๋‚˜๋Š”๋‹ท!?
do {
        try fileManager.moveItem(at: tmpLocation, to: imageSavingPath)
            let imageData = try? Data(contentsOf: imageSavingPath)
            handler(imageData)
    } catch {
        if FileManager().fileExists(atPath: imageSavingPath.path) {
            let imageData = try? Data(contentsOf: imageSavingPath)
            handler(imageData)
        } else { print("MOVE Error!") }
class ImageSetter {

    class func download(with url: String, handler: @escaping((Data?) -> Void)) {
        let fileManager = FileManager.default
        let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let imageSavingPath = cacheURL.appendingPathComponent(URL(string: url)!.lastPathComponent)

            URLSession.shared.downloadTask(with: URL(string: url)!) { (tmpLocation, response, error) in
                if let error = error {
                    print("Image download Error log: \(error)\n")
                }
                if let response = response as? HTTPURLResponse, response.statusCode == 200, let tmpLocation = tmpLocation {
                    do {
                        try fileManager.moveItem(at: tmpLocation, to: imageSavingPath)
                        let sss = try? fileManager.contentsOfDirectory(atPath: imageSavingPath.path)
                        print(sss) // nil - ํŒŒ์ผ๋ช…๊นŒ์ง€์˜ ๊ฒฝ๋กœ๋ผ nil
                        let fff = try? fileManager.contentsOfDirectory(atPath: cacheURL.path)
                        print(fff)
                        let ddd = fileManager.contents(atPath: imageSavingPath.path)
                        print(ddd) // ์ €์žฅ๋œ ์ด๋ฏธ์ง€์— data์ž์ฒด ํ”„๋ฆฐํŠธ๋จ
                        let imageData = try? Data(contentsOf: imageSavingPath)
                        handler(imageData)
                    } catch {
                        if FileManager().fileExists(atPath: imageSavingPath.path) {
                            let imageData = try? Data(contentsOf: imageSavingPath)
                            handler(imageData)
                        } else { print("MOVE Error!") }
                    }
                }
    }.resume()

}
}

์ด๋ฏธ์ง€ ์„ค์ •

    private func setItemImage(imageURL: String) {
        ImageSetter.download(with: imageURL, handler: { imageData in
            DispatchQueue.main.sync { [weak self] in
//                self.itemImage.image = UIImage(contentsOfFile: imagePath)
                guard let data = imageData else { return }
                self?.itemImage.image = UIImage(data: data)
            }
        })
    }
  1. self.imageView?.image = UIImage(contentsOfFile: imagePath) > ๋ฐ”๋กœ ์•ˆ๋˜๊ณ  ์…€์„ ๋‹ค์‹œ ์žฌ์‚ฌ์šฉํ• ๋•Œ ๊ทธ๋ฆผ ๋œธ
  2. self.itemImage.image = UIImage(contentsOfFile: imagePath)
    • 1,2์˜ ์ฐจ์ด: ์…€์ด ์ฒ˜์Œ ์žฌ์‚ฌ์šฉ๋ ๋•Œ UIImageView ํ”„๋กœํผํ‹ฐ ์ž์ฒด๋Š” ์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ ๋งŒ๋“ค์–ด์กŒ๊ธฐ๋•Œ๋ฌธ์—, ์Šคํ† ๋ฆฌ๋ณด๋“œ๋Œ€๋กœ ์ฒ˜์Œ์— ์„ธํŒ…๋˜๊ณ  ๋ฐ์ดํƒ€์†Œ์Šค๊ฐ์ฒด์— ์˜ํ•ด์„œ ์†์„ฑ์ด ํ• ๋‹น๋ ๋•Œ๊ฐ€ ๋˜์„œ์•ผ ์ด๋ฏธ์ง€๊ฐ€ ์„ธํŒ…๋จ(dequeํ• ๋•Œ)

2018.07.26

  • ๋ฐ๋“œ๋ฝ ์ƒํ™ฉ ๋ฐœ๊ฒฌ!
    • main queue์—์„œ ๋˜ main.sync๋กœ ๋Œ์•„๊ฐ€๋Š” ์ฝ”๋“œ๋ธ”๋Ÿญ์„ ํ˜ธ์ถœ
    • setItemImage() ํ•จ์ˆ˜๋Š” ImageSetter์˜ download()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉฐ, ์ธ์ž๋กœ url๊ณผ ํ•จ๊ป˜ ์ปดํ”Œ๋ฆฌ์…˜ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋„˜๊ธด๋‹ค. (main queue์—์„œ ์‹คํ–‰)
    • ํ•ด๋‹น ์ปดํ”Œ๋ฆฌ์…˜ ํ•ธ๋“ค๋Ÿฌ์˜ ์ฝ”๋“œ ๋ธ”๋Ÿญ์„ ํŽธ์˜์ƒ A๋ธ”๋Ÿญ์ด๋ผ๊ณ  ์นญํ•œ๋‹ค.
    • ์ด๋•Œ A๋ธ”๋Ÿญ์˜ ๊ด€๋ จ worker๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
      • Assignor : setItemImage(), Assignee : download()
    • download()๋Š” ํ•จ์ˆ˜๋ฅผ ๋น ์ ธ๋‚˜์˜ค๋Š” ์‹œ์ ์— A๋ธ”๋Ÿญ์„ ์‹คํ–‰ํ•œ๋‹ค.
    • ์ด๋•Œ์˜ A๋ธ”๋Ÿญ์˜ worker๋Š”
      • Assignor : download(), Assignee : setItemImage()
    • ๋‘ worker๊ฐ€ ๋ชจ๋‘ main queue์—์„œ sync๋กœ ๋™์ž‘ํ•˜๊ธฐ๋•Œ๋ฌธ์—, ์„œ๋กœ๊ฐ€ ๊ต์ฐจํ•˜์—ฌ ์„œ๋กœ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ ์žˆ๊ณ  ์„œ๋กœ ์ผ์ด ๋๋‚˜๊ธฐ๋งŒ์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ฐ๋“œ๋ฝ ์ƒํ™ฉ์ด ๋ฐœ์ƒ.
    • handler์˜ ์ฝ”๋“œ๋ฅผ async๋กœ ๋ณ€๊ฒฝํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฌธ์ œ ์ฝ”๋“œ
    // TableViewCell.swift
    
    private func setItemImage(imageURL: String) {
      // 1. ImageSetter์˜ download()ํ˜ธ์ถœ
      ImageSetter.download(with: imageURL, handler: { imageData in
        // 3. download()ํ•จ์ˆ˜๊ฐ€ ์ข…๋ฃŒ๋˜๋ฉด์„œ ํ•ธ๋“ค๋Ÿฌ๊ฐ€์‹คํ–‰๋จ. ๊ฐ™์€ main queue๋ผ์„œ ๋ฌธ์ œ๋ฐœ์ƒ  
        DispatchQueue.main.sync { [weak self] in
          guard let data = imageData else { return }
          self?.itemImage.image = UIImage(data: data)
        }
      })
    }
    
// TableViewCell์—์„œ ImageSetter์˜ download๊ฐ€ ํ˜ธ์ถœ

class func download(with url: String, handler: @escaping((Data) -> Void)) {
    let cacheURL = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
    let imageSavingPath = cacheURL.appendingPathComponent(URL(string: url)!.lastPathComponent)

    // 2. ์•„๋ž˜ ์ค„ ์ฝ”๋“œ์˜ existFile์ด ์ฒดํฌ๋˜๊ณ  handler๊ฐ€ ์‹คํ–‰๋จ
    if let imageData = existFile(at: imageSavingPath) {
        handler(imageData)
    } else {
        URLSession.shared.downloadTask(with: URL(string: url)!) { (tmpLocation, response, error) in
          // do something...
        }.resume()
    }
}

2018.07.27

  • ์Šคํ† ์–ด์•ฑ 7๋‹จ๊ณ„ ์ง„ํ–‰
    • UINavigationController ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๊ณต๋ถ€ (์ƒ์„ธํ™”๋ฉด์—์„œ ์ผ์–ด๋‚˜๋Š” ์•ก์…˜์— ๋”ฐ๋ผ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๋ฉ”์†Œ๋“œ ์—ฐ๊ฒฐ)
    • ์Šคํฌ๋กค๋ทฐ ๊ณต๋ถ€
    • ์ƒํ’ˆ ์ƒ์„ธ ์ •๋ณด ๋ฐ›์•„์˜ค๋Š” ๋ชจ๋ธ ๊ฐ์ฒด ์„ค๊ณ„
    • HTTP & Network Basic ์ฑ… ์ฝ์–ด๋ณด๊ธฐ

2018.07.28

  • ๋ถ€์ŠคํŠธ์ฝ”์Šค ๋‚ ์”จ์•ฑ ํ™”๋ฉด ๊ตฌํ˜„ ๋ฏธ์…˜
  • NSDataAsset: ์—์…‹ ์นดํƒˆ๋กœ๊ทธ์—์„œ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ฌ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐํƒ€์ž…

Comments