2016-07-04 3 views
1

Этот код не компилируется и может казаться глупым, как есть, но я объясню, почему это так важно!Как использовать протокол @objc с дополнительными и расширениями одновременно?

@objc protocol p { 
    optional func f1() 
    func f2() 
} 

extension p { 
    func f1() { } 
    func f2() { } 
} 


class foo: p { 
} 

Компилятор говорит Type c does not conform to protocol 'p' и что это может быть, потому что вы не можете использовать @objc необязательных и расширение в то же время (и не делаете Sence в этом случае тоже). Но рассмотрим следующий пример:

Я хочу, чтобы установить селектор на неопциональные метод, определенный в протоколе в моем расширения (основной причиной я использовал @objc):

func f1() { } ->func f1() { ... #selector(Self.f2) ... }

И Я также хочу, чтобы моя функция f2() имела поведение по умолчанию. Если я отмечаю f2() как optional, он не может использоваться в #selector, потому что компилятор не знает, действительно ли этот метод существует в случае необходимости. Конечно, существует множество неприятных обходных решений, таких как глобальные методы, отправка Selector s методам ввода и т. Д., Но есть ли у них чистый способ его достижения?


Это практический вопрос

@objc 
protocol Refreshable { 
    weak var refreshControl: UIRefreshControl? { get set } 
    optional func setupRefreshControl() 
    func refresh() 
} 

@objc 
protocol ContentLoader { 
    func load(reset: Bool) 
} 

extension Refreshable where Self: ContentLoader { 
    func refresh() { 
     delay(0.75) { [weak self] in 
      self?.load(true) 
     } 
    } 
} 

extension Refreshable where Self: UICollectionViewController { 
    func setupRefreshControl() { 
     let newRefreshControl = UIRefreshControl() 

     newRefreshControl.tintColor = UIColor.grayColor() 
     newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged) 
     collectionView?.addSubview(newRefreshControl) 
     refreshControl = newRefreshControl 
    } 
} 

Теперь, если ViewController реализует Refreshable и ContentLoader, он не находит функцию по умолчанию refresh, но это найти setupRefreshControl. Поэтому я решил, что давайте отметьте refresh как необязательный вариант, но, сделав это, вы больше не можете отправлять его в селектор.

Я даже попытался это:

func refresh() ->optional func refresh() и

let str = "refresh" 
let sel = Selector(str) 

Это Silents компилятор да, но не работает, либо ... поднимается unrecognized selector sent to instance....

+0

Я не могу воспроизвести ваши ошибки. Пожалуйста, подумайте о размещении своего фактического кода (от 'f1' и' f2' и соответствующего кода для класса, который вы хотите соответствовать протоколу 'p'), чтобы другие и я могли вам помочь. Также, пожалуйста, прямо укажите, что вы пытаетесь выполнить, поскольку оно очень разбросано и неясно. – Ike10

+0

Опционы будут проблемой с Objc. Возможно, что вы можете сделать, это настроить нескольких делегатов, каждый для определенной цели, и таким образом, если делегат не установлен, связанные функции не называются – Feldur

+0

@ Ike10 Просто обновил вопрос :) – farzadshbfn

ответ

1

Я думаю, что это невозможно в быстром (из-за того, как он соединяется с протоколами @objc). Но это работа (с использованием объектов, связанных с OBj-c), чтобы решить проблему unrecognized selector sent to instance....

fileprivate class AssociatedObject: NSObject { 
    var closure: (() ->())? = nil 

    func trigger() { 
     closure?() 
    } 
} 

// Keys should be global variables, do not use, static variables inside classes or structs. 
private var associatedObjectKey = "storedObject" 

protocol CustomProtocol: class { 
    func setup() 
} 

extension CustomProtocol where Self: NSObject { 

    fileprivate var associatedObject: AssociatedObject? { 
     get { 
      return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject 
     } 

     set { 
      objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN) 
     } 
    } 


    func setup() { 
     let object = AssociatedObject() 
     object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle 
      self?.functionToCallIndirectlyWithSelector() 
     } 

     let selector = #selector(object.trigger) 
//  Uncomment next line to test it's functionality 
     object.perform(selector) 

//  Here, you must add selector to the target which needs to call the selector, for example: 
//  refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged) 

     self.associatedObject = object 
    } 

    func functionToCallIndirectlyWithSelector() { 
     print("Function got called indirectly.") 
    } 
} 


class CustomClass: NSObject, CustomProtocol {} 

let instance = CustomClass() 

instance.setup() 

Я добавил Self: NSObject ограничение, чтобы быть в состоянии проверить его функциональность в детской площадке, я не уверен, если это необходимо или нет.

+0

Ваш 'associatedObjectKey' небезопасен. Swift не гарантирует стабильность указателя при обращении к статике. Единственная безопасная вещь - использовать глобальную переменную верхнего уровня вместо статического уровня на уровне. –

+0

@KevinBallard У вас есть какие-либо документы по этому поводу? На данный момент я собираюсь изменить ответ, но я хочу знать, почему именно такое произойдет. – farzadshbfn

+1

Вы берете ссылку на статический var с '& associatedObjectKey'. Связанные объекты никогда не смотрят на значение этого, оно строго использует указатель, поэтому указатель, который вы передаете, всегда должен быть одним и тем же. Но Swift не гарантирует, что '& associatedObjectKey' всегда будет одним и тем же указателем. Единственное место, где этот компилятор гарантирует это (хотя язык никогда не работает) предназначен для свойств верхнего уровня. Эта информация поступает прямо от инженера-компилятора. –