วิธีการที่ไม่ใช่ '@ objc' ไม่เป็นไปตามข้อกำหนดทางเลือกของโปรโตคอล '@objc'


107

ภาพรวม:

  • ฉันมีโปรโตคอล P1 ซึ่งจัดเตรียมการใช้งานตามค่าเริ่มต้นของฟังก์ชันเสริม Objective-C อย่างใดอย่างหนึ่ง
  • เมื่อฉันจัดเตรียมการใช้งานฟังก์ชันเสริมเริ่มต้นจะมีคำเตือน

คำเตือนของคอมไพเลอร์:

Non-'@objc' method 'presentationController(_:viewControllerForAdaptivePresentationStyle:)' does not satisfy optional requirement of '@objc' protocol 'UIAdaptivePresentationControllerDelegate'

เวอร์ชัน:

  • สวิฟต์: 3
  • Xcode: 8 (เผยแพร่สู่สาธารณะ)

ความพยายามที่ทำ:

  • พยายามเพิ่ม@objcแต่ไม่ช่วย

คำถาม:

  • ฉันจะแก้ไขปัญหานี้ได้อย่างไร
  • มีงานทำไหม?

รหัส:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}


class A : UIViewController, P1 {

}

คุณมี Xcode เวอร์ชันใหม่ล่าสุดหรือไม่? ฉันไม่ได้รับข้อผิดพลาดใด ๆ เลยถ้าฉันลบ@objc
Qbyte

ฉันใช้ Xcode 8 (เวอร์ชันสาธารณะล่าสุด) ไม่มีข้อผิดพลาด แต่จะมีคำเตือน
user1046037

คำตอบ:


186

แม้ว่าฉันคิดว่าฉันสามารถตอบคำถามของคุณได้ แต่ก็ไม่ใช่คำตอบที่คุณต้องการ

TL; DR: @objcฟังก์ชันอาจไม่อยู่ในส่วนขยายโปรโตคอล คุณสามารถสร้างคลาสพื้นฐานแทนได้แม้ว่านั่นจะไม่ใช่ทางออกที่ดี

ส่วนขยายโปรโตคอลและวัตถุประสงค์ -C

ประการแรกคำถาม / คำตอบนี้ ( สามารถใช้วิธี Swift ที่กำหนดบนส่วนขยายบนโปรโตคอลที่เข้าถึงใน Objective-c ) ดูเหมือนจะแนะนำว่าเนื่องจากวิธีการส่งส่วนขยายโปรโตคอลภายใต้ประทุนวิธีการที่ประกาศในส่วนขยายโปรโตคอลจะไม่ปรากฏในobjc_msgSend()ฟังก์ชันและ ดังนั้นจึงมองไม่เห็นโค้ด Objective-C เนื่องจากเมธอดที่คุณพยายามกำหนดในส่วนขยายของคุณจำเป็นต้องมองเห็น Objective-C (จึงUIKitสามารถใช้งานได้) มันจึงตะโกนใส่คุณว่าไม่รวม@objcแต่เมื่อคุณรวมแล้วมันจะตะโกนใส่คุณเพราะ@objcไม่ได้รับอนุญาตใน ส่วนขยายโปรโตคอล อาจเป็นเพราะปัจจุบันส่วนขยายโปรโตคอลไม่สามารถมองเห็นได้กับ Objective-C

นอกจากนี้เรายังสามารถเห็นข้อความแสดงข้อผิดพลาดเมื่อเราเพิ่ม @objcสถานะ "@objc สามารถใช้ได้กับสมาชิกของคลาสโปรโตคอล @objc และส่วนขยายที่เป็นรูปธรรมของคลาสเท่านั้น" นี่ไม่ใช่ชั้นเรียน ส่วนขยายไปยังโปรโตคอล @objc ไม่เหมือนกับการกำหนดโปรโตคอลเอง (เช่นในข้อกำหนด) และคำว่า "คอนกรีต" จะแนะนำว่าส่วนขยายโปรโตคอลไม่นับเป็นส่วนขยายคลาสที่เป็นรูปธรรม

วิธีแก้ปัญหา

น่าเสียดายที่สิ่งนี้ทำให้คุณไม่สามารถใช้ส่วนขยายโปรโตคอลได้อย่างสมบูรณ์เมื่อการใช้งานเริ่มต้นต้องมองเห็นได้ในกรอบ Objective-C ตอนแรกฉันคิดว่าอาจ@objcไม่ได้รับอนุญาตในส่วนขยายโปรโตคอลของคุณเนื่องจาก Swift Compiler ไม่สามารถรับประกันได้ว่าประเภทที่สอดคล้องกันจะเป็นคลาส (แม้ว่าคุณจะระบุไว้เป็นพิเศษUIViewControllerก็ตาม) ดังนั้นฉันจึงวางclassข้อกำหนดP1ไว้ สิ่งนี้ไม่ได้ผล

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

หากคุณเลือกที่จะไปเส้นทางนี้โปรดคำนึงถึงคำถามนี้ ( Swift 3 ObjC Optional Protocol Method Not called in Subclass ) ดูเหมือนว่าปัญหาปัจจุบันอีกประการหนึ่งใน Swift 3 คือคลาสย่อยจะไม่สืบทอดการใช้งานข้อกำหนดโปรโตคอลเสริมของซูเปอร์คลาสโดยอัตโนมัติ คำตอบสำหรับคำถามนั้นใช้การปรับตัวเป็นพิเศษ@objcเพื่อหลีกเลี่ยงปัญหานี้

การรายงานปัญหา

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

อัปเดต

ในเดือนธันวาคม 2559 ฉบับนี้ ได้รับการรายงานไปยังชุมชน Swift ปัญหานี้ยังคงถูกทำเครื่องหมายเป็นเปิดโดยมีลำดับความสำคัญปานกลาง แต่มีการเพิ่มความคิดเห็นต่อไปนี้:

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

