Быстрое свойство, которое соответствует протоколу и классу

@property (strong, nonatomic) UIViewController<UITableViewDelegate> *thing;

Я хочу реализовать свойство вроде этого кода Objective-C в Swift. Итак, вот что я пробовал:

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    var thing: T!
}

Это компилируется. Моя проблема возникает, когда я добавляю свойства из раскадровки. Тег @IBOutlet генерирует ошибку компилятора.

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    @IBOutlet weak var anotherThing: UILabel!  // error
    var thing: T!
}

Ошибка:

Variable in a generic class cannot be represented in Objective-C

Я реализую это право? Что я могу сделать, чтобы исправить или обойти эту ошибку?

EDIT:

Swift 4, наконец, имеет решение этой проблемы. См. Мой обновленный ответ.

Ответ 1

Обновление для Swift 4

Swift 4 добавила поддержку для представления типа как класса, который соответствует протоколу. Синтаксис Class & Protocol. Вот пример кода, использующего эту концепцию из "Что нового в Swift" (сеанс 402 из WWDC 2017):

protocol Shakeable {
    func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }

// Example function to generically shake some control elements
func shakeEm(controls: [UIControl & Shakeable]) {
    for control in controls where control.state.isEnabled {
        control.shake()
    }
}

С Swift 3 этот метод вызывает проблемы, потому что вы не можете передавать правильные типы. Если вы попытаетесь пройти в [UIControl], у него нет метода shake. Если вы попытаетесь пройти в [UIButton], тогда код компилируется, но вы не можете передать ни одного UISlider s. Если вы перейдете в [Shakeable], вы не сможете проверить control.state, потому что Shakeable этого не делает. Swift 4, наконец, затронул тему.

Старый ответ

Я обойду эту проблему в настоящее время со следующим кодом:

// This class is used to replace the UIViewController<UITableViewDelegate> 
// declaration in Objective-C
class ConformingClass: UIViewController, UITableViewDelegate {}

class AClass: UIViewController {
    @IBOutlet weak var anotherThing: UILabel!
    var thing: ConformingClass!
}

Это кажется мне взломанным. Если бы какой-либо из методов делегата был необходим, мне пришлось бы реализовать эти методы в ConformingClass (которые я НЕ хочу делать) и переопределить их в подклассе.

Я опубликовал этот ответ, если кто-то еще сталкивается с этой проблемой, и мое решение помогает им, но я не доволен решением. Если кто-нибудь опубликует лучшее решение, я приму их ответ.

Ответ 2

Это не идеальное решение, но вы можете использовать универсальную функцию вместо общего класса, например:

    class AClass: UIViewController {
      @IBOutlet weak var anotherThing: UILabel!
      private var thing: UIViewController?

      func setThing<T: UIViewController where T: UITableViewDelegate>(delegate: T) {
        thing = delegate
      }
    }

Ответ 3

Я столкнулся с одной и той же проблемой, а также попробовал общий подход. В конечном итоге общий подход нарушил весь дизайн.

После переосмысления этой проблемы я обнаружил, что протокол, который нельзя использовать для полного указания типа (другими словами, должен содержать дополнительную информацию типа, например тип класса), вряд ли будет полным. Более того, хотя стиль Objc объявления ClassType<ProtocolType> подходит, он не учитывает преимущества абстракции, предоставляемой протоколом, потому что такой протокол действительно не повышает уровень абстракции. Кроме того, если такое объявление появляется в нескольких местах, оно должно быть дублировано. Хуже того, если несколько объявлений такого типа взаимосвязаны (возможно, один объект будет передан вокруг них), программа становится хрупкой и трудноподдерживающейся, потому что позже, если объявление в одном месте должно быть изменено, все соответствующие объявления должны также можно изменить.

Решение

Если в случае использования свойства используется как протокол (скажем ProtocolX), так и некоторые аспекты класса (скажем ClassX), можно было бы принять во внимание следующий подход:

  • Объявите дополнительный протокол, который наследует от ProtocolX с добавленными требованиями к свойствам/свойствам, которые ClassX автоматически удовлетворяет. Как и в приведенном ниже примере, метод и свойство являются дополнительными требованиями, которые оба из них UIViewController автоматически удовлетворяют.

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var navigationController: UINavigationController? { get }
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
    }
    
  • Объявите дополнительный протокол, наследующий от ProtocolX, с дополнительным свойством только для чтения типа ClassX. Этот подход не только позволяет использовать ClassX в целом, но также демонстрирует гибкость, не требующую реализации для подкласса ClassX. Например:

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var viewController: UIViewController { get }
    }
    
    // Implementation A
    class CustomViewController: UIViewController, UITableViewDelegate {
    
        var viewController: UIViewController { return self }
    
        ... // Other important implementation
    }
    
    // Implementation B
    class CustomClass: UITableViewDelegate {
    
        private var _aViewControllerRef: UIViewController // Could come from anywhere e.g. initializer
        var viewController: UIViewController { return _aViewControllerRef } 
    
        ... // UITableViewDelegate methods implementation
    }
    

PS. Фрагмент выше для демонстрации только, смешивание UIViewController и UITableViewDelegate вместе не рекомендуется.

Изменить для Swift 2 +: Спасибо за комментарий @Shaps, можно добавить следующее, чтобы сохранить необходимость реализовать требуемое свойство везде.

extension CustomTableViewDelegate where Self: UIViewController { 
    var viewController: UIViewController { return self } 
}

Ответ 4

вы можете объявить делегата в Swift следующим образом:

weak var delegate : UITableViewDelegate?

Он будет работать даже с гибридным (Objective-c и быстрым) проектом. Делегат должен быть факультативным и слабым, поскольку его доступность не гарантируется и слабая не создает цикл сохранения.

Вы получаете эту ошибку, потому что в Objective-C нет дженериков, и это не позволит вам добавить свойство @IBOutlet.

Изменить: 1. Принуждение типа к делегату

Чтобы заставить этот делегат всегда быть UIViewController, вы можете реализовать пользовательский сеттер и исключить исключение, если оно не является UIViewController.

weak var _delegate : UITableViewDelegate? //stored property
var delegate : UITableViewDelegate? {
    set {
        if newValue! is UIViewController {
            _delegate = newValue
        } else {
            NSException(name: "Inavlid delegate type", reason: "Delegate must be a UIViewController", userInfo: nil).raise()
        }
    }
    get {
        return _delegate
    }
}