วิธีการส่งชนิดคลาสเป็นพารามิเตอร์ฟังก์ชัน


146

ฉันมีฟังก์ชั่นทั่วไปที่เรียกใช้บริการเว็บและทำให้การตอบสนอง JSON กลับเป็นวัตถุ

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

สิ่งที่ฉันพยายามทำให้สำเร็จคือโค้ด Java ที่เทียบเท่ากัน

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • ลายเซ็นวิธีการของฉันเป็นสิ่งที่ฉันพยายามที่จะทำให้ถูกต้องหรือไม่?
  • โดยเฉพาะอย่างยิ่งการระบุAnyClassว่าเป็นพารามิเตอร์ประเภทสิ่งที่ถูกต้องที่จะทำ?
  • เมื่อเรียกเมธอดฉันกำลังส่งผ่านMyObject.selfค่า returnClass แต่ได้รับข้อผิดพลาดในการคอมไพล์"ไม่สามารถแปลงประเภทของนิพจน์ '()' เป็นประเภท 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

แก้ไข:

ฉันพยายามใช้object_getClassตามที่ระบุโดย holex แต่ตอนนี้ฉันได้รับ:

ข้อผิดพลาด: "Type 'CityInfo.Type' ไม่สอดคล้องกับโปรโตคอล 'AnyObject'"

สิ่งที่ต้องทำเพื่อให้สอดคล้องกับโปรโตคอล?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}

ฉันไม่คิดว่า Swifts generics ทำงานได้เหมือน javas ดังนั้นผู้อยู่ใต้บังคับบัญชาจึงไม่สามารถฉลาดได้ id ละเว้น Class <T> สิ่งนี้และระบุประเภททั่วไปอย่างชัดเจนCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

คำตอบ:


144

คุณกำลังเข้าใกล้ในทางที่ผิด: ใน Swift ซึ่งแตกต่างจาก Objective-C คลาสมีชนิดเฉพาะและมีลำดับชั้นการสืบทอด (นั่นคือถ้าคลาสBสืบทอดมาจากAนั้นB.Typeก็สืบทอดมาด้วยA.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

นั่นเป็นเหตุผลที่คุณไม่ควรใช้AnyClassนอกจากว่าคุณต้องการอนุญาตให้มีคลาสใด ๆ ในกรณีนี้ประเภทที่ถูกต้องจะเป็นT.Typeเพราะมันแสดงการเชื่อมโยงระหว่างreturningClassพารามิเตอร์และพารามิเตอร์ของการปิด

ในความเป็นจริงการใช้มันแทนที่จะAnyClassอนุญาตให้คอมไพเลอร์อนุมานชนิดในการเรียกใช้เมธอดอย่างถูกต้อง:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

ขณะนี้มีปัญหาในการสร้างอินสแตนซ์ของTการส่งผ่านไปยังhandler: หากคุณลองและเรียกใช้รหัสในตอนนี้คอมไพเลอร์จะบ่นว่าTไม่สามารถสร้าง()ได้ และอย่างถูกต้องดังนั้น: Tจะต้องมีข้อ จำกัด อย่างชัดเจนว่าต้องใช้เครื่องมือเริ่มต้นที่เฉพาะเจาะจง

สิ่งนี้สามารถทำได้ด้วยโปรโตคอลเช่นหนึ่งต่อไปนี้:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

จากนั้นคุณจะต้องเปลี่ยนข้อ จำกัด ทั่วไปinvokeServiceจากการ<T><T: Initable>

ปลาย

หากคุณได้รับข้อผิดพลาดแปลก ๆ เช่น "ไม่สามารถแปลงชนิดของนิพจน์ '()' เป็นประเภท 'String'" ได้มักจะมีประโยชน์ในการย้ายอาร์กิวเมนต์ของการเรียกเมธอดเป็นตัวแปรของตัวเอง ช่วยลดรหัสที่ทำให้เกิดข้อผิดพลาดและเปิดเผยปัญหาการอนุมานประเภท:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

ขณะนี้มีความเป็นไปได้สองประการ: ข้อผิดพลาดย้ายไปยังหนึ่งในตัวแปร (ซึ่งหมายความว่ามีส่วนที่ผิด) หรือคุณได้รับข้อความที่เป็นความลับเช่น "ไม่สามารถแปลงประเภทของนิพจน์เป็น()ประเภท($T6) -> ($T6) -> $T5"

สาเหตุของข้อผิดพลาดหลังคือคอมไพเลอร์ไม่สามารถอนุมานประเภทของสิ่งที่คุณเขียน ในกรณีนี้ปัญหาคือTใช้เฉพาะในพารามิเตอร์ของการปิดและการปิดที่คุณผ่านไม่ได้ระบุประเภทเฉพาะดังนั้นผู้แปลไม่ทราบประเภทที่จะอนุมาน โดยการเปลี่ยนประเภทของreturningClassการรวมTคุณให้วิธีการรวบรวมพารามิเตอร์ทั่วไป


ขอบคุณสำหรับคำใบ้ T.Type - สิ่งที่ฉันต้องการในการผ่านประเภทคลาสเป็นอาร์กิวเมนต์
ระดับ

"เคล็ดลับ" ในตอนท้ายช่วยฉัน
Alex

แทนที่จะใช้ฟังก์ชั่น <T: เริ่มต้นได้> มันเป็นไปได้ที่จะส่งคืนคลาสเป็นแบบเริ่มต้นได้ประเภท
Devous

ในรหัสต้นฉบับที่จะให้ผลลัพธ์ที่แตกต่าง พารามิเตอร์ทั่วไปTจะใช้ในการแสดงความสัมพันธ์ระหว่างวัตถุและส่งผ่านไปยังreturningClass completionHandlerหากInitiable.Typeมีการใช้ความสัมพันธ์นี้จะหายไป
EliaCereda

และ? ยาสามัญไม่อนุญาตให้เขียนfunc somefunc<U>()
Gargo

34

คุณสามารถรับคลาสAnyObjectด้วยวิธีนี้:

สวิฟท์ 3.x

let myClass: AnyClass = type(of: self)

สวิฟท์ 2.x

let myClass: AnyClass = object_getClass(self)

และคุณสามารถผ่านมันเป็นพารามิเตอร์ได้ในภายหลังหากคุณต้องการ


1
คุณสามารถทำได้self.dynamicType
ใหม่

1
เมื่อใดก็ตามที่ฉันพยายามใช้ myClass มันก่อให้เกิดข้อผิดพลาดของ "การใช้ประเภทที่ไม่ได้ประกาศ" myClass "Swift 3, builds ล่าสุดของ Xcode และ iOS
Confused

@Confused ฉันไม่เห็นปัญหาเช่นนั้นคุณอาจต้องให้ข้อมูลเพิ่มเติมเกี่ยวกับบริบท
holex

ฉันทำปุ่ม ในรหัสของปุ่มนั้น (มันคือ SpriteKit ดังนั้นภายใน touchesBegan) ฉันต้องการให้ปุ่มนี้เรียกฟังก์ชัน Class ดังนั้นจึงต้องมีการอ้างอิงถึงคลาสนั้น ดังนั้นใน Button Class ฉันได้สร้างตัวแปรเพื่อเก็บการอ้างอิงไปยัง Class แต่ฉันไม่สามารถถือคลาสได้หรือประเภทที่เป็นคลาสนั้นไม่ว่าจะเป็นถ้อยคำ / คำศัพท์ที่ถูกต้องก็ตาม ฉันสามารถเอาไปเก็บการอ้างอิงกับอินสแตนซ์เท่านั้น โดยเฉพาะอย่างยิ่งฉันต้องการส่งการอ้างอิงถึงประเภทชั้นเรียนลงในปุ่ม แต่ฉันเพิ่งเริ่มสร้างการอ้างอิงภายในและไม่สามารถใช้งานได้
สับสน

มันเป็นความต่อเนื่องในวิธีการและความคิดผมใช้ที่นี่: stackoverflow.com/questions/41092440/... ฉันได้รับตรรกะบางอย่างในการทำงานกับโพรโทคอลแล้ว แต่ก็เกิดขึ้นอย่างรวดเร็วต่อความจริงที่ว่าฉันจะต้องหาประเภทและข้อมูลทั่วไปที่เกี่ยวข้องเพื่อให้ได้สิ่งที่ฉันคิดว่าฉันสามารถทำได้ด้วยกลไกที่ง่ายกว่า หรือเพียงแค่คัดลอกและวางรหัสจำนวนมากรอบ ๆ ซึ่งเป็นสิ่งที่ฉันทำตามปกติ;)
สับสน

7

ฉันมีกรณีใช้คล้ายกันใน swift5:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

ฉันพยายามที่จะทำอะไรบางอย่างที่คล้ายกัน แต่ฉันได้รับข้อผิดพลาดที่ callsite Static method … requires that 'MyDecodable.Type' conform to 'Decodable'นี้: คุณต้องการอัปเดตคำตอบของคุณเพื่อให้มีการโทรหาตัวอย่างloadItemหรือไม่
Barry Jones

ปรากฎว่านี่เป็นจุดที่ฉันต้องเรียนรู้ความแตกต่างระหว่าง.Typeและ.self(ประเภทและประเภทข้อมูล)
Barry Jones


0

สวิฟท์ 5

ไม่ใช่สถานการณ์เดียวกัน แต่ฉันมีปัญหาที่คล้ายกัน ในที่สุดสิ่งที่ช่วยฉันได้คือ:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

จากนั้นคุณสามารถเรียกมันได้ในตัวอย่างของคลาสที่กล่าวมาดังนี้:

myFunction(type(of: self))

หวังว่าสิ่งนี้จะช่วยให้ใครบางคนอยู่ในสถานการณ์เดียวกันของฉัน

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