เนื่องจากโปรโตคอลของคุณอยู่ในโมดูลเดียวกับส่วนขยายของคุณอย่างไรก็ตามคุณอาจสามารถทำสิ่งนี้ได้ใน Swift เวอร์ชันอนาคต

อัปเดต 2

ในเดือนกุมภาพันธ์ 2017 ปัญหานี้ถูกปิดอย่างเป็นทางการในฐานะ "จะไม่ทำ" โดยหนึ่งในสมาชิกทีม Swift Core พร้อมข้อความต่อไปนี้:

นี่เป็นความตั้งใจ: ส่วนขยายโปรโตคอลไม่สามารถแนะนำจุดเข้า @objc ได้เนื่องจากข้อ จำกัด ของรันไทม์ Objective-C หากคุณต้องการเพิ่มจุดเข้า @objc ใน NSObject ให้ขยาย NSObject

การขยายNSObjectหรือแม้กระทั่งUIViewControllerจะไม่บรรลุสิ่งที่คุณต้องการ แต่น่าเสียดายที่ดูเหมือนว่ามันจะกลายเป็นไปไม่ได้

ในอนาคตระยะยาว (มาก) เราอาจสามารถกำจัดการพึ่งพา@objcวิธีการทั้งหมดได้ แต่เวลานั้นจะไม่มาในเร็ว ๆ นี้เนื่องจากกรอบ Cocoa ยังไม่ได้เขียนด้วย Swift (และไม่สามารถทำได้จนกว่าจะมี ABI ที่เสถียร) .

อัปเดต 3

ในช่วงฤดูใบไม้ร่วงปี 2019 ปัญหานี้จะกลายเป็นปัญหาน้อยลงเนื่องจากเฟรมเวิร์กของ Apple ถูกเขียนใน Swift มากขึ้นเรื่อย ๆ ตัวอย่างเช่นหากคุณใช้SwiftUIแทนคุณUIKitคุณจะหลีกเลี่ยงปัญหาทั้งหมดเพราะ@objcจะไม่จำเป็นเมื่ออ้างถึงSwiftUIวิธีการ

กรอบงานของ Apple ที่เขียนด้วย Swift ได้แก่ :

  • SwiftUI
  • RealityKit
  • รวมกัน
  • CryptoKit

ใคร ๆ ก็คาดหวังว่ารูปแบบนี้จะดำเนินต่อไปเมื่อเวลาผ่านไปว่า Swift เป็น ABI อย่างเป็นทางการและโมดูลมีเสถียรภาพเหมือน Swift 5.0 และ 5.1 ตามลำดับ


1
@ user1046037 ฉันก็ทำเช่นกันเพราะฉันเห็นว่าตัวเองประสบปัญหานี้หลายครั้งในการพัฒนาในอนาคต
Matthew Seaman

2
คุณถูกต้องคำตอบของคุณยังคงดีอยู่แม้ว่าSwift 4ตอนนี้จะไม่มีทางเลือกอื่น
user1046037

2
ฉันมีรหัสเดียวกันที่ใช้งานได้มาระยะหนึ่งแล้ว นี่เป็นสิ่งที่น่าประทับใจมาก มีวิธีการทางเลือกมากมายในโปรโตคอล Objective-C
B

1

ฉันเพิ่งพบสิ่งนี้หลังจากเปิดใช้งาน 'ความเสถียรของโมดูล' (เปิด 'สร้างไลบรารีสำหรับการแจกจ่าย') ในกรอบงานที่รวดเร็วที่ฉันใช้

สิ่งที่ฉันมีคือสิ่งนี้:

class AwesomeClass: LessAwesomeClass {
...
}

extension AwesomeClass: GreatDelegate {
  func niceDelegateFunc() {
  }
}

ฟังก์ชันในส่วนขยายมีข้อผิดพลาดเหล่านี้:

  • วิธีการอินสแตนซ์ '@objc' ในการขยายคลาสย่อยของ 'LessAwesomeClass' ต้องใช้ iOS 13.0.0

  • วิธีการที่ไม่ใช่ '@ objc' 'niceDelegateFunc' ไม่เป็นไปตามข้อกำหนดของโปรโตคอล '@objc' 'GreatDelegate'

การย้ายฟังก์ชันลงในคลาสแทนที่จะเป็นส่วนขยายช่วยแก้ปัญหานี้ได้


0

นี่เป็นวิธีแก้ปัญหาอื่น ๆ ฉันพบปัญหานี้เช่นกันและยังไม่สามารถเปลี่ยนจาก UIKit เป็น SwiftUI ได้ การย้ายการใช้งานเริ่มต้นไปยังคลาสพื้นฐานทั่วไปก็ไม่ใช่ทางเลือกสำหรับฉันเช่นกัน การใช้งานเริ่มต้นของฉันค่อนข้างกว้างขวางดังนั้นฉันจึงไม่ต้องการให้รหัสนั้นซ้ำกันทั้งหมด วิธีแก้ปัญหาที่ฉันใช้คือการใช้ฟังก์ชัน wrapper ในโปรโตคอลจากนั้นเรียกฟังก์ชันเหล่านั้นจากแต่ละคลาส ไม่สวย แต่อาจดีกว่าทางเลือกขึ้นอยู่กับสถานการณ์ จากนั้นรหัสของคุณจะมีลักษณะดังนี้:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {
}

extension P1 where Self : UIViewController {
    func wrapPresentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return UIViewController()
    }
}

class A : UIViewController, P1 {
    func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
        return wrapPresentationController(controller, viewControllerForAdaptivePresentationStyle: style)
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.