在Swift

时间:2018-08-16 15:00:20

标签: swift generics swift-protocols associated-types

我正在尝试实现具有关联类型的数据源协议

protocol DataSourceCompatible {
    associatedtype CellModel
    func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

协议AddressBookViewModelType继承自基本协议,并将关联值限制为另一个协议

protocol AddressBookViewModelType: class, DataSourceCompatible where CellModel == AddressBookCellModelType {
}

AddressBookViewModelAddressBookViewModelType协议的具体实现

class AddressBookViewModel: AddressBookViewModelType {
    func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModelType {
        let contact = sectionedContacts[indexPath.section][indexPath.row]
        return AddressBookCellModel(contact: contact)
    }
}

代码可以正常编译,但是当我在viewcontroller上将viewmodel声明为属性时,编译器将失败,并显示Protocol 'AddressBookViewModelType' can only be used as a generic constraint because it has Self or associated type requirements

class AddressBookViewController: UIViewController {
    private var viewModel: AddressBookViewModelType!

    func configure(viewModel: AddressBookViewModelType) {
        self.viewModel = viewModel
    }
...
}

我记得看到类型擦除可能解决了这个问题,但是我对类型擦除的概念并不熟悉。有办法解决这个问题吗?

更新:

  

AddressBookCellModelType和AddressBookCellModel在这里如何关联?

这是实现协议的结构。

protocol AddressBookCellModelType {
    var name: String { get }
    var photo: UIImage? { get }
    var isInvited: Bool { get }
}

struct AddressBookCellModel: AddressBookCellModelType {
 ....
}

3 个答案:

答案 0 :(得分:2)

您是否尝试过仅将其用作通用名称,例如警告/错误提示:

 // Create the Git pull migration script
 $process = new Process('sh ./createproject.sh '.$this->url);
 $process->setTimeout(300);
 $process->run();

 // Executes after the command finishes
 if (!$process->isSuccessful()) {
     throw new ProcessFailedException($process3);
 }

 \Log::Debug($process->getOutput());

您需要使用变量SUBDOMAIN="$1.domain.com" git clone git@bitbucket.org:user/project.git cd /home/cloudspr/$SUBDOMAIN export COMPOSER_HOME="/opt/cpanel/composer/bin/composer" composer install -d /home/user/$SUBDOMAIN pwd php artisan key:generate php artisan migrate --seed --database=mysql grep APP_KEY .env 的属性初始化控制器,以便可以推断类型。

答案 1 :(得分:1)

要扩展我在评论中的问题,请看这段代码,看起来很灵活,而无需添加AddressBookCellModelTypeAddressBookViewModelType,这也可以消除麻烦,而仍然在DataSourceCompatible上具有通用性。

// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
    associatedtype CellModel
    func cellModelForItem(at indexPath: IndexPath) -> CellModel
}

// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)    
struct AddressBookCellModel {
    var name: String
    var photo: UIImage?
    var isInvited: Bool
}

// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
    let sectionedContacts: [[AddressBookCellModel]] = []
    func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
        return sectionedContacts[indexPath.section][indexPath.row]
    }
}

class AddressBookViewController: UIViewController {
    private var viewModel: AddressBookViewModel!

    func configure(viewModel: AddressBookViewModel) {
        self.viewModel = viewModel
    }
}

通过这种方式可以实现通用的VC,而无需引入更多所需的内容:

class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
    private var viewModel: DataSource.CellModel!

    func configure(viewModel: DataSource.CellModel) {
        self.viewModel = viewModel
    }
}

let vc = DataSourceViewController<AddressBookViewModel>()

答案 2 :(得分:-1)

这只是Swift规范,不能使用“具有关联类型的协议”作为类型声明。原因是编译器在编译时不知道关联的类型实际上是什么,这违反了Swift的“类型安全性”。

解决方案是像您所说的那样使用类型擦除器,或者使类型通用。