ข้อผิดพลาดของคอมไพเลอร์: วิธีที่ตัวเลือก Objective-C ขัดแย้งกับการประกาศก่อนหน้าด้วยตัวเลือก Objective-C เดียวกัน


209

ฉันเริ่มเรียนรู้ Swift และติดตามการบรรยายวิดีโอของมหาวิทยาลัยสแตนฟอร์ดที่ดีมากบน YouTube นี่คือลิงค์หากคุณสนใจหรือช่วยได้ (แม้ว่าไม่จำเป็นต้องเข้าใจปัญหาของฉัน):

การพัฒนาแอพ iOS 8 ด้วย Swift - 2. Xcode และ Swift, MVC เพิ่มเติม

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

รหัสที่สร้างข้อผิดพลาดคือ:

import UIKit

class BugViewController: UIViewController
{
    func perform(operation: (Double) -> Double) {
    }

    func perform(operation: (Double, Double) -> Double) {
    }
}

สิ่งนี้สร้างข้อผิดพลาดคอมไพเลอร์ต่อไปนี้:

วิธีการ 'ดำเนินการ' กับตัวเลือก Objective-C 'ดำเนินการ:' ขัดแย้งกับการประกาศก่อนหน้านี้ด้วยตัวเลือกวัตถุประสงค์ -C เดียวกัน

โดยเพียงแค่การลบการจัดกลุ่มย่อยของ UIViewController รวบรวมรหัส:

import UIKit

class BugViewController
{
    func perform(operation: (Double) -> Double) {
    }

    func perform(operation: (Double, Double) -> Double) {
    }
}

ข้อมูลอื่น ๆ ที่อาจเกี่ยวข้องหรือไม่เกี่ยวข้อง:

  • ฉันเพิ่งอัพเกรดเป็นโยเซมิตี
  • เมื่อฉันติดตั้ง Xcode ฉันลงเอยด้วยรุ่นเบต้า (เวอร์ชัน 6.3 (6D543q)) เพราะ (ถ้าฉันจำได้ถูกต้อง) นี่เป็นรุ่นที่ฉันต้องใช้ในเวอร์ชั่น OS X ของฉัน

ฉันครึ่งหวังว่านี่เป็นข้อผิดพลาดในคอมไพเลอร์เพราะมิฉะนั้นสิ่งนี้ไม่สมเหตุสมผลสำหรับฉัน ความช่วยเหลือใด ๆ ที่ได้รับสุดซึ้งมาก!


3
คุณสามารถรัน Xcode 6.2 บน Yosemite คุณสามารถดาวน์โหลดได้จากแอพสโตร์และสามารถใช้งานได้บนระบบของคุณด้วยรุ่นเบต้า ฉันไม่แนะนำให้ใช้ Xcode 6.3 สำหรับคลาส Stanford ณ จุดนี้เนื่องจากเป็นรุ่นเบต้าและมี Swift 1.2 ซึ่งแตกต่างจาก Swift รุ่นก่อนหน้าที่ใช้ในวิดีโอ
vacawama

2
คำตอบ (ยอมรับในปัจจุบัน) จากผู้ใช้ (feb) ตั้งแต่วันที่ 5 เมษายนไม่ใช่คำตอบที่ดีที่สุดอีกต่อไป แต่คำตอบจาก (James Zhang) จากวันที่ 16 เม.ย. นั้นมีความเฉพาะเจาะจงและถูกต้องมากขึ้น
phlebotinum

คำตอบ:


144

Objective-C ไม่รองรับการบรรทุกข้อมูลเกินวิธีคุณต้องใช้ชื่อวิธีอื่น เมื่อคุณสืบทอด UIViewController คุณได้สืบทอด NSObject และทำให้คลาสเชื่อมต่อกับ Obj-C ในอีกทางหนึ่ง Swift รองรับการรับน้ำหนักมากเกินไปนั่นเป็นสาเหตุที่ทำงานได้เมื่อคุณลบการสืบทอด


2
Objective-C SUPPORTS วิธีการเอาชนะ (ด้วยคำสั่งคอมไพเลอร์ (ที่ปราบปรามได้) เพื่อแจ้งให้คุณทราบเกี่ยวกับการโอเวอร์โหลดสิ่งที่ใช้งานไปแล้ว) Apple ไม่ต้องการให้คุณทำเช่นนั้นเพื่อไม่ให้เฟรมเวิร์กของพวกเขาโอเวอร์โหลด ฉันใช้งานเกินพิกัด fe UIFontทุกวัน
Michi

@ polarwar คำตอบด้านล่างเป็นคำตอบที่ดีที่สุดสำหรับ Swift 2: stackoverflow.com/a/31500740/144088
Crashalot

237

ฉันเองก็กำลังเรียนหลักสูตร Standford และฉันติดอยู่ที่นี่เป็นเวลานานเช่นกัน แต่หลังจากการค้นหาฉันพบบางสิ่งจากที่นี่: บันทึกย่อประจำรุ่นของ Xcodeและมันพูดถึงบางสิ่งด้านล่าง:

