{"action":"created","issue":{"url":"https://api.github.com/repos/danthorpe/Operations/issues/42","labels_url":"https://api.github.com/repos/danthorpe/Operations/issues/42/labels{/name}","comments_url":"https://api.github.com/repos/danthorpe/Operations/issues/42/comments","events_url":"https://api.github.com/repos/danthorpe/Operations/issues/42/events","html_url":"https://github.com/danthorpe/Operations/issues/42","id":100194620,"number":42,"title":"Using GroupOperation","user":{"login":"jshier","id":51020,"avatar_url":"https://avatars.githubusercontent.com/u/51020?v=3","gravatar_id":"","url":"https://api.github.com/users/jshier","html_url":"https://github.com/jshier","followers_url":"https://api.github.com/users/jshier/followers","following_url":"https://api.github.com/users/jshier/following{/other_user}","gists_url":"https://api.github.com/users/jshier/gists{/gist_id}","starred_url":"https://api.github.com/users/jshier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jshier/subscriptions","organizations_url":"https://api.github.com/users/jshier/orgs","repos_url":"https://api.github.com/users/jshier/repos","events_url":"https://api.github.com/users/jshier/events{/privacy}","received_events_url":"https://api.github.com/users/jshier/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/danthorpe/Operations/labels/example","name":"example","color":"fbca04"},{"url":"https://api.github.com/repos/danthorpe/Operations/labels/help%20wanted","name":"help wanted","color":"159818"},{"url":"https://api.github.com/repos/danthorpe/Operations/labels/question","name":"question","color":"cc317c"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":11,"created_at":"2015-08-11T00:04:11Z","updated_at":"2015-08-12T09:05:22Z","closed_at":null,"body":"I'm having trouble understanding how to use `GroupOperation`. I have a networking layer (Alamofire based) that I've wrapped in a `NetworkOperation` (`Operation` subclass). I want to create `GroupOperation` subclasses that allow me to encapsulate these `NetworkOperation`s along with some optional operations that should be done if the initial network request fails with a particular error (user token expired). I must be doing something wrong though, as I keep hitting the \"Cannot add new operations to a group after the group has completed.\" assertion, likely because I'm losing the race between the `GroupOperation` automatically finishing and my operations starting the next one in the sequence in the completion block. Perhaps you can give me a better idea. Here's the code for my `StatusOperation` I'm building as a prototype for the other `GroupOperation`s.\r\n```swift\r\nclass StatusOperation : InterruptibleNetworkOperation {\r\n    let completionHandler: (Status?, Error?) -> Void\r\n    let fetchFromAllServers: Bool\r\n    \r\n    init(account: Account, fetchFromAllServers: Bool, completionHandler: (Status?, Error?) -> Void) {\r\n        self.fetchFromAllServers = fetchFromAllServers\r\n        self.completionHandler = completionHandler\r\n        super.init(operations: [])\r\n        \r\n        if #available(iOS 8.0, *) {\r\n            name = \"Account Status\"\r\n        }\r\n        \r\n        let statusOperation = statusOperationWithAccount(account, fetchFromAllServers: fetchFromAllServers, completionHandler: completionHandler)\r\n        \r\n        addOperation(statusOperation)\r\n    }\r\n    \r\n    func statusOperationWithAccount(account: Account, fetchFromAllServers: Bool, completionHandler: (Status?, Error?) -> Void) -> NetworkOperation {\r\n        return NetworkOperation(request: Manager.manager.request(Router.Status(account: account, fetchFromAllServers: fetchFromAllServers))) { (status: Status?, error: Error?) -> Void in\r\n            if let error = error where error.isExpiredUserTokenError {\r\n                self.handleExpiredUserTokenError()\r\n            }\r\n            else {\r\n                self.completionHandler(status, error)\r\n                self.emptyOperation.finish()\r\n            }\r\n        }\r\n    }\r\n    \r\n    override func successOperationWithAccount(account: Account) -> Operation {\r\n        return statusOperationWithAccount(account, fetchFromAllServers: fetchFromAllServers, completionHandler: completionHandler)\r\n    }\r\n}\r\n```\r\nAnd here's `InterruptibleNetworkOperation`:\r\n```swift\r\nclass InterruptibleNetworkOperation : GroupOperation {\r\n    let emptyOperation = EmptyOperation()\r\n    \r\n    override init(operations: [NSOperation]) {\r\n        super.init(operations: operations + [emptyOperation])\r\n    }\r\n    \r\n    func handleInvalidTokenError() {\r\n        addOperation(AccountRefreshOperation(completionHandler: { (account) in\r\n            guard let account = account else {\r\n                return\r\n            }\r\n            \r\n            log(\"*** Issuing second call. ***\")\r\n            self.addOperation(self.successOperationWithAccount(account))\r\n        }))\r\n    }\r\n    \r\n    func successOperationWithAccount(account: Account) -> Operation {\r\n\t\t// Should always be overridden.\r\n        return BlockOperation(mainQueueBlock: {\r\n            self.emptyOperation.finish()\r\n        })\r\n    }\r\n}\r\n```\r\nThe assertion fails when I try to run `self.addOperation(self.successOperationWithAccount(account))` in `InterruptibleNetworkOperation`, presumably because the `AccountRefreshOperation` (sends an `NSNotification` to a view controller to login, receives notifications about whether the user logged in and returns the account object or nil) has completed but the next operation hasn't been added yet, as the completion handler is still executing. As you can see, I tried to work around the issue by having an `EmptyOperation` (just overrides `execute()` with an empty body) which I could `finish()` manually but it doesn't seem to have done the trick. \r\n\r\nSo the question is, how can I structure this logic using the Operations library? I am using the Swift 2 branch but I'm perfectly happy to pull in code from the development branch. \r\n\r\nAlso, any general suggestions for passing data between operations without having to resort to custom completion handlers all the time?"},"comment":{"url":"https://api.github.com/repos/danthorpe/Operations/issues/comments/130224698","html_url":"https://github.com/danthorpe/Operations/issues/42#issuecomment-130224698","issue_url":"https://api.github.com/repos/danthorpe/Operations/issues/42","id":130224698,"user":{"login":"danthorpe","id":309420,"avatar_url":"https://avatars.githubusercontent.com/u/309420?v=3","gravatar_id":"","url":"https://api.github.com/users/danthorpe","html_url":"https://github.com/danthorpe","followers_url":"https://api.github.com/users/danthorpe/followers","following_url":"https://api.github.com/users/danthorpe/following{/other_user}","gists_url":"https://api.github.com/users/danthorpe/gists{/gist_id}","starred_url":"https://api.github.com/users/danthorpe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danthorpe/subscriptions","organizations_url":"https://api.github.com/users/danthorpe/orgs","repos_url":"https://api.github.com/users/danthorpe/repos","events_url":"https://api.github.com/users/danthorpe/events{/privacy}","received_events_url":"https://api.github.com/users/danthorpe/received_events","type":"User","site_admin":false},"created_at":"2015-08-12T09:05:22Z","updated_at":"2015-08-12T09:05:22Z","body":"Err, well I don't really like there is an error that isn't an error? But I also didn't quite follow where the `RefreshCompletionState` comes from? It looks as though like your network layer mixes authentication/authorization state with errors so there isn't much else you can do but mix your control logic with your errors too.\r\n\r\nWriting this framework it's been quite instructive to see how Apple's APIs deal with authorisation, and authorisation state. For Location, Photos, Contacts, AddressBook, CloudKit, HealthKit, PassKit, Notifications - they all have subtle differences, but all of them have bespoke enums to indicate the \"authorization\" status, e.g. allowed, denied, restricted. In the case of a network layer, I think you want the same thing, an enum to indicate the authorization, with associated values representing credentials (accounts, tokens etc). You've said that you don't have any control over the network backend - this is often the case, but in my experience, it can help to isolate the client from architectural decisions made by the backend. So, maybe you should think about writing your own bit of middleware that sits in the client and mediates between your Operations and the Request type."}}