Swift中协议更好的重用标识符

最近,我正在经历设置新的UICollectionView 。 我已经为单元格编写了一个视图模型,并准备了一个UICollectionViewCell子类。 剩下要做的就是实现cellForItem(at:)

UICollectionView :如果您以前没有使用过UICollectionView ,而是更像UITableView ,那么您可以将Collection替换为Table ,将Item替换为Row并且该帖子仍然有效。

作为一名负责任的iOS工程师[需要引用],我知道我在这里要做的第一件事就是让我的collectionView出队一个单元。 为此,我必须调用dequeueReusableCell(withReuseIdentifier:for:) ,并传递一个String “重用标识符”。为了使collectionView知道我在说什么,我还必须调用register(_:forCellWithReuseIdentifier:) ,因此它知道将此重用标识符映射到我的UICollectionViewCell子类。

该子类大致如下所示:

 最后一个类CustomCollectionViewCell:UICollectionViewCell {//代码等。 

除此以外: CustomCollectionViewCell只是一个示例名称。 请不要这样命名类。

我确定此单元格的明智重用标识符为"CustomCollectionViewCell" 。 因此,我使用了它,并调用了这两种方法。 这些调用看起来像这样:

  //在设置方法中 
collectionView.register(
CustomCollectionViewCell.self,
forCellWithReuseIdentifier:“ CustomCollectionViewCell”

//在cellForItem(at :)中
collectionView.dequeueReusableCell(
withReuseIdentifier:“ CustomCollectionViewCell”
用于:indexPath

这显然很糟糕。 我们周围挂着一根魔力绳,而且它不止一个地方。 显然,这需要一些重构,所以这正是我所做的。 第一步只是将字符串移动到CustomCollectionViewCell类的静态常量中。

 最后一个类CustomCollectionViewCell:UICollectionViewCell {静态让复用Identifier =“ CustomCollectionViewCell”} 

好的,好的开始。 这意味着我们可以使用CustomCollectionViewCell.reuseIdentifier访问代码中的任何位置的标识符。 已经比我们以前有了很大的改进。 但这确实带来了另一个问题。 如果以后更改类名怎么办?

我们可能只记得更改它,但让我们在这里成为现实:我们是人类,我们并不总是记住这样的事情。 即使它正盯着我们看着。 这就是为什么我们需要计算机为我们做这些事情的原因。 这样就很好地引出了下一个问题:我们可以让编译器来帮助我们吗?

好吧,如果答案是否定的,这将是一个简短的博客。

那么我们该怎么做呢? 参见上面,我们将对CustomCollectionViewCell类型的引用传递到register(_:forCellWithReuseIdentifier:) ? 如果我们可以将这种类型转换为String并使用它,那不是很好吗? 那太好了。

(剧烈的停顿。)

是的,我们当然可以做到这一点。 我们可以用String(describing: CustomCollectionViewCell.self) reuseIdentifier String(describing: CustomCollectionViewCell.self)替换复用代码中的裸String ,并获得完全相同的输出。 现在,如果类名更改,则编译器将无法识别那里的类型,并会让我们知道。 成功!


好的,现在,您可能正在查看到目前为止我们所做的事情,并在思考:“马修,这一切都很好,但是标题说的是“协议”,到目前为止,我们只是因为有点重构而生气。您肯定是对的。 仅仅将以上所有内容视为下一步发展的动力。

因此,在使用String(describing: TypeName.self)生成重用标识符一段时间后,我开始怀疑是否有一种方法可以消除在每个单元子类中定义新的复用标识符的样板。

我首先编写以下协议:

 协议ReuseIdentifying { 
静态var复用标识符:字符串{get}
}

然后像这样扩展它:

 扩展名ReuseIdentifying { 
静态var复用标识符:字符串{
返回字符串(描述:/ * er ...这里是什么?* /)
}
}

我在这里呆了一段时间。 我需要一种动态引用实现协议的类型的方法。 显然,使用ReuseIdentifying.self是行不通的,因为这只会每次返回"ReuseIdentifying"

过去,我在协议中使用Self来表示“实现类型”。因此,我想知道是否可以摆脱Self.self类的Self.self 。 看起来很荒谬,但是…

剧透:它完全有效。

所以这是最终的协议扩展:

 扩展名ReuseIdentifying { 
静态var复用标识符:字符串{
返回字符串(描述:Self.self)
}
}

现在,通过将该协议应用于CustomCollectionViewCell我可以删除reuseIdentifier定义,一切按预期进行。 根据需要,返回的复用reuseIdentifier"CustomCollectionViewCell"

还有更好的消息:我们可以做得更好。 假设我们希望每个UICollectionViewCell子类都具有一个reuseIdentifier 。 实际上,我们不需要单独应用此协议。 我们可以执行以下操作:

 扩展UICollectionViewCell:ReuseIdentifying {} 

魔法! ✨

我喜欢测试事物,因此我想针对此协议扩展编写一些单元测试。

我尝试在测试主体中定义一个类。 就像是:

  func test_reuseIdentifier_classIsCalledSomeClass_isSomeClass(){ 
class SomeClass:ReuseIdentifying {}
XCTAssertEqual(“ SomeClass”,SomeClass.reuseIdentifier)
}

据我所知,没有理由不起作用。 但是String(describing:)在这一点上开始变得令人困惑。 reuseIdentifer的返回值为"SomeClass #1"

我认为在方法内部定义类可能是一个问题,因此我将其移至文件范围并再次运行测试。 而且有效!

因此,如果有人知道为什么在方法内部定义类会导致在描述的末尾附加"#1" ,那么我将非常有兴趣找出答案。