繁体   English   中英

在 View (Swift) 中传递模型

[英]Passing a model in View (Swift)

我正在创建一个基本的 iOS 应用程序,在其中我从包含图像的 API 获取一组数据(JSON)。 我正在使用UIViewController并有一个自定义UITableViewCell

为了停止发送网络请求,我创建了一个缓存来存储<url: UIImage> 问题是为了将图像添加到缓存中,我将模型(网络)类传递给自定义UITableViewCell (视图)。

我的理解是视图应该不知道模型。 这是 MVC 可以接受的设计还是有办法解决这个问题?

抽象代码如下(现在使用字典作为缓存。稍后将更改为NSCache )我还使用协议/委托在网络和视图控制器之间传递日期。

protocol ModelDelegate {
    
    func dataFetched(_ data:[DataItem])
}

//This is the model(networking) class
class Model {
    
    var delegate: ModelDelegate?
    
    var imagecache: [String: UIImage] = [:]
    
    func getData() {
        let url = URL(string: Constants.DATA_URL)
        
        guard url != nil else {
            print("Invalid URL!")
            return
        }
        
        let session = URLSession.shared.dataTask(with: url!) { data, response, error in
            
            if error != nil && data == nil {
                print("There was a data retriving error!")
                return
            }
            
            let decoder = JSONDecoder()
            do {
                let response = try decoder.decode(Data.self, from: data!)
                
                if response.data != nil {
                    
                    let sorteddata = response.data!.sorted(by: { $0.data < $1.data })
                    
                    
                    DispatchQueue.main.async {
                        self.delegate?.dataFetched(sorteddata)
                    }
                    
                }
                
            } catch  {
                print(error.localizedDescription)
            }
        }
        session.resume()
    }
}

//Custom UITableView Cell
class DataViewCell: UITableViewCell {

    
    @IBOutlet weak var dataImage: UIImageView!
    @IBOutlet weak var dataName: UILabel!
    
    var model = Model()
    var data: DataItem?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
     
    func setCell(_ data: DataItem) {
        self.data = data
        
        self.dataName.text = data.strdata
        
        guard self.data?.thumbnail != nil else {
            return
        }
        
        if let imageData = model.imagecache[self.data!.thumbnail] {
            print("using cache")
            DispatchQueue.main.async {
                self.DataImage.image = imageData
            }  
        }
        
        let url = URL(string: self.data!.thumbnail)
        
        let session = URLSession.shared.dataTask(with: url!) { data, response, error in
            
            if error == nil && data != nil {
                
                
                let image = UIImage(data: data!)
                self.model.imagecache[self.data!.thumbnail] = image
                DispatchQueue.main.async {
                    self.DataImage.image = image
                }
        
            }
        }
        
        session.resume()
    }
}

//View Controller

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, ModelDelegate {

    @IBOutlet weak var tableView: UITableView!
    
    var model = Model()
    var data = [DataItem]()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
        
        model.delegate = self
        
        model.getdata()
    }
    
    func dataFetched(_ data: [DataItem]) {
        self.data = data
        
        tableView.reloadData()
    }
    
    //TableView methods
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: Constants.ID, for: indexPath) as! DataViewCell
        let data = self.categories[indexPath.row]
        cell.setCell(data)
        
        return cell  
    }
}

单元格是一个视图,视图永远不应该与网络层或底层数据模型交互。 很难说你在做什么,因为你的代码没有编译,所以我做了一个例子:

首先,您有一个名为Model的类,它并不是真正的模型。 它是一个服务或数据提供者。 您的模型是DataItem

这是我的模型通常的样子:

struct DemoModel: Codable {
    let name: String
    let thumbnailURL: URL?
}

我通常有一个如下所示的服务层:

protocol DataProvider {
    func publisher(forURL url: URL) -> AnyPublisher<Data, Error>
}

struct DefaultDataProvider: DataProvider {
    func publisher(forURL url: URL) -> AnyPublisher<Data, Error> {
        URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }
}

struct MockDataProvider: DataProvider {
    let data: Data
    func publisher(forURL url: URL) -> AnyPublisher<Data, Error> {
        return Result.Publisher(.success(data))
            .eraseToAnyPublisher()
    }
}

MockDataProvider允许我在不进行网络调用的情况下注入虚拟数据进行测试。

然后我会有一个服务层,为我的 viewControllers 提供服务。 在这种情况下,我可以获取符合Decodable任何类型的对象,还可以获取具有缓存机制的图像。

struct DecodingService<Output: Decodable> {
    let dataProvider: DataProvider
    func publisher(forURL url: URL) -> AnyPublisher<Output, Error> {
        dataProvider
            .publisher(forURL: url)
            .decode(type: Output.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

struct CachedImageService {
    let dataProvider: DataProvider
    let cache = NSCache<NSString, UIImage>()

    func publisher(forURL url: URL) -> AnyPublisher<UIImage, Error> {
        let key = url.absoluteString as NSString

        if let image = cache.object(forKey: key) {
            print("cached")
            return Result.Publisher(.success(image))
                .eraseToAnyPublisher()
        }

        print("from web")
        return dataProvider
            .publisher(forURL: url)
            .compactMap(UIImage.init(data:))
            .handleEvents(receiveOutput: { image in
                self.cache.setObject(image, forKey: key)
            })
            .eraseToAnyPublisher()
    }
}

单元格应该保持很小,并且它们应该由您的视图控制器配置或填充:

class DemoCell: UITableViewCell {

    static let id = "\(DemoCell.self)"

    @IBOutlet weak var thumbImageView: UIImageView!
    @IBOutlet weak var nameLabel: UILabel!
}

在 MVC 中,视图控制器应该始终是进行网络调用的控制器。

var thumbService: CachedImageService?
var subscriptions = Set<AnyCancellable>()
var models: [DemoModel] = []

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(
        withIdentifier: DemoCell.id, for: indexPath) as! DemoCell
    let model = models[indexPath.row]

    cell.thumbImageView.image = UIImage(named: "placeholder")
    cell.nameLabel.text = model.name

    guard let url = model.thumbnailURL else { return cell }
    thumbService?
        .publisher(forURL: url)
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { _ in
        }, receiveValue: { thumb in
            guard indexPath == tableView.indexPath(for: cell) else { return }
            cell.thumbImageView.image = thumb
        })
        .store(in: &subscriptions)

    return cell
}

正如您在此处看到的,视图控制器进行网络调用,然后配置单元格。

我建议为您的网络层使用Combine ,因为它比单独使用URLSession 和回调等要干净得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM