Il metodo non "@ objc" non soddisfa i requisiti facoltativi del protocollo "@objc"


104

Panoramica:

  • Ho un protocollo P1 che fornisce un'implementazione predefinita di una delle funzioni opzionali Objective-C.
  • Quando fornisco un'implementazione predefinita della funzione opzionale, viene visualizzato un avviso

Avviso del compilatore:

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

Versione:

  • Rapido: 3
  • Xcode: 8 (versione pubblica)

Tentativi effettuati:

  • Ho provato ad aggiungere @objcma non aiuta

Domanda:

  • Come ho risolto questo problema?
  • C'è un lavoro in giro?

Codice:

@objc protocol P1 : UIAdaptivePresentationControllerDelegate {

}

extension P1 where Self : UIViewController {

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


class A : UIViewController, P1 {

}

Hai l'ultima versione di Xcode? Non ricevo mai alcun errore se rimuovo@objc
Qbyte

Sto usando Xcode 8 (ultima versione pubblica). Non ci sono errori, ma verrà visualizzato un avviso
user1046037

Risposte:


183

Anche se penso di poter rispondere alla tua domanda, non è una risposta che ti piacerà.

TL; DR: le @objc funzioni potrebbero non essere attualmente nelle estensioni di protocollo. Potresti invece creare una classe base, anche se non è una soluzione ideale.

Estensioni del protocollo e Objective-C

In primo luogo, questa domanda / risposta ( Metodo Can Swift definito su estensioni su protocolli accessibili in Objective-c ) sembra suggerire che a causa del modo in cui le estensioni di protocollo vengono inviate sotto il cofano, i metodi dichiarati nelle estensioni di protocollo non sono visibili alla objc_msgSend()funzione, e pertanto non sono visibili al codice Objective-C. Poiché il metodo che stai cercando di definire nella tua estensione deve essere visibile a Objective-C (quindi UIKitpuoi usarlo), ti urla per non includerlo @objc, ma una volta che lo includi, ti urla perché @objcnon è consentito estensioni di protocollo. Ciò è probabilmente dovuto al fatto che le estensioni del protocollo non sono attualmente in grado di essere visibili a Objective-C.

Possiamo anche vedere che il messaggio di errore una volta che abbiamo aggiunto @objcafferma "@objc può essere utilizzato solo con membri di classi, protocolli @objc ed estensioni concrete di classi." Questa non è una classe; un'estensione a un protocollo @objc non è la stessa di essere nella definizione del protocollo stesso (cioè nei requisiti), e la parola "concreto" suggerirebbe che un'estensione del protocollo non conta come un'estensione di classe concreta.

Soluzione

Sfortunatamente, questo ti impedisce quasi completamente di usare le estensioni di protocollo quando le implementazioni predefinite devono essere visibili ai framework Objective-C. In un primo momento, ho pensato che forse @objcnon fosse consentito nell'estensione del protocollo perché Swift Compiler non poteva garantire che i tipi conformi sarebbero classi (anche se specificatamente specificato UIViewController). Quindi ho posto un classrequisito P1. Questo non ha funzionato.

Forse l'unica soluzione è usare semplicemente una classe base invece di un protocollo qui, ma questo ovviamente non è del tutto ideale perché una classe può avere solo una singola classe base ma è conforme a più protocolli.

Se scegli di seguire questa strada, ti preghiamo di prendere in considerazione questa domanda ( Swift 3 ObjC Optional Protocol Method Not Called in Subclass ). Sembra che un altro problema attuale in Swift 3 sia che le sottoclassi non ereditano automaticamente le implementazioni dei requisiti di protocollo opzionali della loro superclasse. La risposta a queste domande utilizza un adattamento speciale di @objcper aggirare il problema.

Segnalazione del problema

Penso che questo sia già stato discusso tra coloro che lavorano ai progetti open source di Swift, ma potresti essere sicuro che ne sono a conoscenza utilizzando Bug Reporter di Apple , che probabilmente alla fine si farebbe strada verso lo Swift Core Team, o il bug reporter di Swift . Tuttavia, entrambi potrebbero trovare il tuo bug troppo ampio o già noto. Il team Swift potrebbe anche considerare ciò che stai cercando come una nuova funzionalità linguistica, nel qual caso dovresti prima controllare le mailing list .

Aggiornare

Nel dicembre 2016, questo problema è stato segnalato alla comunità Swift. Il problema è ancora contrassegnato come aperto con una priorità media, ma è stato aggiunto il seguente commento:

Questo è inteso. Non c'è modo di aggiungere l'implementazione del metodo a ogni adottante, poiché l'estensione potrebbe essere aggiunta dopo la conformità al protocollo. Suppongo che potremmo consentirlo se l'estensione è nello stesso modulo del protocollo, però.

Poiché il tuo protocollo si trova nello stesso modulo della tua estensione, tuttavia, potresti essere in grado di farlo in una versione futura di Swift.

Aggiorna 2

Nel febbraio 2017, questo numero è stato ufficialmente chiuso come "Won't Do" da uno dei membri dello Swift Core Team con il seguente messaggio:

Questo è intenzionale: le estensioni del protocollo non possono introdurre punti di ingresso @objc a causa delle limitazioni del runtime Objective-C. Se desideri aggiungere punti di ingresso @objc a NSObject, estendi NSObject.

L'estensione NSObjecto addirittura UIViewControllernon realizzerà esattamente ciò che desideri, ma sfortunatamente non sembra che diventerà possibile.

In un futuro (molto) a lungo termine, potremmo essere in grado di eliminare completamente la dipendenza dai @objcmetodi, ma quel momento probabilmente non arriverà presto poiché i framework Cocoa non sono attualmente scritti in Swift (e non possono esserlo finché non avrà un ABI stabile) .

Aggiorna 3

A partire dall'autunno 2019, questo sta diventando meno un problema perché sempre più framework Apple vengono scritti in Swift. Ad esempio, se usi SwiftUIinvece di UIKit, eludi completamente il problema perché @objcnon sarebbe mai necessario quando ti riferisci a un SwiftUImetodo.

I framework Apple scritti in Swift includono:

