{"_id":"5673029345185e0d008bd1c2","user":"5654ea8be0d82b0d00ab5747","__v":19,"category":{"_id":"5673018a06b19d0d0010691b","pages":["567302825316790d001f8d6a","5673029345185e0d008bd1c2","5676d59a12ea5a2300726aba"],"project":"5672fc989996590d00c22c65","__v":3,"version":"5672fc989996590d00c22c68","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2015-12-17T18:40:10.078Z","from_sync":false,"order":4,"slug":"intermediate","title":"Advanced"},"version":{"_id":"5672fc989996590d00c22c68","__v":7,"project":"5672fc989996590d00c22c65","createdAt":"2015-12-17T18:19:04.699Z","releaseDate":"2015-12-17T18:19:04.699Z","categories":["5672fc999996590d00c22c69","567301169d4c060d009da8b3","56730183547bee0d00997d1a","5673018a06b19d0d0010691b","567301b53054630d00fe9288","567400638565060d009a86fb","5674017bf79ca90d00ad2f67"],"is_deprecated":false,"is_hidden":false,"is_beta":false,"is_stable":true,"codename":"","version_clean":"2.5.0","version":"2.5.0"},"project":"5672fc989996590d00c22c65","updates":[],"next":{"pages":[],"description":""},"createdAt":"2015-12-17T18:44:35.406Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"results":{"codes":[]},"settings":"","auth":"required","params":[],"url":""},"isReference":false,"order":4,"body":"Operations makes it easy to decompose significant *work* into smaller chunks of *work* which can be combined together. This is always a good architectural practice, as it will make each component have reduced impact and increased testability. However, it can be unwieldy and diminish code re-use opportunities.\n\nTherefore we can create more abstract notions of *work* with `GroupOperation`. Earlier in the guide we showed how to use the class directly; whereas here we will focus on subclassing `GroupOperation`.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"The initializer\"\n}\n[/block]\nIf all the child operations are known at compile time they can be configured during initialization. Configuration would be things such as setting dependencies, observers, conditions.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"class LoginOperation: GroupOperation {\\n\\n    class PersistLoginInfo: Operation { /* etc */ }\\n\\t\\tclass LoginSessionTask: NSURLSessionTask { /* etc */ }\\n    \\n    init(credentials: Credentials /* etc, inject known dependencies */) {\\n\\n        // Create the child operations\\n        let persist = PersistLoginInfo(credentials)\\n        let login = URLSessionTaskOperation(task: LoginSessionTask(credentials))\\n        \\n        // Do any configuration or setup\\n        persist.addDependency(login)\\n        \\n        // Call the super initializer with the operations\\n        super.init(operations: [persist, login])\\n        \\n        // Configure any properties, such as name.\\n        name = \\\"Login\\\"\\n        \\n        // Add observers, conditions etc to the group\\n        addObserver(NetworkObserver())\\n        addCondition(MutualExclusive<LoginOperation>())\\n    }\\n}\",\n      \"language\": \"swift\",\n      \"name\": \"group configured at initialization\"\n    }\n  ]\n}\n[/block]\nThe initialization strategy shown above is relatively simple, but shows some good practices. Creating and configuring child operations before calling the `GroupOperation` initializer reduces the complexity and increases the testability and readability of the class. Adding observers and conditions to the group inside its initializer sets the default and expected behavior which makes using the class easier. Remember that these can always be nullified by using `ComposedOperation`. \n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Adding child operations later\"\n}\n[/block]\nIn some cases, the results of one operation are needed by a subsequent operation. We covered techniques to achieve this in [Injecting Results](doc:injecting-results) which still apply here, but don't cover a critical scenario which is branching. A common usage might be to perform either operation `Bar` or `Baz` depending on `Foo`, which cannot be setup during initialization. \n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"class FooBarBazOperation: GroupOperation {\\n    /* etc */\\n\\n    override func operationDidFinish(operation: NSOperation, withErrors errors: [ErrorType]) {\\n        if errors.isEmpty && !cancelled, let foo = operation as? FooOperation {\\n           if foo.doBaz {\\n              addOperation(BazOperation())\\n           }\\n           else {\\n              addOperation(BarOperation())\\n           }\\n        }\\n    }\\n}\",\n      \"language\": \"swift\",\n      \"name\": \"operation did finish\"\n    }\n  ]\n}\n[/block]\nThe above function does nothing in `GroupOperation` and exists purely to allow subclasses to perform actions when each the child operation finishes. Therefore a standard operation should\n\n1. Inspect and handle any errors\n2. Test the received operation to check that it is the expected instance. For example, optionally cast it to the expected type, and possible check if it equal to a stored property of the group.\n3. Call `addOperation` or `addOperations` to add more operations to the queue.\n\nUsing this technique the group will keep executing and only finish until all children, including ones added after the group started, have finished.\n[block:api-header]\n{\n  \"type\": \"basic\",\n  \"title\": \"Cancelling\"\n}\n[/block]\n`GroupOperation` itself will already handle being cancelled correctly. However, in some cases, such as if an `NSOperation` is injected into the group at initialization, it is necessary to cancel the group if one of the children is cancelled. We can do this via a `CancelledObserver`\n\n\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"class LoginSessionTask: NSURLSessionTask { /* etc */ }\\n\\nclass LoginOperation: GroupOperation {\\n\\n    class PersistLoginInfo: Operation { /* etc */ }\\n    \\n    let task: URLSessionTaskOperation\\n    \\n    init(credentials: Credentials, task: URLSessionTaskOperation) {\\n\\t\\t\\t\\tself.task = task\\n        \\n        // Create the child operations\\n        let persist = PersistLoginInfo(credentials)\\n\\n        // Do any configuration or setup\\n        persist.addDependency(task)\\n\\n        // Call the super initializer with the operations\\n        super.init(operations: [persist, login])\\n        \\n        // Configure any properties, such as name.\\n        name = \\\"Login\\\"\\n        \\n        // Add observers, conditions etc to the group\\n        addObserver(NetworkObserver())\\n        addCondition(MutualExclusive<LoginOperation>())\\n    }\\n    \\n    override func execute() {\\n        task.addObserver(CancelledObserver { [weak self] _ in\\n            self?.cancel()\\n        })        \\n        super.execute()\\n    }\\n}\",\n      \"language\": \"swift\",\n      \"name\": \"cancellable child\"\n    }\n  ]\n}\n[/block]\nThe above example is a modified from the original `LoginOperation` example, in that the `URLSessionTask` is injected to the group. This might be preferably to decouple the networking task, possibly to increase testability.\n\nIn this situation, it could be possible for the injected operation to be cancelled from outside the group, but logically that should cause the entire group to be cancelled. Therefore the group must observe this event. Although this could be configured after `super.init` is called, sometimes it is necessary to perform such configuration (which references `self`) after the initializer has finished. \n\nFor these situations, override `execute` but *always* call `super.execute()`. This is because the `GroupOperation` has critical functionality in its `execute` implementation (such as starting the queue).","excerpt":"Encapsulate operations into a group","slug":"groups","type":"basic","title":"Groups"}

Groups

Encapsulate operations into a group

Operations makes it easy to decompose significant *work* into smaller chunks of *work* which can be combined together. This is always a good architectural practice, as it will make each component have reduced impact and increased testability. However, it can be unwieldy and diminish code re-use opportunities. Therefore we can create more abstract notions of *work* with `GroupOperation`. Earlier in the guide we showed how to use the class directly; whereas here we will focus on subclassing `GroupOperation`. [block:api-header] { "type": "basic", "title": "The initializer" } [/block] If all the child operations are known at compile time they can be configured during initialization. Configuration would be things such as setting dependencies, observers, conditions. [block:code] { "codes": [ { "code": "class LoginOperation: GroupOperation {\n\n class PersistLoginInfo: Operation { /* etc */ }\n\t\tclass LoginSessionTask: NSURLSessionTask { /* etc */ }\n \n init(credentials: Credentials /* etc, inject known dependencies */) {\n\n // Create the child operations\n let persist = PersistLoginInfo(credentials)\n let login = URLSessionTaskOperation(task: LoginSessionTask(credentials))\n \n // Do any configuration or setup\n persist.addDependency(login)\n \n // Call the super initializer with the operations\n super.init(operations: [persist, login])\n \n // Configure any properties, such as name.\n name = \"Login\"\n \n // Add observers, conditions etc to the group\n addObserver(NetworkObserver())\n addCondition(MutualExclusive<LoginOperation>())\n }\n}", "language": "swift", "name": "group configured at initialization" } ] } [/block] The initialization strategy shown above is relatively simple, but shows some good practices. Creating and configuring child operations before calling the `GroupOperation` initializer reduces the complexity and increases the testability and readability of the class. Adding observers and conditions to the group inside its initializer sets the default and expected behavior which makes using the class easier. Remember that these can always be nullified by using `ComposedOperation`. [block:api-header] { "type": "basic", "title": "Adding child operations later" } [/block] In some cases, the results of one operation are needed by a subsequent operation. We covered techniques to achieve this in [Injecting Results](doc:injecting-results) which still apply here, but don't cover a critical scenario which is branching. A common usage might be to perform either operation `Bar` or `Baz` depending on `Foo`, which cannot be setup during initialization. [block:code] { "codes": [ { "code": "class FooBarBazOperation: GroupOperation {\n /* etc */\n\n override func operationDidFinish(operation: NSOperation, withErrors errors: [ErrorType]) {\n if errors.isEmpty && !cancelled, let foo = operation as? FooOperation {\n if foo.doBaz {\n addOperation(BazOperation())\n }\n else {\n addOperation(BarOperation())\n }\n }\n }\n}", "language": "swift", "name": "operation did finish" } ] } [/block] The above function does nothing in `GroupOperation` and exists purely to allow subclasses to perform actions when each the child operation finishes. Therefore a standard operation should 1. Inspect and handle any errors 2. Test the received operation to check that it is the expected instance. For example, optionally cast it to the expected type, and possible check if it equal to a stored property of the group. 3. Call `addOperation` or `addOperations` to add more operations to the queue. Using this technique the group will keep executing and only finish until all children, including ones added after the group started, have finished. [block:api-header] { "type": "basic", "title": "Cancelling" } [/block] `GroupOperation` itself will already handle being cancelled correctly. However, in some cases, such as if an `NSOperation` is injected into the group at initialization, it is necessary to cancel the group if one of the children is cancelled. We can do this via a `CancelledObserver` [block:code] { "codes": [ { "code": "class LoginSessionTask: NSURLSessionTask { /* etc */ }\n\nclass LoginOperation: GroupOperation {\n\n class PersistLoginInfo: Operation { /* etc */ }\n \n let task: URLSessionTaskOperation\n \n init(credentials: Credentials, task: URLSessionTaskOperation) {\n\t\t\t\tself.task = task\n \n // Create the child operations\n let persist = PersistLoginInfo(credentials)\n\n // Do any configuration or setup\n persist.addDependency(task)\n\n // Call the super initializer with the operations\n super.init(operations: [persist, login])\n \n // Configure any properties, such as name.\n name = \"Login\"\n \n // Add observers, conditions etc to the group\n addObserver(NetworkObserver())\n addCondition(MutualExclusive<LoginOperation>())\n }\n \n override func execute() {\n task.addObserver(CancelledObserver { [weak self] _ in\n self?.cancel()\n }) \n super.execute()\n }\n}", "language": "swift", "name": "cancellable child" } ] } [/block] The above example is a modified from the original `LoginOperation` example, in that the `URLSessionTask` is injected to the group. This might be preferably to decouple the networking task, possibly to increase testability. In this situation, it could be possible for the injected operation to be cancelled from outside the group, but logically that should cause the entire group to be cancelled. Therefore the group must observe this event. Although this could be configured after `super.init` is called, sometimes it is necessary to perform such configuration (which references `self`) after the initializer has finished. For these situations, override `execute` but *always* call `super.execute()`. This is because the `GroupOperation` has critical functionality in its `execute` implementation (such as starting the queue).