Swift 1.2 นั้นเข้มงวดเกี่ยวกับการตรวจสอบการโอเวอร์โหลดแบบอิงตามประเภทของ @objc และ initializers สิ่งที่ไม่ได้รับการสนับสนุนจาก Objective-C

// Has the Objective-C selector "performOperation:".
func performOperation(op: NSOperation) { /* do something */ }
// Also has the selector "performOperation:".
func performOperation(fn: () -> Void) {
    self.performOperation(NSBlockOperation(block: fn))
}

รหัสนี้จะทำงานเมื่อถูกเรียกใช้จาก Swift แต่อาจผิดพลาดได้อย่างง่ายดายหากถูกเรียกจาก Objective-C ในการแก้ปัญหานี้ให้ใช้ประเภทที่ไม่รองรับโดย Objective-C เพื่อป้องกันไม่ให้คอมไพเลอร์ Swift เปิดเผยสมาชิกไปยัง Objective-C runtime:

  • ถ้าเหมาะสมให้ทำเครื่องหมายสมาชิกว่าเป็นส่วนตัวเพื่อปิดการใช้งานอนุมานของ @objc
  • มิฉะนั้นให้ใช้พารามิเตอร์ดัมมีที่มีค่าเริ่มต้นตัวอย่างเช่น _ nonobjc: () = () (19826275)

การแทนที่เมธอดที่สัมผัสกับ Objective-C ในคลาสย่อยส่วนตัวไม่ได้ถูกอนุมานว่าเป็น @objc ทำให้คอมไพเลอร์ Swift ผิดพลาด เพิ่มแอ็ตทริบิวต์ @objc อย่างชัดเจนให้กับวิธีการเอาชนะใด ๆ ดังกล่าว (19935352)

สัญลักษณ์จาก SDK ไม่สามารถใช้ได้เมื่อใช้ Open Quickly ในโครงการหรือพื้นที่ทำงานที่ใช้ Swift (20349540)

สิ่งที่ฉันทำคือแค่เพิ่ม "ส่วนตัว" ด้านหน้าวิธีการแทนที่เช่นนี้:

    private func performOperation(operation: Double -> Double) {
    if operandStack.count >= 1 {
        displayValue = operation(operandStack.removeLast())
        enter()
    }
}

3
การแก้ปัญหานี้เป็นส่วนใหญ่ที่ทำงานผมพบ IMHO เช่นนี้โดยสิ้นเชิงทำให้รู้สึกถึงการกำหนดวิธีการนี้ส่วนตัว
demental

38
โปรดทราบว่าขณะนี้มีแอตทริบิวต์ @nonobjc ด้วยซึ่งสามารถใช้เพื่อแยกวิธีออกจากรันไทม์ Objective-C
Erik J

2
ฉันสองความคิดเห็น @ ErikJ และคำตอบของ polarwar ด้านล่าง นี่น่าจะเป็นคำตอบที่ดีที่สุดในการก้าวไปข้างหน้ากับ Swift 2 และ xcode 7 หากคุณยังไม่ได้อัปเดตฉันขอแนะนำอย่างยิ่ง
ออสติน

@ polarwar คำตอบด้านล่างเป็นคำตอบที่ดีที่สุดสำหรับ Swift 2: stackoverflow.com/a/31500740/144088
Crashalot

111

ตามที่ได้รับการตอบแล้ว ObjC ไม่สนับสนุนวิธีการโอเวอร์โหลด (สองวิธีที่มีชื่อเดียวกัน) และ In swift 2 ภายใต้ Xcode 7 มีสองตัวเลือกในการแก้ปัญหาประเภทนี้ ทางเลือกหนึ่งคือเปลี่ยนชื่อเมธอดโดยใช้แอททริบิวต์:@objc(newNameMethod:)

func methodOne(par1, par2) {...}

@objc(methodTwo:)
func methodOne(par1) {...}

ตัวเลือกอื่นเพื่อแก้ไขปัญหานี้ใน Xcode 7+ คือการใช้ @nonobjcคุณลักษณะกับวิธีการตัวห้อยหรือตัวเริ่มต้นใด ๆ

func methodOne() {...}

@nonobjc
func methodOne() {...}

6
วิธีนี้จะช่วยแก้ปัญหาสำหรับ swift 2 (และสูงกว่า) ควรอัปเดตเป็นคำตอบที่ถูกต้องที่สุด TY
Maxim Veksler

2
สำหรับใครก็ตามที่ใช้ Swift 2 และ Xcode 7 + นี่คือคำตอบที่ถูกต้องฉันเห็นด้วยกับ polarwar
TerNovi

17

ปัญหาคือUIViewControllerเป็น@objcระดับ เมื่อสืบทอดจากUIViewController, BugViewControllerนอกจากนี้ยังมี@objcระดับ

ซึ่งหมายความว่าจะต้องเป็นไปตามกฎของ Objective-C selectors (ชื่อของวิธีการ) วิธีการfunc perform(operation: (Double) -> Double)และทั้งสองมีตัวเลือกเดียวfunc perform(operation: (Double, Double) -> Double) @selector(perform:)สิ่งนี้ไม่ได้รับอนุญาต

