0

I have a cell and I'm using URLSessionTask inside of it for some images. When the cell scrolls off of the screen, in prepareForReuse I cancel the task and everything works fine. Because I'm doing several thing with the image once I get it from the task, I want to create a function for everything. The problem is I can't pass task: URLSessionDataTask? as a parameter because it is a let constant. I understand the error Cannot assign to value: 'task' is a 'let' constant, but I can't figure out how to get around it because I have to cancel the task once prepareForReuse runs?

func setImageUsingURLSessionTask(photoUrlStr: String, imageView: UIImageView, task: URLSessionDataTask?)

    if let cachedImage = imageCache.object(forKey: photoUrlStr as AnyObject) as? UIImage {
        
        let resizedImage = funcToResizeImage(cachedImage)

        imageView.image = resizedImage
        return
    }

    guard let url = URL(string: photoUrlStr) else { return }

    task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

        // eventually resize image, set it to the imageView, and save it to cache
    })
    task?.resume()
}

Here's the cell if it's not clear. Once cellForItem runs and myObject gets initialized, I pass the photoUrlStr, imageView, and task, to the function. If the cell is scrolled off screen in prepareForReuse the task is cancelled so the incorrect image never appears. This works 100% fine if I set the URLSessionTask inside the cell itself instead of inside a function.

class MyCell: UICollectionViewCell {

    lazy var photoImageView: UIImageView = {
        // ...
    }()

    var task: URLSessionTask?

    var myObject: MyObject? {
        didSet {
            
            guard let photoUrlStr = myObject?.photoUrlStr else { return }

            setImageUsingURLSessionTask(photoUrlStr: photoUrlStr, imageView: photoImageView, task: task)
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        
        task?.cancel()
        task = nil
        photoImageView.image = nil
    }
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • The function `setImageUsingURLSessionTask()` may just return a URLSession Task. Passing it a task as parameter makes no sense to me - it will not be used anyway. You really should improve the design and split the _three_ parts of the function explicitly: try load from cache, start URLSessionTask, handle result. – CouchDeveloper Mar 17 '21 at 11:00
  • You lost me. I pass it the task just to download the image if the image isn't in cache. The function doesn't return anything, where do you see a return statement in the function?. When I take the code outside of the function it works fine with no problems. If the image isn't in cache I run the task. The only thing I'm doing now is putting the code inside a function. I just tried the function with the answer from my question and it works fine. – Lance Samaria Mar 17 '21 at 11:12
  • Your function creates a new task value if the image is not in the cache. It _never uses_ the parameter `task` - which is an optional even. `inout` is not really appropriate here. You may return an optional URLSessionTask in your function instead: it will be `nil` if you didn't create a task, otherwise it returns the task that has been just created. – CouchDeveloper Mar 17 '21 at 11:29
  • How can it not use the parameter `task`? Inside the function, `task = URL...`, which task is it using if it's not using the parameter? There isn't anything inside the function that says `var task: URLSessionTask?` – Lance Samaria Mar 17 '21 at 11:32
  • Well, whatever _value_ parameter task has, will be _overwritten_ with the new value from the result of `URLSession.shared.dataTask()`. So, actually you never use the original value of parameter task. That is, you don't need it. What you want to do is, to create a value of type URLSessionTask then return it, and the caller assigns it the cell instance variable. You really don't need `inout` here - which works - but in a rather weird way as in: `var age: Int? = nil; func myAge(inout age: Int?) ` instead of just `func myAge() -> Int` – CouchDeveloper Mar 17 '21 at 11:44
  • What you're saying to do is what @sh_khan had wrote in his deleted answer, correct? – Lance Samaria Mar 17 '21 at 11:47
  • 1
    yes :) actually. The code will be much cleaner with this small change. – CouchDeveloper Mar 17 '21 at 11:47
  • lol, I wonder why he deleted his answer. When I first saw it I was going to accept it but he deleted it. I though maybe he did something wrong. I'm still lost with what you said here `whatever value parameter task has, will be overwritten with the new value from the result of URLSession.shared.dataTask()` because task is initially nil, it has no value until it gets initialized inside the function. At that point the `var task: URLSessionTask?` from the cell should now have the `URLSessionTask` from inside the function. There is something that I'm completely lost on. – Lance Samaria Mar 17 '21 at 11:51
  • from looking at his answer and what I'm doing in both situations the `var task: URLSessionTask?` from the cell get initialized with the `URLSessionTask from inside the function`. The only difference is his returns the `URLSessionTask` – Lance Samaria Mar 17 '21 at 11:54
  • What you need to take care of in the Cell member variable task is just that _before_ you assign a new task (which you got from your function), ensure that the Cell member variable task (if it is not nil) will be cancelled, then assign it the new task value. You already do this in `prepareForReuse()`. – CouchDeveloper Mar 17 '21 at 11:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230025/discussion-between-lance-samaria-and-couchdeveloper). – Lance Samaria Mar 17 '21 at 12:00
  • @Sh_Khan after reading the conversation above and thinking about it, I feel that my answer is just a quick fix for the problem, but your deleted solution is the right way to go. Could you retrieve your answer, as it should be the accepted one ? – πter Mar 17 '21 at 12:46

1 Answers1

1

You can pass your task as an inout parameter. A simplified version of your function would be:

func changeDataTask(inout task: URLSessionDataTask) {
    task = // You can change it
}

The variable what you pass to the function has to be var. You would call it like this:

var task = // Declare your initial datatask
changeDataTask(task: &task)
πter
  • 1,899
  • 2
  • 9
  • 23