Swift MVC架构下是否允许将Model类传入自定义UITableViewCell视图?
问题答复
现有设计是否符合MVC规范?
完全不可接受,主要有两个层面的问题:
- 架构职责混乱:MVC架构中视图层(包括
UITableViewCell)的唯一职责是接收数据、渲染展示,不应该持有Model层对象,更不应该自行处理网络请求、缓存管理的业务逻辑。 - 实际功能存在严重bug:你当前在每个
DataViewCell内部都初始化了独立的Model实例,各个实例的imagecache字典完全隔离,缓存根本不会共享,滑动列表时同一张图片会重复下载多次,完全达不到你做缓存的预期。另外Cell复用过程中,旧的网络请求回调会覆盖当前Cell的图片,会出现图片错乱的问题。
可采用的优化方案
第一步:调整缓存的存储逻辑
把图片缓存从Model类中抽离,做成全局共享的NSCache单例,保证全局只有一份缓存,所有图片加载逻辑都访问同一个缓存对象即可。
示例缓存单例写法:
class ImageCacheManager { static let shared = ImageCacheManager() private let cache = NSCache<NSString, UIImage>() private init() {} func getImage(for url: String) -> UIImage? { return cache.object(forKey: url as NSString) } func saveImage(_ image: UIImage, for url: String) { cache.setObject(image, forKey: url as NSString) } }
第二步:把图片加载逻辑从Cell中剥离
两种常用方案,都可以避免Cell感知Model层:
方案1:在ViewController层处理图片加载
在cellForRowAt方法中处理图片的缓存查询、网络请求,请求成功后给对应Cell的图片控件赋值,同时做好Cell复用的校验,避免图片错乱:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: Constants.ID, for: indexPath) as! DataViewCell let item = data[indexPath.row] // 先赋值文本 cell.dataName.text = item.strdata cell.dataImage.image = nil // 先清空复用的旧图片 guard let imgUrl = item.thumbnail else { return cell } // 查缓存 if let cachedImg = ImageCacheManager.shared.getImage(for: imgUrl) { cell.dataImage.image = cachedImg return cell } // 没有缓存就请求 guard let url = URL(string: imgUrl) else { return cell } URLSession.shared.dataTask(with: url) { [weak cell, weak self] data, _, error in guard let data = data, error == nil, let image = UIImage(data: data), let self = self else { return } // 存缓存 ImageCacheManager.shared.saveImage(image, for: imgUrl) DispatchQueue.main.async { // 校验当前Cell对应的url是否还是当前请求的url,避免复用错乱 if self.data[indexPath.row].thumbnail == imgUrl { cell?.dataImage.image = image } } }.resume() return cell }
对应的DataViewCell可以删掉model属性、图片加载逻辑,只保留基础的UI控件声明即可。
方案2:封装UIImageView扩展(更推荐)
把带缓存的图片加载逻辑封装成UIImageView的扩展,Cell只需要调用扩展方法,不需要感知任何业务逻辑,代码更简洁,复用性更高:
extension UIImageView { func setImage(with urlStr: String, placeholder: UIImage? = nil) { self.image = placeholder guard let url = URL(string: urlStr) else { return } // 查缓存 if let cachedImg = ImageCacheManager.shared.getImage(for: urlStr) { self.image = cachedImg return } // 发起请求 URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil, let image = UIImage(data: data) else { return } ImageCacheManager.shared.saveImage(image, for: urlStr) DispatchQueue.main.async { self?.image = image } }.resume() } }
调用的时候只需要在Cell的setCell方法里直接调用即可:
func setCell(_ data: DataItem) { self.dataName.text = data.strdata self.dataImage.setImage(with: data.thumbnail ?? "") }
内容的提问来源于stack exchange,提问作者mendokusai
相关产品推荐
相关产品推荐