เพื่อแก้ปัญหานี้ใช้ชื่อที่แตกต่างกันเช่นและfunc perform1(operation: (Double) -> Double)func perform2(operation: (Double, Double) -> Double)


ฉันคิดว่าวิธีที่ดีที่สุดในการจัดการสิ่งนี้คือการทำให้ชื่อของคุณperform()มีความหมายมากขึ้น วิธีการเหล่านี้ทำอะไร พวกเขาจะเปลี่ยนสถานะของตัวควบคุมมุมมองได้อย่างไร ดูUIViewControllerวิธีการอื่น ๆเพื่อสร้างความรู้สึกถึงสไตล์ของการตั้งชื่อเมธอดหรืออ่านชื่อเมธอดควรแสดงออกและไม่ซ้ำกันภายในคลาส


ขอบคุณ - นี่ตอบคำถามของฉันอย่างสมบูรณ์และในขณะที่คุณเป็นคนแรกที่ฉันจะทำเครื่องหมายว่าถูกต้อง
อุปถัมภ์

ต้องบอกว่าฉันยังไม่เข้าใจว่าทำไมรหัสในการบรรยายทำงานเพราะฉันค่อนข้างแน่ใจว่ามันทำในสิ่งที่ฉันไม่ได้รวบรวมรหัส! เฮ้โฮ - ฉันจะกลับไปตรวจสอบอีกครั้ง จะต้องมีสิ่งที่แตกต่าง
อุปถัมภ์

2
@Auspice มันอาจไม่ได้เกิดข้อผิดพลาดกับเวอร์ชั่นของ Xcode ที่พวกเขาใช้สำหรับวิดีโอ แต่มันก็ยังคงเป็นปัญหา จนกระทั่ง Xcode 6.3 นั้นคอมไพเลอร์สามารถตรวจจับสิ่งนี้ได้เตือนคุณ
มิก MacCallum

3
Paul Hegarty ต้องการแสดงให้เห็นถึงฟังก์ชั่น 'overloading' ที่นี่ (2 ฟังก์ชั่นที่มีชื่อเดียวกัน แต่มีชุดของการขัดแย้งที่แตกต่างกัน) ดังนั้นเขาจึงใช้ชื่อเมธอดเดียวกันในวัตถุประสงค์! การโหลดมากเกินไปทำได้เฉพาะใน Swift ไม่ใช่ใน Objective-C นั่นเป็นเหตุผลที่วิธีแก้ปัญหาคือการลบแบบฟอร์มการสืบทอด UIViewController (ซึ่งเป็นคลาส Objective-C) หรือประกาศวิธีการส่วนตัว คำตอบทั้งสองนี้มีคำอธิบายโดยละเอียดในคำตอบอื่น ๆ ที่นี่
Ronny Webers

จริงๆแล้วฉันใช้คีย์เวิร์ดส่วนตัวด้านหน้าของฟังก์ชั่น เช่น private func performOperation (การทำงาน: Double -> Double) {} และ private func performOperation (การทำงาน: (Double, Double) -> Double) {} ที่นี่ฉันได้รับวิธีโอเวอร์โหลดด้วยความช่วยเหลือของ PRIVATE เพราะฉันใช้ทั้งคู่ใน ViewController.Swift เท่านั้น ทำไมคอมไพเลอร์ไม่พูดข้อผิดพลาดใด ๆ
iTag

5

จากhttps://developer.apple.com/library/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc6_release_notes.htmlใต้ "Xcode 6.3 หมายเหตุประจำรุ่น" -> "การเปลี่ยนแปลงภาษาที่รวดเร็ว"

ขณะนี้ Swift ตรวจพบความแตกต่างระหว่างการบรรทุกเกินพิกัดและการเอาชนะในระบบประเภท Swift และพฤติกรรมที่มีประสิทธิภาพที่เห็นผ่านทางรันไทม์ Objective-C


2

ฉันได้รับข้อผิดพลาดเดียวกันเนื่องจากมีสองวิธีที่มีลายเซ็น Obj-C เดียวกัน:

static func prepareForUpSyncing(obj : NSManagedObject!) -> Bool
static func prepareForUpSyncing(objs : [NSManagedObject]!) -> Bool

ฉันไม่ต้องการทำเครื่องหมายหนึ่งในนั้นเป็น @nonobjc เนื่องจากความเป็นไปได้ของผลที่ไม่คาดคิดที่รันไทม์ (บางคนสามารถแก้ไขฉันได้หากไม่มีโอกาส)

แก้ไขได้โดยใช้คุณสมบัติชื่อพารามิเตอร์ภายนอกของ Swift (ฉันสร้างชื่อภายนอกเหมือนกับชื่อโลคอล) เป็นวิธีที่สองซึ่งเปลี่ยนลายเซ็นของวิธี Obj-c ได้อย่างมีประสิทธิภาพ:

static func prepareForUpSyncing(objs objs : [NSManagedObject]!) -> Bool {
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.