  • SwiftUI
  • RealityKit
  • Combina
  • CryptoKit

Ci si aspetterebbe che questo modello continui nel tempo ora che Swift è ufficialmente ABI e modulo stabile a partire da Swift 5.0 e 5.1, rispettivamente.


1
@ user1046037 Anch'io, poiché posso vedermi incappare in questo problema molte volte nello sviluppo futuro.
Matthew Seaman,

2
Hai ragione, la tua risposta è ancora valida nonostante Swift 4nessun'altra alternativa al momento.
user1046037

1
Ho avuto esattamente lo stesso codice che funzionava per me per un po ', ma in una versione successiva di Xcode. Questo è piuttosto esasperante. Ci sono così tanti metodi opzionali nei protocolli Objective-C.
Departamento B

0

Mi sono imbattuto in questo dopo aver abilitato la "stabilità del modulo" (attivando "Crea librerie per la distribuzione") in un framework rapido che utilizzo.

Quello che avevo era qualcosa del genere:

class AwesomeClass: LessAwesomeClass {
...
}

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

La funzione nell'estensione presentava questi errori:

  • Il metodo di istanza "@objc" nell'estensione della sottoclasse di "LessAwesomeClass" richiede iOS 13.0.0

  • Non - Il metodo "@ objc" "niceDelegateFunc" non soddisfa i requisiti del protocollo "@objc" "GreatDelegate"

Spostare le funzioni nella classe piuttosto che in un'estensione ha risolto il problema.


0

Ecco un'altra soluzione alternativa. Mi sono imbattuto anche in questo problema e non posso ancora passare da UIKit a SwiftUI. Anche spostare le implementazioni predefinite in una classe base comune non era un'opzione per me. Le mie implementazioni predefinite erano piuttosto estese, quindi non volevo davvero che tutto quel codice fosse duplicato. La soluzione alternativa che ho finito per utilizzare è stata quella di utilizzare le funzioni wrapper nel protocollo e quindi chiamare semplicemente quelle funzioni da ciascuna classe. Non carino, ma potrebbe essere migliore dell'alternativa, a seconda della situazione. Il tuo codice sarebbe quindi simile a questo:

@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)
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.