คำสำคัญของการ์ด Swift


197

Swift 2 แนะนำguardคำสำคัญซึ่งสามารถใช้เพื่อให้แน่ใจว่าข้อมูลต่าง ๆ ได้รับการกำหนดค่าพร้อมใช้งาน ตัวอย่างที่ฉันเห็นในเว็บไซต์นี้แสดงให้เห็นถึงฟังก์ชั่น submitTapped:

func submitTapped() {
    guard username.text.characters.count > 0 else {
        return
    }

    print("All good")
}

ฉันสงสัยว่าการใช้guardแตกต่างจากการทำแบบเก่าโดยใช้ifเงื่อนไขหรือไม่ มันให้ประโยชน์ซึ่งคุณไม่สามารถทำได้ด้วยการใช้เช็คแบบธรรมดาหรือไม่?



โปรดอ้างอิงลิงค์ต่อไปนี้medium.com/@pvpriya7/swift-guard-18e59c50c624
Priyanka V

คำตอบ:


368

การอ่านบทความนี้ฉันสังเกตเห็นประโยชน์ที่ยอดเยี่ยมในการใช้Guard

ที่นี่คุณสามารถเปรียบเทียบการใช้ยามกับตัวอย่าง:

นี่คือส่วนที่ไม่มีการป้องกัน:

func fooBinding(x: Int?) {
    if let x = x where x > 0 {
        // Do stuff with x
        x.description
    }

    // Value requirements not met, do something
}
  1. ที่นี่คุณใส่รหัสที่คุณต้องการภายในเงื่อนไขทั้งหมด

    คุณอาจไม่เห็นปัญหาทันที แต่คุณสามารถจินตนาการได้ว่ามันจะเกิดความสับสนได้อย่างไรหากเกิดการซ้อนกันด้วยเงื่อนไขมากมายที่จำเป็นต้องได้รับก่อนที่จะเรียกใช้คำสั่งของคุณ

วิธีทำความสะอาดสิ่งนี้คือทำเช็คแต่ละครั้งก่อนออกจากโปรแกรมหากยังไม่เสร็จ สิ่งนี้ช่วยให้เข้าใจได้ง่ายว่าเงื่อนไขใดที่จะทำให้ฟังก์ชันนี้ออก

แต่ตอนนี้เราสามารถใช้ Guard และเราเห็นว่าเป็นไปได้ในการแก้ไขปัญหา:

func fooGuard(x: Int?) {
    guard let x = x where x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
    x.description
}
  1. ตรวจสอบเงื่อนไขที่คุณต้องการไม่ใช่เงื่อนไขที่คุณไม่ต้องการ นี้อีกครั้งคล้ายกับการยืนยัน หากไม่ตรงตามเงื่อนไขจะมีคำสั่งอื่นของ Guard ทำงานซึ่งแบ่งออกจากฟังก์ชัน
  2. หากเงื่อนไขผ่านไปตัวแปรที่เป็นทางเลือกที่นี่จะถูกนำมาห่อให้คุณโดยอัตโนมัติภายในขอบเขตที่คำสั่งป้องกันถูกเรียก - ในกรณีนี้ฟังก์ชัน fooGuard (_ :)
  3. คุณกำลังตรวจสอบกรณีที่ไม่ดีก่อนเวลาทำให้การทำงานของคุณอ่านง่ายขึ้นและง่ายต่อการดูแลรักษา

รูปแบบเดียวกันนี้ถือเป็นจริงสำหรับค่าที่ไม่จำเป็นเช่นกัน:

