Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-retaining multi-resource observers #154

Open
Reedyuk opened this issue Dec 9, 2016 · 6 comments
Open

Non-retaining multi-resource observers #154

Reedyuk opened this issue Dec 9, 2016 · 6 comments

Comments

@Reedyuk
Copy link
Contributor

Reedyuk commented Dec 9, 2016

Hi Paul,
I'm having a little trouble in adding an observer which is called upon All network requests made by a Siesta service object.
service.resource("*").addObserver(self)
Where self is adopting the resourceObserver protocol.

I get the observer callback when i add and remove the observer but it doesn't seem to be latching onto my network requests.

Thoughts? I'm sure its something simple.

@Reedyuk
Copy link
Contributor Author

Reedyuk commented Dec 9, 2016

Actually, just to add. When i set the actual full path, then the observer works fine.
So this maybe a feature request instead.
The ability to wildcard add observers.

@Reedyuk
Copy link
Contributor Author

Reedyuk commented Dec 9, 2016

I created a quick fix for my code for now:

extension Configuration {
    public mutating func listenToRequests() {
        decorateRequests {
            resource, request in
            request.onFailure { _ in
                if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
                    appDelegate.resourceChanged(resource, event: .error)
                }
            }
            request.onSuccess { _ in
                if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
                    appDelegate.resourceChanged(resource, event: .newData(.network))
                }
            }
            return request
        }
    }
}

Then to use it,

service.configure {
            $0.useNetworkActivityIndicator()
            $0.listenToRequests()
        }

@pcantrell
Copy link
Member

pcantrell commented Dec 12, 2016

There are many compelling reasons for this feature or something like it: automatic retry after reestablishing a lost connection, for example, or automatically polling resources while they’re in use.

It’s more complicated than just extending the domain of addObserver(…), however. Under Siesta’s memory rules, resources are retained as long as they’re observed — and if a resource is always observed, then it can never be deallocated.

We need a beast that’s slightly different from a ResourceObserver that can:

  • choose whether it wants to watch a resource as long as it’s in memory or only as long as it has observers, and
  • iterate over all resources it’s watching without actually retaining them.

That’s a big chunk of API design to bite off, so I’ve been deferring it. I’d prioritize it just after having a standard drop-in EntityCache implementation. In the meantime, your decorator approach is a good workaround.

@pcantrell pcantrell changed the title Wildcard for observer Non-retaining multi-resource observers Jan 10, 2017
@wildthink
Copy link

I've been looking at ways to cache some images in a way so that volatile views (observers) are likely, but not required, to find the image locally. This happens in the reuse (q.v.) of CollectionCells as the user scrolls back and forth; a cell scrolls out of view, gets reused, and the original request is dropped on the floor only to be re-requested when the users reverses the scrolling.

I'm thinking of creating a dedicated Service with an EntityCache that implements a "keep the most n recently used" (or such). This way a request for a particular image (URL) is likely to still be available if it is re-requested within a short time.

Thoughts?

@pcantrell
Copy link
Member

I've been looking at ways to cache some images in a way so that volatile views (observers) are likely, but not required, to find the image locally. This happens in the reuse (q.v.) of CollectionCells as the user scrolls back and forth; a cell scrolls out of view, gets reused, and the original request is dropped on the floor only to be re-requested when the users reverses the scrolling.

This is exactly what Siesta is designed for as it stands, and the GithubBrowser example project contains code that demonstrates exactly what you describe (with table views, even).

This issue is about making an observer that can keep watching a resource as long as it is in use by others, but without causing it to be retained forever. For example, such an observer could say, “As long as anyone is using a ‘current temperature’ resource, keep refreshing it.”

@howlingblast
Copy link

howlingblast commented Jun 26, 2018

Hi @pcantrell,

I would like to weigh in with another use case.

I'm currently trying to implement some kind of in-app debug/network monitor.

I'm trying to mimic something that sniffs on the network traffic (like Charles, https://www.charlesproxy.com does) but in a very Siesta-kind of way. I want to be able to track the used resources, responses and combine this with the data before transformation happens.

@Reedyuk's listenToRequests extension was the first step. It let's me track all used resources and hook into their requests. But combining this with a custom transformer is fruitless.

// This no real implementation, just snippets

struct NetworkResourceState {
    static var collected: [String: NetworkResourceState]

    let rawData: Any?
    let entity: Entity<Any>?
}


extension Configuration {
    
    public mutating func sniffRequests() {
        // I want to hook into the initial data before it passes the transformations,
        // so I get the "cleanest" data possible 
        pipeline[.rawData].add(NetworkSniffer())
        decorateRequests { resource, request in
            // here I can track the resources and hook in into their requests
            // I will save the needed data into memory using the resource's `absolutePath` as
            // the key
            var state = NetworkResourceState.collected[resource.url.absolutePath] ?? NetworkResourceState()
            NetworkResourceState.collected[resource.url.absolutePath] = state
            request.onCompletion { _ in 
                switch responseInfo.response {
                case .success(let entity):
                    state?.latestData = entity
                case .failure(_): 
                    break
            } 
         }
     }
}

struct NetworkSniffer: ResponseTransformer {

    func process(_ response: Siesta.Response) -> Siesta.Response {
        switch response {
        case .success(let entity):
            // While I can easily get the response data in a very basic state here
            // I have no chance to reference it to the request (OR resource) that 
            // ended in this response
            ...
        case .failure(let error):
            break
        }
        return response
    }
}

Now if the response would e.g. include the absolute path of the initial resource/request I would be able to link these two steps together. But as it stand Siesta is too closed to be able to understand where things are coming from.

I totally understand the reasoning and I believe it's very good practice to have all these little parts be independent from each other as possible, but maybe there is a way to piggy-pack some introspection information in the chains so one could connect the dots easier?

PS: I could try to merge something like https://github.com/Kofktu/Sniffer and Siesta, but that's not really what I'm looking at (at least not right now). I would like to keep the flow in context of Siesta: Resources -> Responses, with the small issue to get to the rawest form of data as possible before Siesta's transformation chain does it's magic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants