Я создаю приложение, которое использует NSOperation для извлечения изображений из Flickr API в мои collectionViews. У меня есть два collectionViews, поэтому я делаю 2 выборки одновременно. Operation1 извлекает изображения из API, Operation 2 кэширует изображения, а Operation 3 загружает изображения в их collectionView.
Это я говорю mainViewController:
импорт UIKit
класс ShudderViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var places = ["1","2","3","4","5","6","7","8","9"]
var moviesImage = [#imageLiteral(resourceName: "HorrorMovie4"), #imageLiteral(resourceName: "HorrorMovie1"),#imageLiteral(resourceName: "HorrorMovie3"),#imageLiteral(resourceName: "HorrorMovie2"),#imageLiteral(resourceName: "HorrorMovie1"),]
let infinite = 100000
var collectionView2Photos = [Photo]() {
didSet {
print("2 Photo ID = \(collectionView2Photos[0].id)")
}
}
var collectionView3Photos = [Photo]() {
didSet {
print("3 Photo ID = \(collectionView3Photos[0].id)")
print("Collection view 3 got pics")
}
}
@IBOutlet weak var mainCollectionView: UICollectionView!
@IBOutlet weak var newlyAddedCollectionView: UICollectionView!
@IBOutlet weak var curatorsChoiseCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
mainCollectionView.layer.masksToBounds = false
mainCollectionView.delegate = self
mainCollectionView.dataSource = self
newlyAddedCollectionView.layer.masksToBounds = false
newlyAddedCollectionView.delegate = self
newlyAddedCollectionView.dataSource = self
curatorsChoiseCollectionView.layer.masksToBounds = false
curatorsChoiseCollectionView.delegate = self
curatorsChoiseCollectionView.dataSource = self
photoController.searchForFlicker(withSearchterm: "Jesus") { (sucess, photos) in
if sucess {
guard let photos = photos else {return}
DispatchQueue.main.async {
self.collectionView2Photos = photos
self.mainCollectionView.reloadData()
self.newlyAddedCollectionView.reloadData()
}
}
}
photoController.searchForFlicker(withSearchterm: "Basketball") { (sucess, photos) in
if sucess {
guard let photos = photos else {return}
DispatchQueue.main.async {
self.collectionView3Photos = photos
self.curatorsChoiseCollectionView.reloadData()
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
let midIndexPath = IndexPath(row: infinite / 2, section: 0)
mainCollectionView.scrollToItem(at: midIndexPath,
at: .centeredHorizontally,
animated: false)
}
// Make collection view cells fill as much available width as possible
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
var totalUsableWidth = collectionView.frame.width
let inset = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section)
totalUsableWidth -= inset.left + inset.right
let minWidth: CGFloat = 150.0
let numberOfItemsInOneRow = Int(totalUsableWidth / minWidth)
totalUsableWidth -= CGFloat(numberOfItemsInOneRow - 1) * flowLayout.minimumInteritemSpacing
let width = totalUsableWidth / CGFloat(numberOfItemsInOneRow)
return CGSize(width: width, height: width)
}
// Add margins to the left and right side
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 10.0, bottom: 0, right: 10.0)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.newlyAddedCollectionView {
//print(photoController.photoDetail.count)
return collectionView2Photos.count
} else if collectionView == curatorsChoiseCollectionView {
return collectionView3Photos.count
}
return infinite
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.mainCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewcell
cell.imageView.image = moviesImage[indexPath.row % moviesImage.count]
cell.layer.cornerRadius = 12
return cell
}
else if collectionView == self.newlyAddedCollectionView{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "newAddedCell", for: indexPath) as! newlyAddedCollectionViewCell
loadImage(forCell: cell, forItemAt: indexPath, arrayOfPhotos: collectionView2Photos, collectionView: newlyAddedCollectionView)
return cell
}
else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "curatorsCollectionViewCell", for: indexPath) as! CuratorsChoiceCollectionViewCell
loadImage(forCell: cell, forItemAt: indexPath, arrayOfPhotos: collectionView3Photos, collectionView: curatorsChoiseCollectionView)
return cell
}
}
private func loadImage(forCell cell: UICollectionViewCell, forItemAt indexPath: IndexPath, arrayOfPhotos: [Photo], collectionView: UICollectionView) {
if collectionView == curatorsChoiseCollectionView {
let cell = cell as! CuratorsChoiceCollectionViewCell
let photo = arrayOfPhotos[indexPath.item]
if let image = cache[photo.id]{
cell.imageView.image = image
}
else {
let op1 = FetchPhotoOperation(photoRef: photo)
let op2 = BlockOperation {
guard let image = op1.image else { return }
self.cache.cache(value: image, for: photo.id)
}
let op3 = BlockOperation {
guard let image = op1.image else { print("Something went wrong PEREZ");return }
if indexPath == collectionView.indexPath(for: cell) {
cell.imageView.image = image
}
else {
self.fetchRequests[photo.id]?.cancel()
}
}
op3.addDependency(op1)
op2.addDependency(op1)
OperationQueue.main.addOperation(op3)
photoFetchQueue.addOperations([op1, op2], waitUntilFinished: false)
fetchRequests[photo.id] = op1
}
} else if collectionView == newlyAddedCollectionView {
let cell = cell as! newlyAddedCollectionViewCell
let photo = arrayOfPhotos[indexPath.item]
if let image = cache[photo.id]{
cell.imageView.image = image
}
else {
let op1 = FetchPhotoOperation(photoRef: photo)
let op2 = BlockOperation {
guard let image = op1.image else { return }
self.cache.cache(value: image, for: photo.id)
}
let op3 = BlockOperation {
print("testing")
guard let image = op1.image else { print("Something went wrong PEREZ");return }
if indexPath == collectionView.indexPath(for: cell) {
cell.imageView.image = image
}
else {
self.fetchRequests[photo.id]?.cancel()
}
}
op3.addDependency(op1)
op2.addDependency(op1)
OperationQueue.main.addOperation(op3)
photoFetchQueue.addOperations([op1, op2], waitUntilFinished: false)
fetchRequests[photo.id] = op1
}
}
}
@IBOutlet weak var scaryImage: UIImageView!
private let photoController = PhotoController()
private var cache: Cache<String, UIImage> = Cache()
private var photoFetchQueue = OperationQueue()
private var fetchRequests: [String: FetchPhotoOperation] = [:] {
didSet{
print("Hello there")
}
}
}
Это мой кеш-файл:
class Cache<Key, Value> where Key: Hashable {
private var queue = DispatchQueue(label: "CSG.ShudderAppInterview.CacheSerialQueue")
private var cachedItems: [Key: Value] = [:]
subscript(_ key: Key) -> Value? {
get {
// waits for synchronous task to complete before going on to other tasks
return queue.sync { cachedItems[key] ?? nil }
}
}
func cache(value: Value, for key: Key) {
queue.async { self.cachedItems[key] = value }
}
}
и это моя операция FetchPhotos:
import Foundation
import UIKit
class ConcurrentOperation: Operation {
enum State: String {
case isReady, isExecuting, isFinished
}
private var _state = State.isReady
private let stateQueue = DispatchQueue(label: "CSG.ShudderAppInterview.ConcurrentOperationStateQueue")
var state: State {
get {
var result: State?
let queue = self.stateQueue
queue.sync {
result = _state
}
return result!
}
set {
let oldValue = state
willChangeValue(forKey: newValue.rawValue)
willChangeValue(forKey: oldValue.rawValue)
stateQueue.sync { self._state = newValue }
didChangeValue(forKey: oldValue.rawValue)
didChangeValue(forKey: newValue.rawValue)
}
}
override dynamic var isReady: Bool {
return super.isReady && state == .isReady
}
override dynamic var isExecuting: Bool {
return state == .isExecuting
}
override dynamic var isFinished: Bool {
return state == .isFinished
}
override var isAsynchronous: Bool {
return true
}
}
//NSOperation subclass
class FetchPhotoOperation : ConcurrentOperation {
init(photoRef: Photo) {
self.photo = photoRef
}
var photo: Photo
var image : UIImage?
private var dataTask: URLSessionDataTask?
override func start() {
state = .isExecuting
guard let url = URL(string: "https://farm\(photo.farm).staticflickr.com/\(photo.server)/\(photo.id)_\(photo.secret).jpg") else { return }
print(url)
dataTask = URLSession.shared.dataTask(with: url) { (data, _, error) in
defer { self.state = .isFinished }
if let error = error {
NSLog("Error retrieving image from url: \(error), Refer to your dataTast in FetchOperation")
return
}
guard let data = data else { return }
if let downloadedImage = UIImage(data: data) {
self.image = downloadedImage
}
}
dataTask?.resume()
}
override func cancel() {
dataTask?.cancel()
}
}