func fooNonOptionalGood(x: Int) {
    guard x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

func fooNonOptionalBad(x: Int) {
    if x <= 0 {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

หากคุณยังมีคำถามใด ๆ คุณสามารถอ่านบทความทั้งหมด: คำแถลง Swift Guard

ห่อ

และในที่สุดการอ่านและการทดสอบฉันพบว่าถ้าคุณใช้ Guard เพื่อแกะตัวเลือกใด ๆ

ค่าที่ไม่ได้แยกเหล่านั้นจะอยู่รอบ ๆ เพื่อให้คุณใช้ในส่วนที่เหลือของบล็อกรหัสของคุณ

.

guard let unwrappedName = userName else {
    return
}

print("Your username is \(unwrappedName)")

ที่นี่ค่า unrapped จะสามารถใช้ได้เฉพาะในถ้าบล็อก

if let unwrappedName = userName {
    print("Your username is \(unwrappedName)")
} else {
    return
}

// this won't work – unwrappedName doesn't exist here!
print("Your username is \(unwrappedName)")

3
เฮ้ @Eric คุณทำโพสต์ที่ยอดเยี่ยม! ขอบคุณที่ทำให้มันเข้าใจง่าย!
Jorge Casariego

1
ฉันใช้การ์ดเฝ้าระวังในการแกะ NSError แต่เมื่อฉันพยายามที่จะใช้มันภายในขอบเขตของการป้องกัน (สำหรับการส่งข้อผิดพลาดไปยังการเรียกกลับบางอย่าง) มันบอกว่า "ตัวแปรที่ประกาศในสภาพการป้องกันไม่สามารถใช้งานได้ในร่างกายของมัน" มันสมเหตุสมผลไหม? ขอบคุณ
GeRyCh

6
@GeRyCh การคลายคำสั่ง Guard ทำให้ตัวแปรนั้นพร้อมใช้งานหลังจากคำสั่ง Guard ไม่ใช่ภายใน ใช้เวลาสักครู่เพื่อทำความคุ้นเคยกับสิ่งนี้
DonnaLea

2
นี่เป็นอีกบทความที่ยอดเยี่ยมเกี่ยวกับการใช้ Guard เพื่อแกะตัวเลือกใหม่ทั้งหมด ผลรวมมันขึ้นอย่างสวยงาม
Doches

let x = x where x > 0หมายความว่าคุณได้เชื่อมโยงเงื่อนไขอื่นเข้ากับการรวมตัวเลือกของคุณหรือไม่? ฉันหมายความว่ามันเป็นความแตกต่างกันเล็กน้อยจากif let constantName = someOptional { statements }
น้ำผึ้ง

36

ซึ่งแตกต่างจากif, guardสร้างตัวแปรที่สามารถเข้าถึงได้จากนอกบล็อกของตน มันจะมีประโยชน์ในการแกะจำนวนมากOptionals


24

guardมีจริงๆสองประโยชน์ใหญ่ที่จะมี หนึ่งคือการหลีกเลี่ยงปิรามิดแห่งการลงโทษตามที่คนอื่นพูดถึง - มีif letข้อความที่น่ารำคาญมากมายที่ซ้อนกันอยู่ในกันและกันและเคลื่อนไปทางขวา

ประโยชน์อื่น ๆ มักจะเป็นตรรกะที่คุณต้องการใช้คือ " if not let" มากกว่า " if let { } else"

นี่คือตัวอย่าง: สมมติว่าคุณต้องการนำไปใช้accumulate- การข้ามระหว่างmapและreduceตำแหน่งที่จะช่วยให้คุณลดจำนวนการเรียกใช้ นี่คือด้วยguard:

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {
        // if there are no elements, I just want to bail out and
        // return an empty array
        guard var running = self.first else { return [] }

        // running will now be an unwrapped non-optional
        var result = [running]

        // dropFirst is safe because the collection
        // must have at least one element at this point
        for x in dropFirst(self) {
            running = combine(running, x)
            result.append(running)
        }
        return result
    }

}


let a = [1,2,3].accumulate(+)  // [1,3,6]
let b = [Int]().accumulate(+)  // []

คุณจะเขียนมันอย่างไรโดยไม่มีการป้องกัน แต่ยังคงใช้firstที่ส่งกลับทางเลือก? บางสิ่งเช่นนี้

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {

        if var running = self.first  {
            var result = [running]

            for x in dropFirst(self) {
                running = combine(running, x)
                result.append(running)
            }
            return result
        }
        else {
            return []
        }
    }

}

การทำรังแบบพิเศษนั้นน่ารำคาญ แต่ก็ไม่ใช่ว่าจะมีเหตุผลifและelseอยู่ห่างไกลกัน มันอ่านได้มากขึ้นที่จะมีทางออกก่อนหน้าสำหรับกรณีที่ว่างเปล่าจากนั้นดำเนินการต่อกับส่วนที่เหลือของฟังก์ชั่นราวกับว่ามันเป็นไปไม่ได้


19

เมื่อตรงตามเงื่อนไขการใช้guardมันจะเปิดเผยตัวแปรที่ประกาศภายในguardบล็อกไปยังส่วนที่เหลือของบล็อกโค้ดซึ่งจะนำพวกเขาเข้าสู่ขอบเขต ซึ่งตามที่ระบุไว้ก่อนหน้านี้จะมาพร้อมกับif letงบที่ซ้อนกันอย่างแน่นอน

โปรดทราบว่ายามต้องส่งคืนหรือโยนในคำสั่งอื่น

การแยก JSON ด้วย Guard

ด้านล่างนี้เป็นตัวอย่างของวิธีที่อาจแยกวิเคราะห์วัตถุ JSON โดยใช้การป้องกันแทนที่จะปล่อยให้ นี่คือข้อความที่ตัดตอนมาจากรายการบล็อกที่มีไฟล์สนามเด็กเล่นซึ่งคุณสามารถค้นหาได้ที่นี่:

วิธีใช้ Guard ใน Swift 2 เพื่อวิเคราะห์ JSON

func parseJSONWithGuard(data : [String : AnyObject]) throws -> Developer {

    guard let firstname = data["First"] as? String  else {
        return Developer() // we could return a nil Developer()
    }

    guard let lastname = data["Last"] as? String else {
        throw ParseError.BadName // or we could throw a custom exception and handle the error
    }

    guard let website = data["WebSite"] as? String else {
        throw ParseError.BadName
    }

    guard let iosDev = data["iosDeveloper"] as? Bool else {
        throw ParseError.BadName
    }



    return Developer(first: firstname, last: lastname, site: website, ios: iosDev)

}

ดาวน์โหลด Playground: Guard playground

ข้อมูลเพิ่มเติม:

นี่คือข้อความที่ตัดตอนมาจากคู่มือภาษาโปรแกรม Swift:

หากตรงตามเงื่อนไขของคำสั่ง Guard การเรียกใช้โค้ดจะดำเนินต่อไปหลังจากการปิดคำสั่ง Guard ตัวแปรหรือค่าคงที่ใด ๆ ที่ได้รับการกำหนดค่าโดยใช้ตัวเลือกการรวมเป็นส่วนหนึ่งของเงื่อนไขจะพร้อมใช้งานสำหรับส่วนที่เหลือของบล็อกรหัสที่คำสั่ง Guard ปรากฏขึ้น

หากไม่เป็นไปตามเงื่อนไขดังกล่าวรหัสภายในสาขาอื่นจะถูกดำเนินการ สาขานั้นจะต้องถ่ายโอนการควบคุมเพื่อออกจากบล็อครหัสที่คำสั่ง Guard ปรากฏขึ้นซึ่งสามารถทำได้โดยใช้คำสั่งการโอนการควบคุมเช่น return, break หรือ Continue หรือสามารถเรียกฟังก์ชันหรือวิธีที่ไม่ส่งคืนเช่น เป็น fatalError ()


7

ประโยชน์อย่างหนึ่งคือกำจัดif letข้อความที่ซ้อนกันมากมาย ดูวิดีโอ "มีอะไรใหม่ในสวิฟต์" WWDC รอบ 15:30 นส่วนที่ชื่อว่า "Pyramid of Doom"


6

ควรใช้ยามเมื่อใด

หากคุณมีตัวควบคุมมุมมองที่มีองค์ประกอบ UITextField ไม่กี่ตัวหรือการป้อนข้อมูลของผู้ใช้บางประเภทคุณจะสังเกตเห็นได้ทันทีว่าคุณต้องแกะ textField.text ที่เป็นตัวเลือกเพื่อไปที่ข้อความภายใน (ถ้ามี!) isEmpty จะไม่ทำคุณให้ดีที่นี่โดยไม่ต้องป้อนข้อมูลใด ๆ ที่ฟิลด์ข้อความจะกลับศูนย์

ดังนั้นคุณจึงมีบางส่วนที่คุณไม่ได้แกะและในที่สุดก็ส่งผ่านไปยังฟังก์ชั่นที่โพสต์ไว้ที่จุดปลายเซิร์ฟเวอร์ เราไม่ต้องการให้รหัสเซิร์ฟเวอร์ต้องจัดการกับค่าศูนย์หรือส่งค่าที่ไม่ถูกต้องไปยังเซิร์ฟเวอร์โดยไม่ตั้งใจดังนั้นเราจะทำการแกะค่าอินพุตที่มีการป้องกันก่อน

func submit() {
    guard let name = nameField.text else {
        show("No name to submit")
        return
    }

    guard let address = addressField.text else {
        show("No address to submit")
        return
    }

    guard let phone = phoneField.text else {
        show("No phone to submit")
        return
    }

    sendToServer(name, address: address, phone: phone)
}

func sendToServer(name: String, address: String, phone: String) {
  ...
}

คุณจะสังเกตเห็นว่าฟังก์ชั่นการสื่อสารเซิร์ฟเวอร์ของเราใช้ค่าสตริงที่ไม่จำเป็นเป็นพารามิเตอร์ดังนั้นเจ้าหน้าที่รักษาความปลอดภัยจะทำการเปิดเครื่องล่วงหน้า การคลายออกเป็นสิ่งที่ไม่คุ้นเคยเพราะเราคุ้นเคยกับการคลายถ้าปล่อยให้ค่าที่ไม่ได้ใช้สำหรับการใช้งานภายในบล็อก นี่คือคำแถลงยามที่มีบล็อกที่เกี่ยวข้อง แต่จริงๆแล้วมันเป็นบล็อกอื่น - นั่นคือสิ่งที่คุณทำถ้าการแกะที่ล้มเหลว - ค่าจะถูกแกะตรงเข้าไปในบริบทเดียวกันกับคำสั่งนั้น

// การแยกข้อกังวล

ไม่มียาม

โดยไม่ต้องใช้ยามที่เราต้องการจบลงด้วยกองใหญ่ของรหัสที่คล้ายปิรามิดของการลงโทษ สิ่งนี้ไม่ได้ปรับขนาดให้เหมาะสมสำหรับการเพิ่มเขตข้อมูลใหม่ในแบบฟอร์มของเราหรือทำให้เป็นรหัสที่อ่านง่าย การเยื้องอาจเป็นเรื่องยากที่จะปฏิบัติตามโดยเฉพาะอย่างยิ่งกับข้อความอื่น ๆ จำนวนมากในแต่ละส้อม

func nonguardSubmit() {
    if let name = nameField.text {
        if let address = addressField.text {
            if let phone = phoneField.text {
                sendToServer(name, address: address, phone: phone)
            } else {
                show("no phone to submit")
            }
        } else {
            show("no address to submit")
        }
    } else {
        show("no name to submit")
    }
}

ใช่เราสามารถรวมสิ่งเหล่านี้ได้ถ้าปล่อยให้ statement เป็นคำสั่งเดียวคั่นด้วยเครื่องหมายจุลภาค แต่เราจะสูญเสียความสามารถในการพิจารณาว่าคำสั่งใดล้มเหลวและนำเสนอข้อความถึงผู้ใช้

https://thatthinginswift.com/guard-statement-swift/


5

ด้วยการใช้ยามความตั้งใจของเราชัดเจน เราไม่ต้องการรันโค้ดที่เหลือหากเงื่อนไขนั้นไม่เป็นที่พอใจ ที่นี่เราสามารถขยายเครือข่ายได้เช่นกันโปรดดูรหัสด้านล่าง:

guard let value1 = number1, let value2 = number2 else { return }
 // do stuff here

5

งบยามจะทำ มันเป็นคู่ที่แตกต่างกัน

1) มันช่วยให้ฉันลดระดับที่ซ้อนกันถ้าคำสั่ง
2) มันเป็นการเพิ่มขอบเขตของฉันซึ่งตัวแปรของฉันสามารถเข้าถึงได้

ถ้างบ

func doTatal(num1 : Int?, num2: Int?) {
  // nested if statement
    if let fistNum = num1 where num1 > 0 {
        if let lastNum = num2 where num2 < 50 {

          let total = fistNum + lastNum
        }
    }
 // don't allow me to access out of the scope 
 //total = fistNum + lastNum 
}

คำแถลงยาม

func doTatal(num1 : Int?, num2: Int?) {
   //reduce  nested if statement and check positive way not negative way 
    guard let fistNum = num1 where num1 > 0 else{
      return
    }
    guard  let lastNum = num2 where num2 < 50 else {
     return
    }
    // increase my scope which my variable accessible
    let total = fistNum + lastNum

}

3

จากเอกสารของ Apple:

คำแถลงยาม

คำสั่ง Guard ใช้เพื่อถ่ายโอนการควบคุมโปรแกรมออกจากขอบเขตหากไม่พบเงื่อนไขอย่างน้อยหนึ่งข้อ

Synatx:

guard condition else {
    statements
}

ความได้เปรียบ:

1. โดยการใช้guardคำสั่งเราสามารถกำจัดเงื่อนไขที่ซ้อนกันอย่างลึกล้ำซึ่งมีวัตถุประสงค์เพียงอย่างเดียวคือการตรวจสอบชุดของข้อกำหนด

2. มันถูกออกแบบมาโดยเฉพาะสำหรับออกจากวิธีการหรือฟังก์ชั่นก่อน

ถ้าคุณใช้ถ้าให้ด้านล่างเป็นรหัสว่ามันมีลักษณะอย่างไร

  let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

        if error == nil {
            if let  statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 {
                if let data = data {

                    //Process Data Here.
                    print("Data: \(data)")

                } else {
                    print("No data was returned by the request!")
                }
            } else {
                print("Your request returned a status code other than 2XX!")
            }
        } else {
            print("Error Info: \(error.debugDescription)")
        }
    }
    task.resume()

การใช้ตัวป้องกันคุณสามารถถ่ายโอนการควบคุมนอกขอบเขตได้หากไม่พบเงื่อนไขอย่างน้อยหนึ่งข้อ

let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            /* GUARD: was there an error? */
            guard (error == nil) else {
                print("There was an error with your request: \(error)")
                return
            }

            /* GUARD: Did we get a successful 2XX response? */
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
                print("Your request returned a status code other than 2XX!")
                return
            }

            /* GUARD: was there any data returned? */
            guard let data = data else {
                print("No data was returned by the request!")
                return
            }

            //Process Data Here.
            print("Data: \(data)")
}
task.resume()

อ้างอิง:

1. Swift 2: Exit Early With Guard 2. Udacity 3. Guard Guard


แต่คุณสามารถทำหลังถ้ามีงบ? if condition { return }โกรธ?
Oliver Dixon

2

เช่นเดียวกับคำสั่ง if, ยามรันคำสั่งตามค่าบูลีนของการแสดงออก ซึ่งแตกต่างจากคำสั่ง if, งบยามทำงานเฉพาะในกรณีที่ไม่เป็นไปตามเงื่อนไข คุณสามารถนึกถึงยามมากกว่า Assert แต่แทนที่จะกระแทกคุณสามารถออกไปได้อย่างสง่างาม

อ้างถึง: http://ericcerney.com/swift-guard-statement/


1

จริงๆแล้วมันจะทำให้ลำดับของการค้นหาและตัวเลือกที่หลากหลายกระชับและชัดเจนยิ่งขึ้นและลดจำนวนการซ้อนกัน ดูริกา Sadun โพสต์เกี่ยวกับการเปลี่ยนอิฟ .... อาจถูกพาไปตัวอย่างต่อไปนี้:

    let filteredLinks = locationsLinkedToList.filter({$0.actionVerb == movementCommand})
    guard let foundLink = filteredLinks.first else {return ("<Person> cannot go in that direction.", nil, nil)}
    guard filteredLinks.count == 1 else {return ("<Person> cannot decide which route to take.", nil, nil)}
    guard let nextLocation = foundLink.toLocation else {return ("<Person> cannot go in that direction.", nil, nil)}

ดูว่ามันเกาะติด


1

ใส่เพียงแค่มันมีวิธีการตรวจสอบเขตข้อมูลก่อนที่จะดำเนินการ นี่เป็นรูปแบบการเขียนโปรแกรมที่ดีเนื่องจากช่วยเพิ่มความสามารถในการอ่าน ในภาษาอื่น ๆ อาจมีลักษณะเช่นนี้:

func doSomething() {
    if something == nil {
        // return, break, throw error, etc.
    }
    ...
}

แต่เนื่องจาก Swift ให้ทางเลือกแก่คุณเราจึงไม่สามารถตรวจสอบว่าเป็นศูนย์และกำหนดค่าให้กับตัวแปรหรือไม่ ในทางตรงกันข้ามif letตรวจสอบว่าไม่ใช่ศูนย์และกำหนดตัวแปรให้เก็บค่าที่แท้จริง นี่คือที่guardเข้ามาเล่น มันทำให้คุณมีวิธีรัดกุมในการออกจากการใช้ตัวเลือก


1

ที่มา: Guard in Swift

เรามาดูตัวอย่างเพื่อทำความเข้าใจอย่างชัดเจน

ตัวอย่างที่ 1:

func validate() {         
    guard 3>2 else {             
    print ("False")             
    return         
    }         
    print ("True") //True     
} 
validate()

ในตัวอย่างข้างต้นเราจะเห็นว่า 3 มีค่ามากกว่า 2 และคำสั่งในส่วนคำสั่งอื่นถูกข้ามและพิมพ์ True

ตัวอย่างที่ 2:

func validate() {         
    guard 1>2 else {             
    print ("False")            //False 
    return         
    }         
    print ("True")      
} 
validate()

ในตัวอย่างข้างต้นเราจะเห็นว่า 1 มีขนาดเล็กกว่า 2 และมีการดำเนินการคำสั่งภายในส่วนคำสั่งอื่นและพิมพ์ตามด้วยการคืนค่าเท็จ

Example 3: gaurd let, unwrapping optionals through guard let

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          // Condition is false            return         
     }         
     print("Condition is met\(name)")     
} 
getName(args: "")

ในตัวอย่างข้างต้นเราใช้ Guard ให้ยกเลิกการเลือกออปชั่น ในฟังก์ชั่น getName เราได้กำหนดตัวแปรประเภทสตริง myName ซึ่งเป็นตัวเลือก จากนั้นเราใช้ Guard let เพื่อตรวจสอบว่าตัวแปร myName เป็นศูนย์หรือไม่หากไม่ได้กำหนดให้เป็นชื่อและตรวจสอบอีกครั้งชื่อจะไม่ว่างเปล่า หากทั้งสองเงื่อนไขมีคุณสมบัติเช่นจริงบล็อกอื่นจะถูกข้ามไปและพิมพ์“ เงื่อนไขเป็นไปตามชื่อ”

โดยทั่วไปเรากำลังตรวจสอบสองสิ่งที่นี่คั่นด้วยเครื่องหมายจุลภาคอันดับแรกที่ไม่เปิดใช้งานและเป็นทางเลือกและตรวจสอบว่าเป็นไปตามเงื่อนไขหรือไม่

ที่นี่เรากำลังส่งผ่านอะไรไปยังฟังก์ชั่นเช่นสตริงที่ว่างเปล่าและดังนั้นสภาพที่เป็นเท็จคือการพิมพ์

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          
     return         
     }        
     print("Condition is met \(name)") // Condition is met Hello    
} getName(args: "Hello")

ที่นี่เรากำลังส่ง“ Hello” ไปยังฟังก์ชันและคุณจะเห็นผลลัพธ์ที่พิมพ์ออกมา“ ตรงกับเงื่อนไข Hello”

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.