diff --git a/ADVOperation.xcodeproj/project.pbxproj b/ADVOperation.xcodeproj/project.pbxproj index e3c0f58..98d8856 100644 --- a/ADVOperation.xcodeproj/project.pbxproj +++ b/ADVOperation.xcodeproj/project.pbxproj @@ -7,43 +7,90 @@ objects = { /* Begin PBXBuildFile section */ + 134930FC7911FA67B2B4D7DF /* OperationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A84EC920C5908B9EBA0 /* OperationErrors.swift */; }; + 134931087A4BE6A019937AB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 134932E651583716948CE833 /* Assets.xcassets */; }; + 134931411BBF6370A978DABC /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134938E6D96135B1DBE590FA /* OperationQueue.swift */; }; + 13493201C21925B5246A0246 /* NSOperation+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134939BD12D892DAF26E1F5D /* NSOperation+Operations.swift */; }; + 134932BF46092E111600482B /* NegatedCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493BA2D24FE404BC15CBF8 /* NegatedCondition.swift */; }; + 13493338E30D6F010F759FD2 /* SilentCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493BAA7B983B2A9DD70153 /* SilentCondition.swift */; }; + 134933B7FB906B44B1607B54 /* NoCancelledDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349381A537AEBCEB914ECBD /* NoCancelledDependencies.swift */; }; + 1349341FFC87A62ED60EE2EF /* OperationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493D60F708E7BFD326F144 /* OperationObserver.swift */; }; + 1349346F044013C41928C1FC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13493049D3DD2461C2FD577D /* Main.storyboard */; }; + 134935195F8C1F85F4302B2C /* PassbookCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134937EC6979C8C804E07AFA /* PassbookCondition.swift */; }; + 1349360903CEC2ACDE4CBC91 /* UIUserNotifications+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493011AE350750A6134743 /* UIUserNotifications+Operations.swift */; }; + 1349360E87176D613F47500D /* CloudCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932F050B4568B14C8F2D3 /* CloudCondition.swift */; }; + 13493711793B138D03F2D29A /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932428BA712B0F210BAA5 /* ExclusivityController.swift */; }; + 1349373A64C53B6C57E57A49 /* Dictionary+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493ECC76B568D7691439A3 /* Dictionary+Operations.swift */; }; + 1349373AE6F86D1C8BC8FB3A /* RemoteNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932EF0A14315328EC6A3A /* RemoteNotificationCondition.swift */; }; + 1349375932A0DA51708ACCEE /* PhotosCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A3DA8A5946B70A14674 /* PhotosCondition.swift */; }; + 134938AACE32347FA50E1C6F /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A18E225102B2BB5F75C /* LocationOperation.swift */; }; + 134938EC0526BD7445A952EF /* OperationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932126ECD95BB92B6A6E5 /* OperationCondition.swift */; }; + 13493A38C8C16BE598E04969 /* HealthCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493FC92789F5097A896A73 /* HealthCondition.swift */; }; + 13493A98CA8E7A43C7FC23A9 /* ReachabilityCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349301BB36EA46A258584E3 /* ReachabilityCondition.swift */; }; + 13493AAF2E22246D33908905 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A5F6B2DD167BD0C5DF9 /* AppDelegate.swift */; }; + 13493ACEE507EF3D8B2A3676 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134936FFFC75FF2FD1D19228 /* ViewController.swift */; }; + 13493AE54263F87E42D5B799 /* BlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493F87EE5CB20941A934F2 /* BlockOperation.swift */; }; + 13493B409710FCD8B3596BD6 /* CalendarCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493ED11D23BAEFDCC5146A /* CalendarCondition.swift */; }; + 13493BDEBF2CDEED6AD2028A /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493B673624213866B375D7 /* Operation.swift */; }; + 13493BEBAA8A2614FBCDBF0E /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493B097F3E57A358999B78 /* URLSessionTaskOperation.swift */; }; + 13493C5D50A6087A92FDD508 /* BlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134938B6B589E03B1377C6FD /* BlockObserver.swift */; }; + 13493C5FA625EC02B3D0A7A8 /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349349EF54CDBAC63C60197 /* MutuallyExclusive.swift */; }; + 13493CB2964452FAEEE4E28B /* DelayOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493D01646023977702042A /* DelayOperation.swift */; }; + 13493DC402D9C1782CF671E2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1349319B6E547AABDD1F5BCC /* LaunchScreen.storyboard */; }; + 13493DF93070CB84CD5A58B9 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932D5FE660F99325CD4C0 /* GroupOperation.swift */; }; + 13493E6507EEA35308B23182 /* UserNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A3B178B50C93EB4A857 /* UserNotificationCondition.swift */; }; + 13493ECEA49AE331A40DA484 /* LocationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A642DCBC93D7AFCF84A /* LocationCondition.swift */; }; + 13493ED6E7A06C6B9DCE6ACD /* TimeoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493EDCE9202ADB66218B97 /* TimeoutObserver.swift */; }; + 13493FCD68D71F2EB0BFD4D9 /* CKContainer+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493405578740A4F865B7EC /* CKContainer+Operations.swift */; }; + 3F8D74A01B4BC06D0049FBD0 /* HTTPBinNetworkOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134931D4EAC34EED6631C161 /* HTTPBinNetworkOperation.swift */; }; + 3FBF72F41B4BBA7A00914526 /* ADVOperation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA82155D1B2F23FA00A04622 /* ADVOperation.framework */; }; + 3FBF72F51B4BBA7A00914526 /* ADVOperation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AA82155D1B2F23FA00A04622 /* ADVOperation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3FBF72F91B4BBB4C00914526 /* CalendarCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493ED11D23BAEFDCC5146A /* CalendarCondition.swift */; }; + 3FBF72FA1B4BBB4C00914526 /* CloudCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932F050B4568B14C8F2D3 /* CloudCondition.swift */; }; + 3FBF72FB1B4BBB4C00914526 /* HealthCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493FC92789F5097A896A73 /* HealthCondition.swift */; }; + 3FBF72FC1B4BBB4C00914526 /* LocationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A642DCBC93D7AFCF84A /* LocationCondition.swift */; }; + 3FBF72FD1B4BBB4C00914526 /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349349EF54CDBAC63C60197 /* MutuallyExclusive.swift */; }; + 3FBF72FE1B4BBB4C00914526 /* NegatedCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493BA2D24FE404BC15CBF8 /* NegatedCondition.swift */; }; + 3FBF72FF1B4BBB4C00914526 /* NoCancelledDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349381A537AEBCEB914ECBD /* NoCancelledDependencies.swift */; }; + 3FBF73001B4BBB4C00914526 /* OperationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932126ECD95BB92B6A6E5 /* OperationCondition.swift */; }; + 3FBF73011B4BBB4C00914526 /* OperationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A84EC920C5908B9EBA0 /* OperationErrors.swift */; }; + 3FBF73021B4BBB4C00914526 /* PassbookCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134937EC6979C8C804E07AFA /* PassbookCondition.swift */; }; + 3FBF73031B4BBB4C00914526 /* PhotosCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A3DA8A5946B70A14674 /* PhotosCondition.swift */; }; + 3FBF73041B4BBB4C00914526 /* ReachabilityCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1349301BB36EA46A258584E3 /* ReachabilityCondition.swift */; }; + 3FBF73051B4BBB4C00914526 /* RemoteNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932EF0A14315328EC6A3A /* RemoteNotificationCondition.swift */; }; + 3FBF73061B4BBB4C00914526 /* SilentCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493BAA7B983B2A9DD70153 /* SilentCondition.swift */; }; + 3FBF73071B4BBB4C00914526 /* UserNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A3B178B50C93EB4A857 /* UserNotificationCondition.swift */; }; + 3FBF73081B4BBB5100914526 /* CKContainer+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493405578740A4F865B7EC /* CKContainer+Operations.swift */; }; + 3FBF73091B4BBB5100914526 /* Dictionary+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493ECC76B568D7691439A3 /* Dictionary+Operations.swift */; }; + 3FBF730A1B4BBB5100914526 /* NSOperation+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134939BD12D892DAF26E1F5D /* NSOperation+Operations.swift */; }; + 3FBF730B1B4BBB5100914526 /* UIUserNotifications+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493011AE350750A6134743 /* UIUserNotifications+Operations.swift */; }; + 3FBF730F1B4BBB5900914526 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493F5C1CF7D0460544720C /* NetworkObserver.swift */; }; + 3FBF73101B4BBB5900914526 /* BlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134938B6B589E03B1377C6FD /* BlockObserver.swift */; }; + 3FBF73111B4BBB5900914526 /* OperationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493D60F708E7BFD326F144 /* OperationObserver.swift */; }; + 3FBF73121B4BBB5900914526 /* TimeoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493EDCE9202ADB66218B97 /* TimeoutObserver.swift */; }; + 3FBF73131B4BBB5900914526 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493F5C1CF7D0460544720C /* NetworkObserver.swift */; }; + 3FBF73141B4BBB5E00914526 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932428BA712B0F210BAA5 /* ExclusivityController.swift */; }; + 3FBF73151B4BBB5E00914526 /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134938E6D96135B1DBE590FA /* OperationQueue.swift */; }; + 3FBF731C1B4BBB6300914526 /* BlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493F87EE5CB20941A934F2 /* BlockOperation.swift */; }; + 3FBF731D1B4BBB6300914526 /* DelayOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493D01646023977702042A /* DelayOperation.swift */; }; + 3FBF731E1B4BBB6300914526 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 134932D5FE660F99325CD4C0 /* GroupOperation.swift */; }; + 3FBF731F1B4BBB6300914526 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493A18E225102B2BB5F75C /* LocationOperation.swift */; }; + 3FBF73201B4BBB6300914526 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493B673624213866B375D7 /* Operation.swift */; }; + 3FBF73211B4BBB6300914526 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493B097F3E57A358999B78 /* URLSessionTaskOperation.swift */; }; + 3FBF73221B4BBB6300914526 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493858DE7778C37EADB241 /* AlertOperation.swift */; }; + 3FBF73231B4BBB6300914526 /* AlertOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13493858DE7778C37EADB241 /* AlertOperation.swift */; }; AA8215681B2F23FA00A04622 /* ADVOperation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA82155D1B2F23FA00A04622 /* ADVOperation.framework */; }; - AA8215851B2F260200A04622 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AA8215831B2F260200A04622 /* Info.plist */; }; - AA8215891B2F417E00A04622 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = AA8215881B2F417E00A04622 /* Info.plist */; }; - AA8215AA1B2F422C00A04622 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82158C1B2F422C00A04622 /* GroupOperation.swift */; }; - AA8215AB1B2F422C00A04622 /* BlockOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82158D1B2F422C00A04622 /* BlockOperation.swift */; }; - AA8215AC1B2F422C00A04622 /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82158E1B2F422C00A04622 /* OperationQueue.swift */; }; - AA8215AD1B2F422C00A04622 /* BlockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82158F1B2F422C00A04622 /* BlockObserver.swift */; }; - AA8215AE1B2F422C00A04622 /* CalendarCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215901B2F422C00A04622 /* CalendarCondition.swift */; }; - AA8215AF1B2F422C00A04622 /* CKContainer+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215911B2F422C00A04622 /* CKContainer+Operations.swift */; }; - AA8215B01B2F422C00A04622 /* CloudCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215921B2F422C00A04622 /* CloudCondition.swift */; }; - AA8215B11B2F422C00A04622 /* DelayOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215931B2F422C00A04622 /* DelayOperation.swift */; }; - AA8215B21B2F422C00A04622 /* Dictionary+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215941B2F422C00A04622 /* Dictionary+Operations.swift */; }; - AA8215B31B2F422C00A04622 /* ExclusivityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215951B2F422C00A04622 /* ExclusivityController.swift */; }; - AA8215B41B2F422C00A04622 /* HealthCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215961B2F422C00A04622 /* HealthCondition.swift */; }; - AA8215B51B2F422C00A04622 /* LocationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215971B2F422C00A04622 /* LocationCondition.swift */; }; - AA8215B61B2F422C00A04622 /* LocationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215981B2F422C00A04622 /* LocationOperation.swift */; }; - AA8215B71B2F422C00A04622 /* MutuallyExclusive.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215991B2F422C00A04622 /* MutuallyExclusive.swift */; }; - AA8215B81B2F422C00A04622 /* NegatedCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159A1B2F422C00A04622 /* NegatedCondition.swift */; }; - AA8215B91B2F422C00A04622 /* NoCancelledDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159B1B2F422C00A04622 /* NoCancelledDependencies.swift */; }; - AA8215BA1B2F422C00A04622 /* NSOperation+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159C1B2F422C00A04622 /* NSOperation+Operations.swift */; }; - AA8215BB1B2F422C00A04622 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159D1B2F422C00A04622 /* Operation.swift */; }; - AA8215BC1B2F422C00A04622 /* OperationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159E1B2F422C00A04622 /* OperationCondition.swift */; }; - AA8215BD1B2F422C00A04622 /* OperationErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA82159F1B2F422C00A04622 /* OperationErrors.swift */; }; - AA8215BE1B2F422C00A04622 /* OperationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A01B2F422C00A04622 /* OperationObserver.swift */; }; - AA8215BF1B2F422C00A04622 /* PassbookCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A11B2F422C00A04622 /* PassbookCondition.swift */; }; - AA8215C01B2F422C00A04622 /* PhotosCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A21B2F422C00A04622 /* PhotosCondition.swift */; }; - AA8215C11B2F422C00A04622 /* ReachabilityCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A31B2F422C00A04622 /* ReachabilityCondition.swift */; }; - AA8215C21B2F422C00A04622 /* RemoteNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A41B2F422C00A04622 /* RemoteNotificationCondition.swift */; }; - AA8215C31B2F422C00A04622 /* SilentCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A51B2F422C00A04622 /* SilentCondition.swift */; }; - AA8215C41B2F422C00A04622 /* TimeoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A61B2F422C00A04622 /* TimeoutObserver.swift */; }; - AA8215C51B2F422C00A04622 /* UIUserNotifications+Operations.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A71B2F422C00A04622 /* UIUserNotifications+Operations.swift */; }; - AA8215C61B2F422C00A04622 /* URLSessionTaskOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A81B2F422C00A04622 /* URLSessionTaskOperation.swift */; }; - AA8215C71B2F422C00A04622 /* UserNotificationCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8215A91B2F422C00A04622 /* UserNotificationCondition.swift */; }; AA9A92321B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9A92311B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 3FBF72F61B4BBA7A00914526 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA8215541B2F23FA00A04622 /* Project object */; + proxyType = 1; + remoteGlobalIDString = AA82155C1B2F23FA00A04622; + remoteInfo = ADVOperation; + }; AA8215691B2F23FA00A04622 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AA8215541B2F23FA00A04622 /* Project object */; @@ -53,46 +100,78 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 3FBF72F81B4BBA7B00914526 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3FBF72F51B4BBA7A00914526 /* ADVOperation.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 13493011AE350750A6134743 /* UIUserNotifications+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIUserNotifications+Operations.swift"; sourceTree = ""; }; + 1349301BB36EA46A258584E3 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = ""; }; + 134931D4EAC34EED6631C161 /* HTTPBinNetworkOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPBinNetworkOperation.swift; sourceTree = ""; }; + 134931F7658131E92511027A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 134932126ECD95BB92B6A6E5 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = ""; }; + 134932428BA712B0F210BAA5 /* ExclusivityController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExclusivityController.swift; sourceTree = ""; }; + 134932D5FE660F99325CD4C0 /* GroupOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupOperation.swift; sourceTree = ""; }; + 134932E651583716948CE833 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 134932EF0A14315328EC6A3A /* RemoteNotificationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteNotificationCondition.swift; sourceTree = ""; }; + 134932F050B4568B14C8F2D3 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = ""; }; + 13493405578740A4F865B7EC /* CKContainer+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CKContainer+Operations.swift"; sourceTree = ""; }; + 1349349EF54CDBAC63C60197 /* MutuallyExclusive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutuallyExclusive.swift; sourceTree = ""; }; + 134936FFFC75FF2FD1D19228 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 1349378344214C06DCA78DC4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 134937EC6979C8C804E07AFA /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = ""; }; + 1349381A537AEBCEB914ECBD /* NoCancelledDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoCancelledDependencies.swift; sourceTree = ""; }; + 13493858DE7778C37EADB241 /* AlertOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = ""; }; + 134938B6B589E03B1377C6FD /* BlockObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockObserver.swift; sourceTree = ""; }; + 134938E6D96135B1DBE590FA /* OperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; + 13493940730F0891BFF91B91 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; + 134939BD12D892DAF26E1F5D /* NSOperation+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSOperation+Operations.swift"; sourceTree = ""; }; + 13493A18E225102B2BB5F75C /* LocationOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationOperation.swift; sourceTree = ""; }; + 13493A3B178B50C93EB4A857 /* UserNotificationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationCondition.swift; sourceTree = ""; }; + 13493A3DA8A5946B70A14674 /* PhotosCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosCondition.swift; sourceTree = ""; }; + 13493A5F6B2DD167BD0C5DF9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 13493A642DCBC93D7AFCF84A /* LocationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationCondition.swift; sourceTree = ""; }; + 13493A84EC920C5908B9EBA0 /* OperationErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationErrors.swift; sourceTree = ""; }; + 13493B097F3E57A358999B78 /* URLSessionTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskOperation.swift; sourceTree = ""; }; + 13493B673624213866B375D7 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; }; + 13493BA2D24FE404BC15CBF8 /* NegatedCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NegatedCondition.swift; sourceTree = ""; }; + 13493BAA7B983B2A9DD70153 /* SilentCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilentCondition.swift; sourceTree = ""; }; + 13493D01646023977702042A /* DelayOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayOperation.swift; sourceTree = ""; }; + 13493D60F708E7BFD326F144 /* OperationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationObserver.swift; sourceTree = ""; }; + 13493ECC76B568D7691439A3 /* Dictionary+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Operations.swift"; sourceTree = ""; }; + 13493ED11D23BAEFDCC5146A /* CalendarCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarCondition.swift; sourceTree = ""; }; + 13493EDCE9202ADB66218B97 /* TimeoutObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeoutObserver.swift; sourceTree = ""; }; + 13493F5C1CF7D0460544720C /* NetworkObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkObserver.swift; sourceTree = ""; }; + 13493F87EE5CB20941A934F2 /* BlockOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockOperation.swift; sourceTree = ""; }; + 13493FC92789F5097A896A73 /* HealthCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthCondition.swift; sourceTree = ""; }; + 3FBF72E21B4BBA4C00914526 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; AA82155D1B2F23FA00A04622 /* ADVOperation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ADVOperation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AA8215671B2F23FA00A04622 /* ADVOperationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ADVOperationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AA8215831B2F260200A04622 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AA8215871B2F417E00A04622 /* ADVOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADVOperation.h; sourceTree = ""; }; AA8215881B2F417E00A04622 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AA82158C1B2F422C00A04622 /* GroupOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupOperation.swift; sourceTree = ""; }; - AA82158D1B2F422C00A04622 /* BlockOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockOperation.swift; sourceTree = ""; }; - AA82158E1B2F422C00A04622 /* OperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; - AA82158F1B2F422C00A04622 /* BlockObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockObserver.swift; sourceTree = ""; }; - AA8215901B2F422C00A04622 /* CalendarCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarCondition.swift; sourceTree = ""; }; - AA8215911B2F422C00A04622 /* CKContainer+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CKContainer+Operations.swift"; sourceTree = ""; }; - AA8215921B2F422C00A04622 /* CloudCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudCondition.swift; sourceTree = ""; }; - AA8215931B2F422C00A04622 /* DelayOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayOperation.swift; sourceTree = ""; }; - AA8215941B2F422C00A04622 /* Dictionary+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Operations.swift"; sourceTree = ""; }; - AA8215951B2F422C00A04622 /* ExclusivityController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExclusivityController.swift; sourceTree = ""; }; - AA8215961B2F422C00A04622 /* HealthCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HealthCondition.swift; sourceTree = ""; }; - AA8215971B2F422C00A04622 /* LocationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationCondition.swift; sourceTree = ""; }; - AA8215981B2F422C00A04622 /* LocationOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationOperation.swift; sourceTree = ""; }; - AA8215991B2F422C00A04622 /* MutuallyExclusive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutuallyExclusive.swift; sourceTree = ""; }; - AA82159A1B2F422C00A04622 /* NegatedCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NegatedCondition.swift; sourceTree = ""; }; - AA82159B1B2F422C00A04622 /* NoCancelledDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoCancelledDependencies.swift; sourceTree = ""; }; - AA82159C1B2F422C00A04622 /* NSOperation+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSOperation+Operations.swift"; sourceTree = ""; }; - AA82159D1B2F422C00A04622 /* Operation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = ""; }; - AA82159E1B2F422C00A04622 /* OperationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationCondition.swift; sourceTree = ""; }; - AA82159F1B2F422C00A04622 /* OperationErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationErrors.swift; sourceTree = ""; }; - AA8215A01B2F422C00A04622 /* OperationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationObserver.swift; sourceTree = ""; }; - AA8215A11B2F422C00A04622 /* PassbookCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassbookCondition.swift; sourceTree = ""; }; - AA8215A21B2F422C00A04622 /* PhotosCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosCondition.swift; sourceTree = ""; }; - AA8215A31B2F422C00A04622 /* ReachabilityCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityCondition.swift; sourceTree = ""; }; - AA8215A41B2F422C00A04622 /* RemoteNotificationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteNotificationCondition.swift; sourceTree = ""; }; - AA8215A51B2F422C00A04622 /* SilentCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilentCondition.swift; sourceTree = ""; }; - AA8215A61B2F422C00A04622 /* TimeoutObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeoutObserver.swift; sourceTree = ""; }; - AA8215A71B2F422C00A04622 /* UIUserNotifications+Operations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIUserNotifications+Operations.swift"; sourceTree = ""; }; - AA8215A81B2F422C00A04622 /* URLSessionTaskOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskOperation.swift; sourceTree = ""; }; - AA8215A91B2F422C00A04622 /* UserNotificationCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationCondition.swift; sourceTree = ""; }; AA9A92311B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTaskOperationTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 3FBF72DF1B4BBA4C00914526 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3FBF72F41B4BBA7A00914526 /* ADVOperation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA8215591B2F23FA00A04622 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -111,126 +190,176 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - AA8215531B2F23FA00A04622 = { + 13493063DF9812FFE481F145 /* Extensions */ = { isa = PBXGroup; children = ( - AA8215861B2F417E00A04622 /* Source */, - AA8215811B2F260200A04622 /* Tests */, - AA82155E1B2F23FA00A04622 /* Products */, + 13493405578740A4F865B7EC /* CKContainer+Operations.swift */, + 13493ECC76B568D7691439A3 /* Dictionary+Operations.swift */, + 134939BD12D892DAF26E1F5D /* NSOperation+Operations.swift */, + 13493011AE350750A6134743 /* UIUserNotifications+Operations.swift */, ); + path = Extensions; sourceTree = ""; }; - AA82155E1B2F23FA00A04622 /* Products */ = { + 134933275DC3425D864C5967 /* Operation */ = { isa = PBXGroup; children = ( - AA82155D1B2F23FA00A04622 /* ADVOperation.framework */, - AA8215671B2F23FA00A04622 /* ADVOperationTests.xctest */, + 13493F87EE5CB20941A934F2 /* BlockOperation.swift */, + 13493D01646023977702042A /* DelayOperation.swift */, + 134932D5FE660F99325CD4C0 /* GroupOperation.swift */, + 13493A18E225102B2BB5F75C /* LocationOperation.swift */, + 13493B673624213866B375D7 /* Operation.swift */, + 13493B097F3E57A358999B78 /* URLSessionTaskOperation.swift */, + 13493858DE7778C37EADB241 /* AlertOperation.swift */, ); - name = Products; + path = Operation; sourceTree = ""; }; - AA8215811B2F260200A04622 /* Tests */ = { + 1349341AE04A878C559A67CF /* Ressources */ = { isa = PBXGroup; children = ( - AA9A92311B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift */, - AA82158B1B2F41D100A04622 /* Supporting Files */, + 1349319B6E547AABDD1F5BCC /* LaunchScreen.storyboard */, + 13493049D3DD2461C2FD577D /* Main.storyboard */, + 134932E651583716948CE833 /* Assets.xcassets */, + 13493940730F0891BFF91B91 /* Info.plist */, ); - path = Tests; + name = Ressources; sourceTree = ""; }; - AA8215861B2F417E00A04622 /* Source */ = { + 134937951FAD670D064AD595 /* ViewControllers */ = { isa = PBXGroup; children = ( - AA8215C81B2F425F00A04622 /* Operation Queue */, - AA9A922D1B2F431F003CF5D2 /* Operations */, - AA9A922E1B2F434A003CF5D2 /* Observers */, - AA9A92301B2F438D003CF5D2 /* Conditions */, - AA9A922F1B2F436C003CF5D2 /* Convenience Extensions */, - AA82158A1B2F418400A04622 /* Supporting Files */, + 134936FFFC75FF2FD1D19228 /* ViewController.swift */, ); - path = Source; + path = ViewControllers; sourceTree = ""; }; - AA82158A1B2F418400A04622 /* Supporting Files */ = { + 13493992B6D1BB9D1AA5D60E /* Classes */ = { isa = PBXGroup; children = ( - AA8215871B2F417E00A04622 /* ADVOperation.h */, - AA8215881B2F417E00A04622 /* Info.plist */, + 13493A5F6B2DD167BD0C5DF9 /* AppDelegate.swift */, + 134937951FAD670D064AD595 /* ViewControllers */, + 13493B17335AE2EB89AA1D7F /* Operations */, ); - name = "Supporting Files"; + path = Classes; sourceTree = ""; }; - AA82158B1B2F41D100A04622 /* Supporting Files */ = { + 13493A1CA8F733B3147721E4 /* Observer */ = { isa = PBXGroup; children = ( - AA8215831B2F260200A04622 /* Info.plist */, + 134938B6B589E03B1377C6FD /* BlockObserver.swift */, + 13493D60F708E7BFD326F144 /* OperationObserver.swift */, + 13493EDCE9202ADB66218B97 /* TimeoutObserver.swift */, + 13493F5C1CF7D0460544720C /* NetworkObserver.swift */, ); - name = "Supporting Files"; + path = Observer; sourceTree = ""; }; - AA8215C81B2F425F00A04622 /* Operation Queue */ = { + 13493A247F0CFCC8A9E6B161 /* Conditions */ = { isa = PBXGroup; children = ( - AA8215951B2F422C00A04622 /* ExclusivityController.swift */, - AA82158E1B2F422C00A04622 /* OperationQueue.swift */, + 13493ED11D23BAEFDCC5146A /* CalendarCondition.swift */, + 134932F050B4568B14C8F2D3 /* CloudCondition.swift */, + 13493FC92789F5097A896A73 /* HealthCondition.swift */, + 13493A642DCBC93D7AFCF84A /* LocationCondition.swift */, + 1349349EF54CDBAC63C60197 /* MutuallyExclusive.swift */, + 13493BA2D24FE404BC15CBF8 /* NegatedCondition.swift */, + 1349381A537AEBCEB914ECBD /* NoCancelledDependencies.swift */, + 134932126ECD95BB92B6A6E5 /* OperationCondition.swift */, + 13493A84EC920C5908B9EBA0 /* OperationErrors.swift */, + 134937EC6979C8C804E07AFA /* PassbookCondition.swift */, + 13493A3DA8A5946B70A14674 /* PhotosCondition.swift */, + 1349301BB36EA46A258584E3 /* ReachabilityCondition.swift */, + 134932EF0A14315328EC6A3A /* RemoteNotificationCondition.swift */, + 13493BAA7B983B2A9DD70153 /* SilentCondition.swift */, + 13493A3B178B50C93EB4A857 /* UserNotificationCondition.swift */, ); - name = "Operation Queue"; + path = Conditions; sourceTree = ""; }; - AA9A922D1B2F431F003CF5D2 /* Operations */ = { + 13493B17335AE2EB89AA1D7F /* Operations */ = { isa = PBXGroup; children = ( - AA82159D1B2F422C00A04622 /* Operation.swift */, - AA82158D1B2F422C00A04622 /* BlockOperation.swift */, - AA82158C1B2F422C00A04622 /* GroupOperation.swift */, - AA8215A81B2F422C00A04622 /* URLSessionTaskOperation.swift */, - AA8215981B2F422C00A04622 /* LocationOperation.swift */, - AA8215931B2F422C00A04622 /* DelayOperation.swift */, - ); - name = Operations; + 134931D4EAC34EED6631C161 /* HTTPBinNetworkOperation.swift */, + ); + path = Operations; sourceTree = ""; }; - AA9A922E1B2F434A003CF5D2 /* Observers */ = { + 13493E72AC03FFD260E62889 /* OperationQueue */ = { isa = PBXGroup; children = ( - AA8215A01B2F422C00A04622 /* OperationObserver.swift */, - AA82158F1B2F422C00A04622 /* BlockObserver.swift */, - AA8215A61B2F422C00A04622 /* TimeoutObserver.swift */, + 134932428BA712B0F210BAA5 /* ExclusivityController.swift */, + 134938E6D96135B1DBE590FA /* OperationQueue.swift */, ); - name = Observers; + path = OperationQueue; sourceTree = ""; }; - AA9A922F1B2F436C003CF5D2 /* Convenience Extensions */ = { + 3FBF72E31B4BBA4C00914526 /* Demo */ = { isa = PBXGroup; children = ( - AA8215941B2F422C00A04622 /* Dictionary+Operations.swift */, - AA82159C1B2F422C00A04622 /* NSOperation+Operations.swift */, - AA8215911B2F422C00A04622 /* CKContainer+Operations.swift */, - AA8215A71B2F422C00A04622 /* UIUserNotifications+Operations.swift */, + 1349341AE04A878C559A67CF /* Ressources */, + 13493992B6D1BB9D1AA5D60E /* Classes */, ); - name = "Convenience Extensions"; + path = Demo; sourceTree = ""; }; - AA9A92301B2F438D003CF5D2 /* Conditions */ = { + AA8215531B2F23FA00A04622 = { isa = PBXGroup; children = ( - AA8215901B2F422C00A04622 /* CalendarCondition.swift */, - AA8215921B2F422C00A04622 /* CloudCondition.swift */, - AA8215961B2F422C00A04622 /* HealthCondition.swift */, - AA8215971B2F422C00A04622 /* LocationCondition.swift */, - AA8215991B2F422C00A04622 /* MutuallyExclusive.swift */, - AA82159A1B2F422C00A04622 /* NegatedCondition.swift */, - AA82159B1B2F422C00A04622 /* NoCancelledDependencies.swift */, - AA82159E1B2F422C00A04622 /* OperationCondition.swift */, - AA82159F1B2F422C00A04622 /* OperationErrors.swift */, - AA8215A11B2F422C00A04622 /* PassbookCondition.swift */, - AA8215A21B2F422C00A04622 /* PhotosCondition.swift */, - AA8215A31B2F422C00A04622 /* ReachabilityCondition.swift */, - AA8215A41B2F422C00A04622 /* RemoteNotificationCondition.swift */, - AA8215A51B2F422C00A04622 /* SilentCondition.swift */, - AA8215A91B2F422C00A04622 /* UserNotificationCondition.swift */, - ); - name = Conditions; + 3FBF72E31B4BBA4C00914526 /* Demo */, + AA8215861B2F417E00A04622 /* Source */, + AA8215811B2F260200A04622 /* Tests */, + AA82155E1B2F23FA00A04622 /* Products */, + ); + sourceTree = ""; + }; + AA82155E1B2F23FA00A04622 /* Products */ = { + isa = PBXGroup; + children = ( + AA82155D1B2F23FA00A04622 /* ADVOperation.framework */, + AA8215671B2F23FA00A04622 /* ADVOperationTests.xctest */, + 3FBF72E21B4BBA4C00914526 /* Demo.app */, + ); + name = Products; + sourceTree = ""; + }; + AA8215811B2F260200A04622 /* Tests */ = { + isa = PBXGroup; + children = ( + AA9A92311B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift */, + AA82158B1B2F41D100A04622 /* Supporting Files */, + ); + path = Tests; + sourceTree = ""; + }; + AA8215861B2F417E00A04622 /* Source */ = { + isa = PBXGroup; + children = ( + AA82158A1B2F418400A04622 /* Supporting Files */, + 13493A247F0CFCC8A9E6B161 /* Conditions */, + 13493063DF9812FFE481F145 /* Extensions */, + 13493A1CA8F733B3147721E4 /* Observer */, + 13493E72AC03FFD260E62889 /* OperationQueue */, + 134933275DC3425D864C5967 /* Operation */, + ); + path = Source; + sourceTree = ""; + }; + AA82158A1B2F418400A04622 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AA8215871B2F417E00A04622 /* ADVOperation.h */, + AA8215881B2F417E00A04622 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + AA82158B1B2F41D100A04622 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AA8215831B2F260200A04622 /* Info.plist */, + ); + name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ @@ -246,6 +375,25 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 3FBF72E11B4BBA4C00914526 /* Demo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3FBF72F31B4BBA4C00914526 /* Build configuration list for PBXNativeTarget "Demo" */; + buildPhases = ( + 3FBF72DE1B4BBA4C00914526 /* Sources */, + 3FBF72DF1B4BBA4C00914526 /* Frameworks */, + 3FBF72E01B4BBA4C00914526 /* Resources */, + 3FBF72F81B4BBA7B00914526 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 3FBF72F71B4BBA7A00914526 /* PBXTargetDependency */, + ); + name = Demo; + productName = Demo; + productReference = 3FBF72E21B4BBA4C00914526 /* Demo.app */; + productType = "com.apple.product-type.application"; + }; AA82155C1B2F23FA00A04622 /* ADVOperation */ = { isa = PBXNativeTarget; buildConfigurationList = AA8215711B2F23FA00A04622 /* Build configuration list for PBXNativeTarget "ADVOperation" */; @@ -291,6 +439,9 @@ LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Advanced Operation"; TargetAttributes = { + 3FBF72E11B4BBA4C00914526 = { + CreatedOnToolsVersion = 7.0; + }; AA82155C1B2F23FA00A04622 = { CreatedOnToolsVersion = 7.0; }; @@ -305,12 +456,14 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = AA8215531B2F23FA00A04622; productRefGroup = AA82155E1B2F23FA00A04622 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( + 3FBF72E11B4BBA4C00914526 /* Demo */, AA82155C1B2F23FA00A04622 /* ADVOperation */, AA8215661B2F23FA00A04622 /* ADVOperationTests */, ); @@ -318,6 +471,16 @@ /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 3FBF72E01B4BBA4C00914526 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13493DC402D9C1782CF671E2 /* LaunchScreen.storyboard in Resources */, + 1349346F044013C41928C1FC /* Main.storyboard in Resources */, + 134931087A4BE6A019937AB4 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA82155B1B2F23FA00A04622 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -329,18 +492,58 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA8215891B2F417E00A04622 /* Info.plist in Resources */, - AA8215851B2F260200A04622 /* Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 3FBF72DE1B4BBA4C00914526 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13493AAF2E22246D33908905 /* AppDelegate.swift in Sources */, + 3F8D74A01B4BC06D0049FBD0 /* HTTPBinNetworkOperation.swift in Sources */, + 13493ACEE507EF3D8B2A3676 /* ViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; AA8215581B2F23FA00A04622 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3FBF72FE1B4BBB4C00914526 /* NegatedCondition.swift in Sources */, + 3FBF73011B4BBB4C00914526 /* OperationErrors.swift in Sources */, + 3FBF731D1B4BBB6300914526 /* DelayOperation.swift in Sources */, + 3FBF73091B4BBB5100914526 /* Dictionary+Operations.swift in Sources */, + 3FBF73151B4BBB5E00914526 /* OperationQueue.swift in Sources */, + 3FBF730B1B4BBB5100914526 /* UIUserNotifications+Operations.swift in Sources */, + 3FBF73051B4BBB4C00914526 /* RemoteNotificationCondition.swift in Sources */, + 3FBF73121B4BBB5900914526 /* TimeoutObserver.swift in Sources */, + 3FBF73071B4BBB4C00914526 /* UserNotificationCondition.swift in Sources */, + 3FBF73211B4BBB6300914526 /* URLSessionTaskOperation.swift in Sources */, + 3FBF72FD1B4BBB4C00914526 /* MutuallyExclusive.swift in Sources */, + 3FBF72FF1B4BBB4C00914526 /* NoCancelledDependencies.swift in Sources */, + 3FBF73101B4BBB5900914526 /* BlockObserver.swift in Sources */, + 3FBF73131B4BBB5900914526 /* NetworkObserver.swift in Sources */, + 3FBF73061B4BBB4C00914526 /* SilentCondition.swift in Sources */, + 3FBF72FA1B4BBB4C00914526 /* CloudCondition.swift in Sources */, + 3FBF72FC1B4BBB4C00914526 /* LocationCondition.swift in Sources */, + 3FBF73021B4BBB4C00914526 /* PassbookCondition.swift in Sources */, + 3FBF731F1B4BBB6300914526 /* LocationOperation.swift in Sources */, + 3FBF730A1B4BBB5100914526 /* NSOperation+Operations.swift in Sources */, + 3FBF73041B4BBB4C00914526 /* ReachabilityCondition.swift in Sources */, + 3FBF72F91B4BBB4C00914526 /* CalendarCondition.swift in Sources */, + 3FBF73031B4BBB4C00914526 /* PhotosCondition.swift in Sources */, + 3FBF72FB1B4BBB4C00914526 /* HealthCondition.swift in Sources */, + 3FBF73221B4BBB6300914526 /* AlertOperation.swift in Sources */, + 3FBF73201B4BBB6300914526 /* Operation.swift in Sources */, + 3FBF73001B4BBB4C00914526 /* OperationCondition.swift in Sources */, + 3FBF73081B4BBB5100914526 /* CKContainer+Operations.swift in Sources */, + 3FBF731C1B4BBB6300914526 /* BlockOperation.swift in Sources */, + 3FBF731E1B4BBB6300914526 /* GroupOperation.swift in Sources */, + 3FBF73111B4BBB5900914526 /* OperationObserver.swift in Sources */, + 3FBF73141B4BBB5E00914526 /* ExclusivityController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -348,43 +551,50 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - AA8215BA1B2F422C00A04622 /* NSOperation+Operations.swift in Sources */, - AA8215B81B2F422C00A04622 /* NegatedCondition.swift in Sources */, - AA8215AB1B2F422C00A04622 /* BlockOperation.swift in Sources */, - AA8215AE1B2F422C00A04622 /* CalendarCondition.swift in Sources */, - AA8215BD1B2F422C00A04622 /* OperationErrors.swift in Sources */, - AA8215B11B2F422C00A04622 /* DelayOperation.swift in Sources */, - AA8215C71B2F422C00A04622 /* UserNotificationCondition.swift in Sources */, - AA8215B21B2F422C00A04622 /* Dictionary+Operations.swift in Sources */, - AA8215C61B2F422C00A04622 /* URLSessionTaskOperation.swift in Sources */, - AA8215B61B2F422C00A04622 /* LocationOperation.swift in Sources */, - AA8215AC1B2F422C00A04622 /* OperationQueue.swift in Sources */, - AA8215BB1B2F422C00A04622 /* Operation.swift in Sources */, - AA8215B91B2F422C00A04622 /* NoCancelledDependencies.swift in Sources */, - AA8215C01B2F422C00A04622 /* PhotosCondition.swift in Sources */, - AA8215B51B2F422C00A04622 /* LocationCondition.swift in Sources */, - AA8215C21B2F422C00A04622 /* RemoteNotificationCondition.swift in Sources */, - AA8215AA1B2F422C00A04622 /* GroupOperation.swift in Sources */, - AA8215B31B2F422C00A04622 /* ExclusivityController.swift in Sources */, - AA8215B71B2F422C00A04622 /* MutuallyExclusive.swift in Sources */, - AA8215B01B2F422C00A04622 /* CloudCondition.swift in Sources */, - AA8215C41B2F422C00A04622 /* TimeoutObserver.swift in Sources */, - AA8215BF1B2F422C00A04622 /* PassbookCondition.swift in Sources */, AA9A92321B301FE8003CF5D2 /* URLSessionTaskOperationTests.swift in Sources */, - AA8215B41B2F422C00A04622 /* HealthCondition.swift in Sources */, - AA8215AF1B2F422C00A04622 /* CKContainer+Operations.swift in Sources */, - AA8215AD1B2F422C00A04622 /* BlockObserver.swift in Sources */, - AA8215C31B2F422C00A04622 /* SilentCondition.swift in Sources */, - AA8215C51B2F422C00A04622 /* UIUserNotifications+Operations.swift in Sources */, - AA8215BC1B2F422C00A04622 /* OperationCondition.swift in Sources */, - AA8215BE1B2F422C00A04622 /* OperationObserver.swift in Sources */, - AA8215C11B2F422C00A04622 /* ReachabilityCondition.swift in Sources */, + 13493B409710FCD8B3596BD6 /* CalendarCondition.swift in Sources */, + 3FBF730F1B4BBB5900914526 /* NetworkObserver.swift in Sources */, + 1349360E87176D613F47500D /* CloudCondition.swift in Sources */, + 13493A38C8C16BE598E04969 /* HealthCondition.swift in Sources */, + 13493ECEA49AE331A40DA484 /* LocationCondition.swift in Sources */, + 13493C5FA625EC02B3D0A7A8 /* MutuallyExclusive.swift in Sources */, + 134932BF46092E111600482B /* NegatedCondition.swift in Sources */, + 134933B7FB906B44B1607B54 /* NoCancelledDependencies.swift in Sources */, + 134938EC0526BD7445A952EF /* OperationCondition.swift in Sources */, + 134930FC7911FA67B2B4D7DF /* OperationErrors.swift in Sources */, + 134935195F8C1F85F4302B2C /* PassbookCondition.swift in Sources */, + 1349375932A0DA51708ACCEE /* PhotosCondition.swift in Sources */, + 13493A98CA8E7A43C7FC23A9 /* ReachabilityCondition.swift in Sources */, + 1349373AE6F86D1C8BC8FB3A /* RemoteNotificationCondition.swift in Sources */, + 13493338E30D6F010F759FD2 /* SilentCondition.swift in Sources */, + 13493E6507EEA35308B23182 /* UserNotificationCondition.swift in Sources */, + 13493FCD68D71F2EB0BFD4D9 /* CKContainer+Operations.swift in Sources */, + 1349373A64C53B6C57E57A49 /* Dictionary+Operations.swift in Sources */, + 13493201C21925B5246A0246 /* NSOperation+Operations.swift in Sources */, + 3FBF73231B4BBB6300914526 /* AlertOperation.swift in Sources */, + 1349360903CEC2ACDE4CBC91 /* UIUserNotifications+Operations.swift in Sources */, + 13493C5D50A6087A92FDD508 /* BlockObserver.swift in Sources */, + 1349341FFC87A62ED60EE2EF /* OperationObserver.swift in Sources */, + 13493ED6E7A06C6B9DCE6ACD /* TimeoutObserver.swift in Sources */, + 13493711793B138D03F2D29A /* ExclusivityController.swift in Sources */, + 134931411BBF6370A978DABC /* OperationQueue.swift in Sources */, + 13493AE54263F87E42D5B799 /* BlockOperation.swift in Sources */, + 13493CB2964452FAEEE4E28B /* DelayOperation.swift in Sources */, + 13493DF93070CB84CD5A58B9 /* GroupOperation.swift in Sources */, + 134938AACE32347FA50E1C6F /* LocationOperation.swift in Sources */, + 13493BDEBF2CDEED6AD2028A /* Operation.swift in Sources */, + 13493BEBAA8A2614FBCDBF0E /* URLSessionTaskOperation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 3FBF72F71B4BBA7A00914526 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = AA82155C1B2F23FA00A04622 /* ADVOperation */; + targetProxy = 3FBF72F61B4BBA7A00914526 /* PBXContainerItemProxy */; + }; AA82156A1B2F23FA00A04622 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = AA82155C1B2F23FA00A04622 /* ADVOperation */; @@ -392,7 +602,50 @@ }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + 13493049D3DD2461C2FD577D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 1349378344214C06DCA78DC4 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 1349319B6E547AABDD1F5BCC /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 134931F7658131E92511027A /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ + 3FBF72F11B4BBA4C00914526 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = Demo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = de.lmis.Demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 3FBF72F21B4BBA4C00914526 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + INFOPLIST_FILE = Demo/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = de.lmis.Demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; AA82156F1B2F23FA00A04622 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -430,7 +683,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -472,7 +725,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -537,6 +790,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 3FBF72F31B4BBA4C00914526 /* Build configuration list for PBXNativeTarget "Demo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3FBF72F11B4BBA4C00914526 /* Debug */, + 3FBF72F21B4BBA4C00914526 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; AA8215571B2F23FA00A04622 /* Build configuration list for PBXProject "ADVOperation" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..118c98f --- /dev/null +++ b/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Demo/Base.lproj/LaunchScreen.storyboard b/Demo/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..cd5aa4d --- /dev/null +++ b/Demo/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/Base.lproj/Main.storyboard b/Demo/Base.lproj/Main.storyboard new file mode 100644 index 0000000..dbe1adf --- /dev/null +++ b/Demo/Base.lproj/Main.storyboard @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Demo/Classes/AppDelegate.swift b/Demo/Classes/AppDelegate.swift new file mode 100644 index 0000000..d8290f5 --- /dev/null +++ b/Demo/Classes/AppDelegate.swift @@ -0,0 +1,22 @@ +// +// AppDelegate.swift +// Demo +// +// Created by Phillipp (LMIS) on 07/07/15. +// Copyright © 2015 Advanced Operation. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject:AnyObject]?) -> Bool { + // Override point for customization after application launch. + return true + } + +} + diff --git a/Demo/Classes/Operations/HTTPBinNetworkOperation.swift b/Demo/Classes/Operations/HTTPBinNetworkOperation.swift new file mode 100644 index 0000000..be67144 --- /dev/null +++ b/Demo/Classes/Operations/HTTPBinNetworkOperation.swift @@ -0,0 +1,113 @@ +// +// Created by Phillipp (LMIS) on 07/07/15. +// Copyright (c) 2015 Advanced Operation. All rights reserved. +// + +import Foundation +import ADVOperation + + +final class HTTPBinNetworkOperation: GroupOperation { + + let cacheFile:NSURL + + init(cacheFile:NSURL) { + + self.cacheFile = cacheFile + + super.init(operations: []) + self.name = "HTTPBin Operation" + + let url = NSURL(string: "http://httpbin.org/image/png")! + let session = NSURLSession.sharedSession() + + let task = session.downloadTaskWithURL(url) { + url, response, error in + self.downloadFinished(url, response: response as? NSHTTPURLResponse, error: error) + } + + if let task = task { + let taskOperation = URLSessionTaskOperation(task: task) + + let reachabilityCondition = ReachabilityCondition(host: url) + taskOperation.addCondition(reachabilityCondition) + + let networkObserver = NetworkObserver() + taskOperation.addObserver(networkObserver) + + addOperation(taskOperation) + } + + } + + private func downloadFinished(url: NSURL?, response: NSHTTPURLResponse?, error: NSError?) { + + if let error = error { + print("download failed \(error)") + } else { + print("download succeeded") + } + + if let localURL = url { + do { + /* + If we already have a file at this location, just delete it. + Also, swallow the error, because we don't really care about it. + */ + try NSFileManager.defaultManager().removeItemAtURL(cacheFile) + } + catch { + } + + do { + try NSFileManager.defaultManager().moveItemAtURL(localURL, toURL: cacheFile) + print("data successfully moved to \(cacheFile.absoluteString)") + } + catch let error as NSError { + aggregateError(error) + } + + } else if let error = error { + aggregateError(error) + } else { + // Do nothing, and the operation will automatically finish. + } + + + } + + override func finished(errors: [NSError]) { + guard let firstError = errors.first where userInitiated else { return } + + /* + We failed to load the model on a user initiated operation try and present + an error. + */ + + let alert = AlertOperation() + + alert.title = "Unable to load download image" + + alert.message = "An error occurred while downloading image. \(firstError.localizedDescription). Please try again later." + + // No custom action for this button. + alert.addAction("Retry Later", style: .Cancel) + + /* + For this operation, the `loadHandler` is only ever invoked if there are + no errors, so if we get to this point we know that it was not executed. + This means that we can offer to the user to try loading the model again, + simply by creating a new copy of the operation and giving it the same + loadHandler. + */ + alert.addAction("Retry Now") { alertOperation in + let retryOperation = HTTPBinNetworkOperation(cacheFile:self.cacheFile) + retryOperation.userInitiated = true + alertOperation.produceOperation(retryOperation) + } + + produceOperation(alert) + } + + +} \ No newline at end of file diff --git a/Demo/Classes/ViewControllers/ViewController.swift b/Demo/Classes/ViewControllers/ViewController.swift new file mode 100644 index 0000000..515b7d0 --- /dev/null +++ b/Demo/Classes/ViewControllers/ViewController.swift @@ -0,0 +1,76 @@ +// +// ViewController.swift +// Demo +// +// Created by Phillipp on 07/07/15. +// Copyright © 2015 Advanced Operation. All rights reserved. +// + +import UIKit +import ADVOperation + +class ViewController: UITableViewController, OperationQueueDelegate { + + let operationQueue = OperationQueue() + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + +// self.operationQueue.delegate = self + } + + // MARK: Actions + + @IBAction func startAction(sender: UIBarButtonItem) { + + do { + let cachesFolder = try NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true) + let cacheFile = cachesFolder.URLByAppendingPathComponent("image.png") + + let networkOperation = HTTPBinNetworkOperation(cacheFile: cacheFile) + networkOperation.userInitiated = true + self.operationQueue.addOperation(networkOperation) + + } catch let error as NSError { + print("error getting caches folder \(error)") + } + + } + + + // MARK: UITableViewDataSource + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.operationQueue.operationCount + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) + let operation = self.operationQueue.operations[indexPath.item] + let operationName = operation.name ?? "no name" + cell.textLabel?.text = "Name: \(operationName) - State: \(operation.ready)" + + return cell + } + + // MARK: OperationQueue Delegate + + func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) { + print("OperationCount: \(operationQueue.operationCount)") + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.reloadData() + } + } + + func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) { + print("OperationCount: \(operationQueue.operationCount)") + NSOperationQueue.mainQueue().addOperationWithBlock { + self.tableView.reloadData() + } + } + + + +} + diff --git a/Demo/Info.plist b/Demo/Info.plist new file mode 100644 index 0000000..7cf0c9d --- /dev/null +++ b/Demo/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + DEV + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Source/CalendarCondition.swift b/Source/Conditions/CalendarCondition.swift similarity index 56% rename from Source/CalendarCondition.swift rename to Source/Conditions/CalendarCondition.swift index 8977608..c21b022 100644 --- a/Source/CalendarCondition.swift +++ b/Source/Conditions/CalendarCondition.swift @@ -9,35 +9,36 @@ This file shows an example of implementing the OperationCondition protocol. import EventKit /// A condition for verifying access to the user's calendar. -struct CalendarCondition: OperationCondition { - - static let name = "Calendar" + +public struct CalendarCondition: OperationCondition { + + public static let name = "Calendar" + public static let isMutuallyExclusive = false static let entityTypeKey = "EKEntityType" - static let isMutuallyExclusive = false - + let entityType: EKEntityType - - init(entityType: EKEntityType) { + + public init(entityType: EKEntityType) { self.entityType = entityType } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return CalendarPermissionOperation(entityType: entityType) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { switch EKEventStore.authorizationStatusForEntityType(entityType) { - case .Authorized: - completion(.Satisfied) + case .Authorized: + completion(.Satisfied) - default: - // We are not authorized to access entities of this type. - let error = NSError(code: .ConditionFailed, userInfo: [ + default: + // We are not authorized to access entities of this type. + let error = NSError(code: .ConditionFailed, userInfo: [ OperationConditionKey: self.dynamicType.name, self.dynamicType.entityTypeKey: entityType.rawValue - ]) - - completion(.Failed(error)) + ]) + + completion(.Failed(error)) } } } @@ -46,30 +47,32 @@ struct CalendarCondition: OperationCondition { A private `Operation` that will request access to the user's Calendar/Reminders, if it has not already been granted. */ + private class CalendarPermissionOperation: Operation { let entityType: EKEntityType let store = EKEventStore() - + init(entityType: EKEntityType) { self.entityType = entityType super.init() addCondition(AlertPresentation()) } - + override func execute() { let status = EKEventStore.authorizationStatusForEntityType(entityType) - + switch status { - case .NotDetermined: - dispatch_async(dispatch_get_main_queue()) { - self.store.requestAccessToEntityType(self.entityType) { granted, error in - self.finish() - } + case .NotDetermined: + dispatch_async(dispatch_get_main_queue()) { + self.store.requestAccessToEntityType(self.entityType) { + granted, error in + self.finish() } + } - default: - finish() + default: + finish() } } - + } diff --git a/Source/CloudCondition.swift b/Source/Conditions/CloudCondition.swift similarity index 74% rename from Source/CloudCondition.swift rename to Source/Conditions/CloudCondition.swift index 5c303cc..b3d6929 100644 --- a/Source/CloudCondition.swift +++ b/Source/Conditions/CloudCondition.swift @@ -9,49 +9,51 @@ This file shows an example of implementing the OperationCondition protocol. import CloudKit /// A condition describing that the operation requires access to a specific CloudKit container. -struct CloudContainerCondition: OperationCondition { - - static let name = "CloudContainer" + +public struct CloudContainerCondition: OperationCondition { + + public static let name = "CloudContainer" static let containerKey = "CKContainer" - + /* CloudKit has no problem handling multiple operations at the same time so we will allow operations that use CloudKit to be concurrent with each other. */ - static let isMutuallyExclusive = false - - let container: CKContainer // this is the container to which you need access. + public static let isMutuallyExclusive = false + + let container: CKContainer + // this is the container to which you need access. let permission: CKApplicationPermissions - + /** - parameter container: the `CKContainer` to which you need access. - parameter permission: the `CKApplicationPermissions` you need for the container. This parameter has a default value of `[]`, which would get you anonymized read/write access. */ - init(container: CKContainer, permission: CKApplicationPermissions = []) { + public init(container: CKContainer, permission: CKApplicationPermissions = []) { self.container = container self.permission = permission } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return CloudKitPermissionOperation(container: container, permission: permission) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { - container.verifyPermission(permission, requestingIfNecessary: false) { error in + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + container.verifyPermission(permission, requestingIfNecessary: false) { + error in if let error = error { let conditionError = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.containerKey: self.container, - NSUnderlyingErrorKey: error + OperationConditionKey: self.dynamicType.name, + self.dynamicType.containerKey: self.container, + NSUnderlyingErrorKey: error ]) completion(.Failed(conditionError)) - } - else { + } else { completion(.Satisfied) } } @@ -62,15 +64,16 @@ struct CloudContainerCondition: OperationCondition { This operation asks the user for permission to use CloudKit, if necessary. If permission has already been granted, this operation will quickly finish. */ + private class CloudKitPermissionOperation: Operation { let container: CKContainer let permission: CKApplicationPermissions - + init(container: CKContainer, permission: CKApplicationPermissions) { self.container = container self.permission = permission super.init() - + if permission != [] { /* Requesting non-zero permissions means that this potentially presents @@ -80,11 +83,12 @@ private class CloudKitPermissionOperation: Operation { addCondition(AlertPresentation()) } } - + override func execute() { - container.verifyPermission(permission, requestingIfNecessary: true) { error in + container.verifyPermission(permission, requestingIfNecessary: true) { + error in self.finishWithError(error) } } - + } diff --git a/Source/HealthCondition.swift b/Source/Conditions/HealthCondition.swift similarity index 79% rename from Source/HealthCondition.swift rename to Source/Conditions/HealthCondition.swift index 0e35826..2352974 100644 --- a/Source/HealthCondition.swift +++ b/Source/Conditions/HealthCondition.swift @@ -7,7 +7,7 @@ This file shows an example of implementing the OperationCondition protocol. */ #if os(iOS) - + import HealthKit import UIKit @@ -15,15 +15,19 @@ import UIKit A condition to indicate an `Operation` requires access to the user's health data. */ -struct HealthCondition: OperationCondition { - static let name = "Health" + +public struct HealthCondition: OperationCondition { + + public static let name = "Health" + public static let isMutuallyExclusive = false + static let healthDataAvailable = "HealthDataAvailable" static let unauthorizedShareTypesKey = "UnauthorizedShareTypes" - static let isMutuallyExclusive = false - + + let shareTypes: Set let readTypes: Set - + /** The designated initializer. @@ -33,12 +37,12 @@ struct HealthCondition: OperationCondition { - parameter typesToRead: An array of `HKSampleType` objects, indicating the kinds of data you wish to read from HealthKit. */ - init(typesToWrite: Set, typesToRead: Set) { + public init(typesToWrite: Set, typesToRead: Set) { shareTypes = typesToWrite readTypes = typesToRead } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { guard HKHealthStore.isHealthDataAvailable() else { return nil } @@ -49,67 +53,70 @@ struct HealthCondition: OperationCondition { return HealthPermissionOperation(shareTypes: shareTypes, readTypes: readTypes) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { guard HKHealthStore.isHealthDataAvailable() else { failed(shareTypes, completion: completion) return } let store = HKHealthStore() + /* Note that we cannot check to see if access to the "typesToRead" - has been granted or not, as that is sensitive data. For example, - a person with diabetes may choose to not allow access to Blood Glucose + has been granted or not, as that is sensitive data. For example, + a person with diabetes may choose to not allow access to Blood Glucose data, and the fact that this request has denied is itself an indicator that the user may have diabetes. - - Thus, we can only check to see if we've been given permission to + + Thus, we can only check to see if we've been given permission to write data to HealthKit. */ - let unauthorizedShareTypes = shareTypes.filter { shareType in + let unauthorizedShareTypes = shareTypes.filter { + shareType in return store.authorizationStatusForType(shareType) != .SharingAuthorized } if !unauthorizedShareTypes.isEmpty { failed(Set(unauthorizedShareTypes), completion: completion) - } - else { + } else { completion(.Satisfied) } } - + // Break this out in to its own method so we don't clutter up the evaluate... method. private func failed(unauthorizedShareTypes: Set, completion: OperationConditionResult -> Void) { let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.healthDataAvailable: HKHealthStore.isHealthDataAvailable(), - self.dynamicType.unauthorizedShareTypesKey: unauthorizedShareTypes + OperationConditionKey: self.dynamicType.name, + self.dynamicType.healthDataAvailable: HKHealthStore.isHealthDataAvailable(), + self.dynamicType.unauthorizedShareTypesKey: unauthorizedShareTypes ]) completion(.Failed(error)) } + } /** A private `Operation` that will request access to the user's health data, if it has not already been granted. */ + private class HealthPermissionOperation: Operation { let shareTypes: Set let readTypes: Set - + init(shareTypes: Set, readTypes: Set) { self.shareTypes = shareTypes self.readTypes = readTypes - + super.init() addCondition(MutuallyExclusive()) addCondition(MutuallyExclusive()) addCondition(AlertPresentation()) } - + override func execute() { dispatch_async(dispatch_get_main_queue()) { let store = HKHealthStore() @@ -118,12 +125,13 @@ private class HealthPermissionOperation: Operation { has already been granted. */ - store.requestAuthorizationToShareTypes(self.shareTypes, readTypes: self.readTypes) { completed, error in + store.requestAuthorizationToShareTypes(self.shareTypes, readTypes: self.readTypes) { + completed, error in self.finish() } } } - + } - + #endif diff --git a/Source/LocationCondition.swift b/Source/Conditions/LocationCondition.swift similarity index 61% rename from Source/LocationCondition.swift rename to Source/Conditions/LocationCondition.swift index c382e89..096c7fd 100644 --- a/Source/LocationCondition.swift +++ b/Source/Conditions/LocationCondition.swift @@ -9,70 +9,73 @@ This file shows an example of implementing the OperationCondition protocol. import CoreLocation /// A condition for verifying access to the user's location. -struct LocationCondition: OperationCondition { + +public struct LocationCondition: OperationCondition { /** Declare a new enum instead of using `CLAuthorizationStatus`, because that enum has more case values than are necessary for our purposes. */ + enum Usage { case WhenInUse case Always } + + public static let name = "Location" + public static let isMutuallyExclusive = false - static let name = "Location" static let locationServicesEnabledKey = "CLLocationServicesEnabled" static let authorizationStatusKey = "CLAuthorizationStatus" - static let isMutuallyExclusive = false - + + let usage: Usage - + init(usage: Usage) { self.usage = usage } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return LocationPermissionOperation(usage: usage) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { let enabled = CLLocationManager.locationServicesEnabled() let actual = CLLocationManager.authorizationStatus() - + var error: NSError? // There are several factors to consider when evaluating this condition switch (enabled, usage, actual) { - case (true, _, .AuthorizedAlways): - // The service is enabled, and we have "Always" permission -> condition satisfied. - break - - case (true, .WhenInUse, .AuthorizedWhenInUse): - /* - The service is enabled, and we have and need "WhenInUse" - permission -> condition satisfied. - */ - break - - default: - /* - Anything else is an error. Maybe location services are disabled, - or maybe we need "Always" permission but only have "WhenInUse", - or maybe access has been restricted or denied, - or maybe access hasn't been request yet. - - The last case would happen if this condition were wrapped in a `SilentCondition`. - */ - error = NSError(code: .ConditionFailed, userInfo: [ + case (true, _, .AuthorizedAlways): + // The service is enabled, and we have "Always" permission -> condition satisfied. + break + + case (true, .WhenInUse, .AuthorizedWhenInUse): + /* + The service is enabled, and we have and need "WhenInUse" + permission -> condition satisfied. + */ + break + + default: + /* + Anything else is an error. Maybe location services are disabled, + or maybe we need "Always" permission but only have "WhenInUse", + or maybe access has been restricted or denied, + or maybe access hasn't been request yet. + + The last case would happen if this condition were wrapped in a `SilentCondition`. + */ + error = NSError(code: .ConditionFailed, userInfo: [ OperationConditionKey: self.dynamicType.name, self.dynamicType.locationServicesEnabledKey: enabled, self.dynamicType.authorizationStatusKey: Int(actual.rawValue) - ]) + ]) } - + if let error = error { completion(.Failed(error)) - } - else { + } else { completion(.Satisfied) } } @@ -82,10 +85,11 @@ struct LocationCondition: OperationCondition { A private `Operation` that will request permission to access the user's location, if permission has not already been granted. */ + private class LocationPermissionOperation: Operation { let usage: LocationCondition.Usage var manager: CLLocationManager? - + init(usage: LocationCondition.Usage) { self.usage = usage super.init() @@ -95,43 +99,43 @@ private class LocationPermissionOperation: Operation { */ addCondition(AlertPresentation()) } - + override func execute() { /* Not only do we need to handle the "Not Determined" case, but we also need to handle the "upgrade" (.WhenInUse -> .Always) case. */ switch (CLLocationManager.authorizationStatus(), usage) { - case (.NotDetermined, _), (.AuthorizedWhenInUse, .Always): - dispatch_async(dispatch_get_main_queue()) { - self.requestPermission() - } + case (.NotDetermined, _ ), (.AuthorizedWhenInUse, .Always): + dispatch_async(dispatch_get_main_queue()) { + self.requestPermission() + } - default: - finish() + default: + finish() } } - + private func requestPermission() { manager = CLLocationManager() manager?.delegate = self let key: String - + switch usage { - case .WhenInUse: - key = "NSLocationWhenInUseUsageDescription" - manager?.requestWhenInUseAuthorization() - - case .Always: - key = "NSLocationAlwaysUsageDescription" - manager?.requestAlwaysAuthorization() + case .WhenInUse: + key = "NSLocationWhenInUseUsageDescription" + manager?.requestWhenInUseAuthorization() + + case .Always: + key = "NSLocationAlwaysUsageDescription" + manager?.requestAlwaysAuthorization() } - + // This is helpful when developing the app. assert(NSBundle.mainBundle().objectForInfoDictionaryKey(key) != nil, "Requesting location permission requires the \(key) key in your Info.plist") } - + } extension LocationPermissionOperation: CLLocationManagerDelegate { diff --git a/Source/MutuallyExclusive.swift b/Source/Conditions/MutuallyExclusive.swift similarity index 59% rename from Source/MutuallyExclusive.swift rename to Source/Conditions/MutuallyExclusive.swift index c332af1..53d1e37 100644 --- a/Source/MutuallyExclusive.swift +++ b/Source/Conditions/MutuallyExclusive.swift @@ -9,22 +9,24 @@ This file shows an example of implementing the OperationCondition protocol. import Foundation /// A generic condition for describing kinds of operations that may not execute concurrently. -struct MutuallyExclusive: OperationCondition { - static var name: String { + +public struct MutuallyExclusive: OperationCondition { + public static var name: String { return "MutuallyExclusive<\(T.self)>" } - static var isMutuallyExclusive: Bool { + public static var isMutuallyExclusive: Bool { return true } - - init() { } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public init() { + } + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return nil } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { completion(.Satisfied) } } @@ -33,7 +35,9 @@ struct MutuallyExclusive: OperationCondition { The purpose of this enum is to simply provide a non-constructible type to be used with `MutuallyExclusive`. */ -enum Alert { } + +public enum Alert { +} /// A condition describing that the targeted operation may present an alert. -typealias AlertPresentation = MutuallyExclusive +public typealias AlertPresentation = MutuallyExclusive diff --git a/Source/NegatedCondition.swift b/Source/Conditions/NegatedCondition.swift similarity index 57% rename from Source/NegatedCondition.swift rename to Source/Conditions/NegatedCondition.swift index cd57dd1..10d378b 100644 --- a/Source/NegatedCondition.swift +++ b/Source/Conditions/NegatedCondition.swift @@ -13,42 +13,45 @@ import Foundation This is useful (for example) if you want to only execute an operation if the network is NOT reachable. */ -struct NegatedCondition: OperationCondition { - static var name: String { + +public struct NegatedCondition: OperationCondition { + + public static var name: String { return "Not<\(T.name)>" } - - static var negatedConditionKey: String { + + public static var negatedConditionKey: String { return "NegatedCondition" } - - static var isMutuallyExclusive: Bool { + + public static var isMutuallyExclusive: Bool { return T.isMutuallyExclusive } - - let condition: T - init(condition: T) { + public let condition: T + + public init(condition: T) { self.condition = condition } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return condition.dependencyForOperation(operation) } + - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { - condition.evaluateForOperation(operation) { result in - if result.error == nil { + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + condition.evaluateForOperation(operation) { + result in + if result.error != nil { // If the composed condition failed, then this one succeeded. completion(.Satisfied) - } - else { + } else { // If the composed condition succeeded, then this one failed. let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.negatedConditionKey: self.condition.dynamicType.name + OperationConditionKey: self.dynamicType.name, + self.dynamicType.negatedConditionKey: self.condition.dynamicType.name ]) - + completion(.Failed(error)) } } diff --git a/Source/NoCancelledDependencies.swift b/Source/Conditions/NoCancelledDependencies.swift similarity index 83% rename from Source/NoCancelledDependencies.swift rename to Source/Conditions/NoCancelledDependencies.swift index d88d2f1..018f319 100644 --- a/Source/NoCancelledDependencies.swift +++ b/Source/Conditions/NoCancelledDependencies.swift @@ -13,33 +13,35 @@ import Foundation If any dependency was cancelled, the target operation will be cancelled as well. */ + struct NoCancelledDependencies: OperationCondition { static let name = "NoCancelledDependencies" static let cancelledDependenciesKey = "CancelledDependencies" static let isMutuallyExclusive = false - + init() { // No op. } - + func dependencyForOperation(operation: Operation) -> NSOperation? { return nil } - + func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { // Verify that all of the dependencies executed. - let cancelled = operation.dependencies.filter { $0.cancelled } + let cancelled = operation.dependencies.filter { + $0.cancelled + } if !cancelled.isEmpty { // At least one dependency was cancelled; the condition was not satisfied. let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.cancelledDependenciesKey: cancelled + OperationConditionKey: self.dynamicType.name, + self.dynamicType.cancelledDependenciesKey: cancelled ]) - + completion(.Failed(error)) - } - else { + } else { completion(.Satisfied) } } diff --git a/Source/OperationCondition.swift b/Source/Conditions/OperationCondition.swift similarity index 92% rename from Source/OperationCondition.swift rename to Source/Conditions/OperationCondition.swift index 4829971..19e06b3 100644 --- a/Source/OperationCondition.swift +++ b/Source/Conditions/OperationCondition.swift @@ -14,19 +14,20 @@ let OperationConditionKey = "OperationCondition" A protocol for defining conditions that must be satisfied in order for an operation to begin execution. */ -protocol OperationCondition { + +public protocol OperationCondition { /** The name of the condition. This is used in userInfo dictionaries of `.ConditionFailed` errors as the value of the `OperationConditionKey` key. */ static var name: String { get } - + /** Specifies whether multiple instances of the conditionalized operation may be executing simultaneously. */ static var isMutuallyExclusive: Bool { get } - + /** Some conditions may have the ability to satisfy the condition if another operation is executed first. Use this method to return an operation that @@ -40,24 +41,23 @@ protocol OperationCondition { a single `GroupOperation` that executes multiple operations internally. */ func dependencyForOperation(operation: Operation) -> NSOperation? - + /// Evaluate the condition, to see if it has been satisfied or not. func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) } /** - An enum to indicate whether an `OperationCondition` was satisfied, or if it + An enum to indicate whether an `OperationCondition` was satisfied, or if it failed with an error. */ -enum OperationConditionResult { +public enum OperationConditionResult { case Satisfied case Failed(NSError) - + var error: NSError? { if case .Failed(let error) = self { return error } - return nil } } @@ -70,21 +70,24 @@ struct OperationConditionEvaluator { let conditionGroup = dispatch_group_create() var results = [OperationConditionResult?](count: conditions.count, repeatedValue: nil) - + // Ask each condition to evaluate and store its result in the "results" array. for (index, condition) in conditions.enumerate() { dispatch_group_enter(conditionGroup) - condition.evaluateForOperation(operation) { result in + condition.evaluateForOperation(operation) { + result in results[index] = result dispatch_group_leave(conditionGroup) } } - + // After all the conditions have evaluated, this block will execute. dispatch_group_notify(conditionGroup, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { // Aggregate the errors that occurred, in order. - var failures = results.flatMap { $0?.error } - + var failures = results.flatMap { + $0?.error + } + /* If any of the conditions caused this operation to be cancelled, check for that. @@ -92,7 +95,7 @@ struct OperationConditionEvaluator { if operation.cancelled { failures.append(NSError(code: .ConditionFailed)) } - + completion(failures) } } diff --git a/Source/OperationErrors.swift b/Source/Conditions/OperationErrors.swift similarity index 97% rename from Source/OperationErrors.swift rename to Source/Conditions/OperationErrors.swift index a1cd6e2..d6bc217 100644 --- a/Source/OperationErrors.swift +++ b/Source/Conditions/OperationErrors.swift @@ -16,7 +16,7 @@ enum OperationErrorCode: Int { } extension NSError { - convenience init(code: OperationErrorCode, userInfo: [NSObject: AnyObject]? = nil) { + convenience init(code: OperationErrorCode, userInfo: [NSObject:AnyObject]? = nil) { self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo) } } diff --git a/Source/PassbookCondition.swift b/Source/Conditions/PassbookCondition.swift similarity index 88% rename from Source/PassbookCondition.swift rename to Source/Conditions/PassbookCondition.swift index eb0cce1..423e785 100644 --- a/Source/PassbookCondition.swift +++ b/Source/Conditions/PassbookCondition.swift @@ -7,17 +7,19 @@ This file shows an example of implementing the OperationCondition protocol. */ #if os(iOS) - + import PassKit /// A condition for verifying that Passbook exists and is accessible. + struct PassbookCondition: OperationCondition { - + static let name = "Passbook" static let isMutuallyExclusive = false - - init() { } - + + init() { + } + func dependencyForOperation(operation: Operation) -> NSOperation? { /* There's nothing you can do to make Passbook available if it's not @@ -25,19 +27,18 @@ struct PassbookCondition: OperationCondition { */ return nil } - + func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { if PKPassLibrary.isPassLibraryAvailable() { completion(.Satisfied) - } - else { + } else { let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name + OperationConditionKey: self.dynamicType.name ]) completion(.Failed(error)) } } } - + #endif diff --git a/Source/PhotosCondition.swift b/Source/Conditions/PhotosCondition.swift similarity index 80% rename from Source/PhotosCondition.swift rename to Source/Conditions/PhotosCondition.swift index d606996..92f0509 100644 --- a/Source/PhotosCondition.swift +++ b/Source/Conditions/PhotosCondition.swift @@ -11,18 +11,18 @@ This file shows an example of implementing the OperationCondition protocol. import Photos /// A condition for verifying access to the user's Photos library. -struct PhotosCondition: OperationCondition { +public struct PhotosCondition: OperationCondition { - static let name = "Photos" - static let isMutuallyExclusive = false + public static let name = "Photos" + public static let isMutuallyExclusive = false - init() { } + public init() { } - func dependencyForOperation(operation: Operation) -> NSOperation? { + public func dependencyForOperation(operation: Operation) -> NSOperation? { return PhotosPermissionOperation() } - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { switch PHPhotoLibrary.authorizationStatus() { case .Authorized: completion(.Satisfied) diff --git a/Source/ReachabilityCondition.swift b/Source/Conditions/ReachabilityCondition.swift similarity index 76% rename from Source/ReachabilityCondition.swift rename to Source/Conditions/ReachabilityCondition.swift index 0166de9..aec0d5c 100644 --- a/Source/ReachabilityCondition.swift +++ b/Source/Conditions/ReachabilityCondition.swift @@ -14,46 +14,48 @@ import SystemConfiguration It does *not* perform a long-running reachability check, nor does it respond to changes in reachability. Reachability is evaluated once when the operation to which this is attached is asked about its readiness. */ -struct ReachabilityCondition: OperationCondition { - static let hostKey = "Host" - static let name = "Reachability" - static let isMutuallyExclusive = false - + +public struct ReachabilityCondition: OperationCondition { + public static let hostKey = "Host" + public static let name = "Reachability" + public static let isMutuallyExclusive = false + let host: NSURL - - - init(host: NSURL) { + + + public init(host: NSURL) { self.host = host } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return nil } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { - ReachabilityController.requestReachability(host) { reachable in + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + ReachabilityController.requestReachability(host) { + reachable in if reachable { completion(.Satisfied) - } - else { + } else { let error = NSError(code: .ConditionFailed, userInfo: [ - OperationConditionKey: self.dynamicType.name, - self.dynamicType.hostKey: self.host + OperationConditionKey: self.dynamicType.name, + self.dynamicType.hostKey: self.host ]) - + completion(.Failed(error)) } } } - + } /// A private singleton that maintains a basic cache of `SCNetworkReachability` objects. + private class ReachabilityController { static var reachabilityRefs = [String: SCNetworkReachability]() static let reachabilityQueue = dispatch_queue_create("Operations.Reachability", DISPATCH_QUEUE_SERIAL) - + static func requestReachability(url: NSURL, completionHandler: (Bool) -> Void) { if let host = url.host { dispatch_async(reachabilityQueue) { @@ -63,10 +65,10 @@ private class ReachabilityController { let hostString = host as NSString ref = SCNetworkReachabilityCreateWithName(nil, hostString.UTF8String) } - + if let ref = ref { self.reachabilityRefs[host] = ref - + var reachable = false var flags: SCNetworkReachabilityFlags = [] if SCNetworkReachabilityGetFlags(ref, &flags) != 0 { @@ -79,13 +81,11 @@ private class ReachabilityController { reachable = flags.contains(.Reachable) } completionHandler(reachable) - } - else { + } else { completionHandler(false) } } - } - else { + } else { completionHandler(false) } } diff --git a/Source/RemoteNotificationCondition.swift b/Source/Conditions/RemoteNotificationCondition.swift similarity index 78% rename from Source/RemoteNotificationCondition.swift rename to Source/Conditions/RemoteNotificationCondition.swift index b0bca99..88faefb 100644 --- a/Source/RemoteNotificationCondition.swift +++ b/Source/Conditions/RemoteNotificationCondition.swift @@ -9,7 +9,7 @@ This file shows an example of implementing the OperationCondition protocol. #if os(iOS) import UIKit - + private let RemoteNotificationQueue = OperationQueue() private let RemoteNotificationName = "RemoteNotificationPermissionNotification" @@ -19,49 +19,52 @@ private enum RemoteRegistrationResult { } /// A condition for verifying that the app has the ability to receive push notifications. -struct RemoteNotificationCondition: OperationCondition { - static let name = "RemoteNotification" - static let isMutuallyExclusive = false + +public struct RemoteNotificationCondition: OperationCondition { + public static let name = "RemoteNotification" + public static let isMutuallyExclusive = false + static func didReceiveNotificationToken(token: NSData) { NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [ - "token": token + "token": token ]) } - + static func didFailToRegister(error: NSError) { NSNotificationCenter.defaultCenter().postNotificationName(RemoteNotificationName, object: nil, userInfo: [ - "error": error + "error": error ]) } - - let application: UIApplication - - init(application: UIApplication) { + + public let application: UIApplication + + public init(application: UIApplication) { self.application = application } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return RemoteNotificationPermissionOperation(application: application, handler: { _ in }) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { /* Since evaluation requires executing an operation, use a private operation queue. */ - RemoteNotificationQueue.addOperation(RemoteNotificationPermissionOperation(application: application) { result in + RemoteNotificationQueue.addOperation(RemoteNotificationPermissionOperation(application: application) { + result in switch result { - case .Token(_): - completion(.Satisfied) + case .Token(_ ): + completion(.Satisfied) - case .Error(let underlyingError): - let error = NSError(code: .ConditionFailed, userInfo: [ + case .Error(let underlyingError): + let error = NSError(code: .ConditionFailed, userInfo: [ OperationConditionKey: self.dynamicType.name, NSUnderlyingErrorKey: underlyingError - ]) + ]) - completion(.Failed(error)) + completion(.Failed(error)) } }) } @@ -78,50 +81,49 @@ struct RemoteNotificationCondition: OperationCondition { `RemoteNotificationCondition.didFailToRegister(_:)` in the appropriate `UIApplicationDelegate` method, as shown in the `AppDelegate.swift` file. */ + private class RemoteNotificationPermissionOperation: Operation { let application: UIApplication private let handler: RemoteRegistrationResult -> Void - + private init(application: UIApplication, handler: RemoteRegistrationResult -> Void) { self.application = application self.handler = handler super.init() - + /* This operation cannot run at the same time as any other remote notification permission operation. */ addCondition(MutuallyExclusive()) } - + override func execute() { dispatch_async(dispatch_get_main_queue()) { let notificationCenter = NSNotificationCenter.defaultCenter() - + notificationCenter.addObserver(self, selector: "didReceiveResponse:", name: RemoteNotificationName, object: nil) - + self.application.registerForRemoteNotifications() } } - + @objc func didReceiveResponse(notification: NSNotification) { NSNotificationCenter.defaultCenter().removeObserver(self) - + let userInfo = notification.userInfo if let token = userInfo?["token"] as? NSData { handler(.Token(token)) - } - else if let error = userInfo?["error"] as? NSError { + } else if let error = userInfo?["error"] as? NSError { handler(.Error(error)) - } - else { + } else { fatalError("Received a notification without a token and without an error.") } finish() } } - + #endif diff --git a/Source/SilentCondition.swift b/Source/Conditions/SilentCondition.swift similarity index 68% rename from Source/SilentCondition.swift rename to Source/Conditions/SilentCondition.swift index 3ed5a2a..595fcb4 100644 --- a/Source/SilentCondition.swift +++ b/Source/Conditions/SilentCondition.swift @@ -14,27 +14,28 @@ import Foundation the user's location, but you do not want to prompt them for permission if you do not already have it. */ -struct SilentCondition: OperationCondition { + +public struct SilentCondition: OperationCondition { let condition: T - - static var name: String { + + public static var name: String { return "Silent<\(T.name)>" } - - static var isMutuallyExclusive: Bool { + + public static var isMutuallyExclusive: Bool { return T.isMutuallyExclusive } - - init(condition: T) { + + public init(condition: T) { self.condition = condition } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { // Returning nil means we will never a dependency to be generated. return nil } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { condition.evaluateForOperation(operation, completion: completion) } } diff --git a/Source/UserNotificationCondition.swift b/Source/Conditions/UserNotificationCondition.swift similarity index 72% rename from Source/UserNotificationCondition.swift rename to Source/Conditions/UserNotificationCondition.swift index b4e7505..2357f04 100644 --- a/Source/UserNotificationCondition.swift +++ b/Source/Conditions/UserNotificationCondition.swift @@ -14,25 +14,28 @@ import UIKit A condition for verifying that we can present alerts to the user via `UILocalNotification` and/or remote notifications. */ -struct UserNotificationCondition: OperationCondition { - - enum Behavior { + +public struct UserNotificationCondition: OperationCondition { + + public enum Behavior { /// Merge the new `UIUserNotificationSettings` with the `currentUserNotificationSettings`. case Merge /// Replace the `currentUserNotificationSettings` with the new `UIUserNotificationSettings`. case Replace } + + public static let name = "UserNotification" + public static let isMutuallyExclusive = false - static let name = "UserNotification" static let currentSettings = "CurrentUserNotificationSettings" static let desiredSettings = "DesiredUserNotificationSettigns" - static let isMutuallyExclusive = false - - let settings: UIUserNotificationSettings - let application: UIApplication - let behavior: Behavior - + + + public let settings: UIUserNotificationSettings + public let application: UIApplication + public let behavior: Behavior + /** The designated initializer. @@ -48,35 +51,35 @@ struct UserNotificationCondition: OperationCondition { `application`. You may also specify `.Replace`, which means the `settings` will overwrite the exisiting settings. */ - init(settings: UIUserNotificationSettings, application: UIApplication, behavior: Behavior = .Merge) { + public init(settings: UIUserNotificationSettings, application: UIApplication, behavior: Behavior = .Merge) { self.settings = settings self.application = application self.behavior = behavior } - - func dependencyForOperation(operation: Operation) -> NSOperation? { + + public func dependencyForOperation(operation: Operation) -> NSOperation? { return UserNotificationPermissionOperation(settings: settings, application: application, behavior: behavior) } - - func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { + + public func evaluateForOperation(operation: Operation, completion: OperationConditionResult -> Void) { let result: OperationConditionResult - + let current = application.currentUserNotificationSettings() - switch (current, settings) { - case (let current?, let settings) where current.contains(settings): - result = .Satisfied + switch (current, settings) { + case (let current?, let settings) where current.contains(settings): + result = .Satisfied - default: - let error = NSError(code: .ConditionFailed, userInfo: [ + default: + let error = NSError(code: .ConditionFailed, userInfo: [ OperationConditionKey: self.dynamicType.name, self.dynamicType.currentSettings: current ?? NSNull(), self.dynamicType.desiredSettings: settings - ]) - - result = .Failed(error) + ]) + + result = .Failed(error) } - + completion(result) } } @@ -85,38 +88,39 @@ struct UserNotificationCondition: OperationCondition { A private `Operation` subclass to register a `UIUserNotificationSettings` object with a `UIApplication`, prompting the user for permission if necessary. */ + private class UserNotificationPermissionOperation: Operation { let settings: UIUserNotificationSettings let application: UIApplication let behavior: UserNotificationCondition.Behavior - + init(settings: UIUserNotificationSettings, application: UIApplication, behavior: UserNotificationCondition.Behavior) { self.settings = settings self.application = application self.behavior = behavior - + super.init() addCondition(AlertPresentation()) } - + override func execute() { dispatch_async(dispatch_get_main_queue()) { let current = self.application.currentUserNotificationSettings() - + let settingsToRegister: UIUserNotificationSettings - + switch (current, self.behavior) { - case (let currentSettings?, .Merge): - settingsToRegister = currentSettings.settingsByMerging(self.settings) + case (let currentSettings?, .Merge): + settingsToRegister = currentSettings.settingsByMerging(self.settings) - default: - settingsToRegister = self.settings + default: + settingsToRegister = self.settings } - + self.application.registerUserNotificationSettings(settingsToRegister) } } } - + #endif diff --git a/Source/CKContainer+Operations.swift b/Source/Extensions/CKContainer+Operations.swift similarity index 83% rename from Source/CKContainer+Operations.swift rename to Source/Extensions/CKContainer+Operations.swift index 8d6e564..e3d331f 100644 --- a/Source/CKContainer+Operations.swift +++ b/Source/Extensions/CKContainer+Operations.swift @@ -8,7 +8,7 @@ A convenient extension to CloudKit.CKContainer. import CloudKit -extension CKContainer { +internal extension CKContainer { /** Verify that the current user has certain permissions for the `CKContainer`, and potentially requesting the permission if necessary. @@ -34,30 +34,28 @@ extension CKContainer { `CKContainer`. */ private func verifyAccountStatus(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) { - container.accountStatusWithCompletionHandler { accountStatus, accountError in + container.accountStatusWithCompletionHandler { + accountStatus, accountError in if accountStatus == .Available { if permission != [] { verifyPermission(container, permission: permission, shouldRequest: shouldRequest, completion: completion) - } - else { + } else { completion(nil) } - } - else { + } else { completion(accountError) } } } private func verifyPermission(container: CKContainer, permission: CKApplicationPermissions, shouldRequest: Bool, completion: NSError? -> Void) { - container.statusForApplicationPermission(permission) { permissionStatus, permissionError in + container.statusForApplicationPermission(permission) { + permissionStatus, permissionError in if permissionStatus == .Granted { completion(nil) - } - else if permissionStatus == .InitialState && shouldRequest { + } else if permissionStatus == .InitialState && shouldRequest { requestPermission(container, permission: permission, completion: completion) - } - else { + } else { completion(permissionError) } } @@ -65,11 +63,11 @@ private func verifyPermission(container: CKContainer, permission: CKApplicationP private func requestPermission(container: CKContainer, permission: CKApplicationPermissions, completion: NSError? -> Void) { dispatch_async(dispatch_get_main_queue()) { - container.requestApplicationPermission(permission) { requestStatus, requestError in + container.requestApplicationPermission(permission) { + requestStatus, requestError in if requestStatus == .Granted { completion(nil) - } - else { + } else { completion(requestError) } } diff --git a/Source/Dictionary+Operations.swift b/Source/Extensions/Dictionary+Operations.swift similarity index 85% rename from Source/Dictionary+Operations.swift rename to Source/Extensions/Dictionary+Operations.swift index 8ec8d80..5d682ec 100644 --- a/Source/Dictionary+Operations.swift +++ b/Source/Extensions/Dictionary+Operations.swift @@ -6,7 +6,7 @@ Abstract: A convenient extension to Swift.Dictionary. */ -extension Dictionary { +internal extension Dictionary { /** It's not uncommon to want to turn a sequence of values into a dictionary, where each value is keyed by some unique identifier. This initializer will @@ -19,7 +19,7 @@ extension Dictionary { be used as the key for the value in the `Dictionary`. If the closure returns `nil`, then the value will be omitted from the `Dictionary`. */ - init(sequence: Sequence, keyMapper: Value -> Key?) { + init(sequence: Sequence, keyMapper: Value -> Key?) { self.init() for item in sequence { diff --git a/Source/NSOperation+Operations.swift b/Source/Extensions/NSOperation+Operations.swift similarity index 94% rename from Source/NSOperation+Operations.swift rename to Source/Extensions/NSOperation+Operations.swift index e5c44a7..4db65be 100644 --- a/Source/NSOperation+Operations.swift +++ b/Source/Extensions/NSOperation+Operations.swift @@ -8,7 +8,7 @@ A convenient extension to Foundation.NSOperation. import Foundation -extension NSOperation { +public extension NSOperation { /** Add a completion block to be executed after the `NSOperation` enters the "finished" state. @@ -23,8 +23,7 @@ extension NSOperation { existing() block() } - } - else { + } else { completionBlock = block } } diff --git a/Source/UIUserNotifications+Operations.swift b/Source/Extensions/UIUserNotifications+Operations.swift similarity index 80% rename from Source/UIUserNotifications+Operations.swift rename to Source/Extensions/UIUserNotifications+Operations.swift index cc1af71..53afa37 100644 --- a/Source/UIUserNotifications+Operations.swift +++ b/Source/Extensions/UIUserNotifications+Operations.swift @@ -10,37 +10,42 @@ A convenient extension to UIKit.UIUserNotificationSettings. import UIKit -extension UIUserNotificationSettings { +public extension UIUserNotificationSettings { + /// Check to see if one Settings object is a superset of another Settings object. - func contains(settings: UIUserNotificationSettings) -> Bool { + public func contains(settings: UIUserNotificationSettings) -> Bool { // our types must contain all of the other types if !types.contains(settings.types) { return false } - + let otherCategories = settings.categories ?? [] let myCategories = categories ?? [] - + return myCategories.isSupersetOf(otherCategories) } - + /** Merge two Settings objects together. `UIUserNotificationCategories` with the same identifier are considered equal. */ - func settingsByMerging(settings: UIUserNotificationSettings) -> UIUserNotificationSettings { + public func settingsByMerging(settings: UIUserNotificationSettings) -> UIUserNotificationSettings { let mergedTypes = types.union(settings.types) - + let myCategories = categories ?? [] - var existingCategoriesByIdentifier = Dictionary(sequence: myCategories) { $0.identifier } - + var existingCategoriesByIdentifier = Dictionary(sequence: myCategories) { + $0.identifier + } + let newCategories = settings.categories ?? [] - let newCategoriesByIdentifier = Dictionary(sequence: newCategories) { $0.identifier } - + let newCategoriesByIdentifier = Dictionary(sequence: newCategories) { + $0.identifier + } + for (newIdentifier, newCategory) in newCategoriesByIdentifier { existingCategoriesByIdentifier[newIdentifier] = newCategory } - + let mergedCategories = Set(existingCategoriesByIdentifier.values) return UIUserNotificationSettings(forTypes: mergedTypes, categories: mergedCategories) } diff --git a/Source/BlockObserver.swift b/Source/Observer/BlockObserver.swift similarity index 64% rename from Source/BlockObserver.swift rename to Source/Observer/BlockObserver.swift index 826ede5..8d759bf 100644 --- a/Source/BlockObserver.swift +++ b/Source/Observer/BlockObserver.swift @@ -12,30 +12,31 @@ import Foundation The `BlockObserver` is a way to attach arbitrary blocks to significant events in an `Operation`'s lifecycle. */ -struct BlockObserver: OperationObserver { + +public struct BlockObserver: OperationObserver { // MARK: Properties - + private let startHandler: (Operation -> Void)? private let produceHandler: ((Operation, NSOperation) -> Void)? private let finishHandler: ((Operation, [NSError]) -> Void)? - - init(startHandler: (Operation -> Void)? = nil, produceHandler: ((Operation, NSOperation) -> Void)? = nil, finishHandler: ((Operation, [NSError]) -> Void)? = nil) { + + public init(startHandler: (Operation -> Void)? = nil, produceHandler: ((Operation, NSOperation) -> Void)? = nil, finishHandler: ((Operation, [NSError]) -> Void)? = nil) { self.startHandler = startHandler self.produceHandler = produceHandler self.finishHandler = finishHandler } - + // MARK: OperationObserver - - func operationDidStart(operation: Operation) { + + public func operationDidStart(operation: Operation) { startHandler?(operation) } - - func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { + + public func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { produceHandler?(operation, newOperation) } - - func operationDidFinish(operation: Operation, errors: [NSError]) { + + public func operationDidFinish(operation: Operation, errors: [NSError]) { finishHandler?(operation, errors) } } diff --git a/Source/Observer/NetworkObserver.swift b/Source/Observer/NetworkObserver.swift new file mode 100644 index 0000000..78566ef --- /dev/null +++ b/Source/Observer/NetworkObserver.swift @@ -0,0 +1,121 @@ +/* +Copyright (C) 2015 Apple Inc. All Rights Reserved. +See LICENSE.txt for this sample’s licensing information + +Abstract: +Contains the code to manage the visibility of the network activity indicator +*/ + +import UIKit + +/** +An `OperationObserver` that will cause the network activity indicator to appear +as long as the `Operation` to which it is attached is executing. +*/ + +public struct NetworkObserver: OperationObserver { + + public init() { + } + + public func operationDidStart(operation: Operation) { + dispatch_async(dispatch_get_main_queue()) { + // increment the network indicator's "retain count" + NetworkIndicatorController.sharedIndicatorController.networkActivityDidStart() + } + } + + public func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { + + } + + public func operationDidFinish(operation: Operation, errors: [NSError]) { + dispatch_async(dispatch_get_main_queue()) { + // Decrement the network indicator's "reference count". + NetworkIndicatorController.sharedIndicatorController.networkActivityDidEnd() + } + } + +} + +/// A singleton to manage a visual "reference count" on the network activity indicator. + +private class NetworkIndicatorController { + // MARK: Properties + + static let sharedIndicatorController = NetworkIndicatorController() + + private var activityCount = 0 + + private var visibilityTimer: Timer? + + // MARK: Methods + + func networkActivityDidStart() { + assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") + + activityCount++ + + updateIndicatorVisibility() + } + + func networkActivityDidEnd() { + assert(NSThread.isMainThread(), "Altering network activity indicator state can only be done on the main thread.") + + activityCount-- + + updateIndicatorVisibility() + } + + private func updateIndicatorVisibility() { + if activityCount > 0 { + showIndicator() + } else { + /* + To prevent the indicator from flickering on and off, we delay the + hiding of the indicator by one second. This provides the chance + to come in and invalidate the timer before it fires. + */ + visibilityTimer = Timer(interval: 1.0) { + self.hideIndicator() + } + } + } + + private func showIndicator() { + visibilityTimer?.cancel() + visibilityTimer = nil + UIApplication.sharedApplication().networkActivityIndicatorVisible = true + } + + private func hideIndicator() { + visibilityTimer?.cancel() + visibilityTimer = nil + UIApplication.sharedApplication().networkActivityIndicatorVisible = false + } +} + +/// Essentially a cancellable `dispatch_after`. + +class Timer { + // MARK: Properties + + private var isCancelled = false + + // MARK: Initialization + + init(interval: NSTimeInterval, handler: dispatch_block_t) { + let when = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))) + + dispatch_after(when, dispatch_get_main_queue()) { + [weak self] in + if self?.isCancelled != true { + handler() + } + } + } + + func cancel() { + isCancelled = true + } +} \ No newline at end of file diff --git a/Source/OperationObserver.swift b/Source/Observer/OperationObserver.swift similarity index 94% rename from Source/OperationObserver.swift rename to Source/Observer/OperationObserver.swift index ee2b994..b8cbcec 100644 --- a/Source/OperationObserver.swift +++ b/Source/Observer/OperationObserver.swift @@ -12,18 +12,19 @@ import Foundation The protocol that types may implement if they wish to be notified of significant operation lifecycle events. */ -protocol OperationObserver { - + +public protocol OperationObserver { + /// Invoked immediately prior to the `Operation`'s `execute()` method. func operationDidStart(operation: Operation) - + /// Invoked when `Operation.produceOperation(_:)` is executed. func operation(operation: Operation, didProduceOperation newOperation: NSOperation) - + /** Invoked as an `Operation` finishes, along with any errors produced during execution (or readiness evaluation). */ func operationDidFinish(operation: Operation, errors: [NSError]) - + } diff --git a/Source/TimeoutObserver.swift b/Source/Observer/TimeoutObserver.swift similarity index 75% rename from Source/TimeoutObserver.swift rename to Source/Observer/TimeoutObserver.swift index 76c0d37..f588562 100644 --- a/Source/TimeoutObserver.swift +++ b/Source/Observer/TimeoutObserver.swift @@ -12,22 +12,23 @@ import Foundation `TimeoutObserver` is a way to make an `Operation` automatically time out and cancel after a specified time interval. */ -struct TimeoutObserver: OperationObserver { + +public struct TimeoutObserver: OperationObserver { // MARK: Properties static let timeoutKey = "Timeout" - + private let timeout: NSTimeInterval - + // MARK: Initialization - - init(timeout: NSTimeInterval) { + + public init(timeout: NSTimeInterval) { self.timeout = timeout } - + // MARK: OperationObserver - - func operationDidStart(operation: Operation) { + + public func operationDidStart(operation: Operation) { // When the operation starts, queue up a block to cause it to time out. let when = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * Double(NSEC_PER_SEC))) @@ -38,7 +39,7 @@ struct TimeoutObserver: OperationObserver { */ if !operation.finished && !operation.cancelled { let error = NSError(code: .ExecutionFailed, userInfo: [ - self.dynamicType.timeoutKey: self.timeout + self.dynamicType.timeoutKey: self.timeout ]) operation.cancelWithError(error) @@ -46,11 +47,11 @@ struct TimeoutObserver: OperationObserver { } } - func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { + public func operation(operation: Operation, didProduceOperation newOperation: NSOperation) { // No op. } - func operationDidFinish(operation: Operation, errors: [NSError]) { + public func operationDidFinish(operation: Operation, errors: [NSError]) { // No op. } } diff --git a/Source/Operation/AlertOperation.swift b/Source/Operation/AlertOperation.swift new file mode 100644 index 0000000..7d266c2 --- /dev/null +++ b/Source/Operation/AlertOperation.swift @@ -0,0 +1,96 @@ +// +// AlertOperation.swift +// LMOperations +// +// Created by Phillipp Bertram on 26/06/15. +// Copyright (c) 2015 LMIS AG. All rights reserved. +// + +#if os(iOS) + +import UIKit + +public class AlertOperation: Operation { + + private let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .Alert) + private let presentationContext: UIViewController? + + /// title of alert + public var title: String? { + get { + return alertController.title + } + + set { + alertController.title = newValue + name = newValue + } + } + + /// message of alert + public var message: String? { + get { + return alertController.message + } + + set { + alertController.message = newValue + } + } + + // MARK: Initialization + + public init(presentationContext: UIViewController? = nil) { + self.presentationContext = presentationContext ?? UIApplication.sharedApplication().keyWindow?.rootViewController + + super.init() + + addCondition(AlertPresentation()) + + /* + This operation modifies the view controller hierarchy. + Doing this while other such operations are executing can lead to + inconsistencies in UIKit. So, let's make them mutally exclusive. + */ + addCondition(MutuallyExclusive()) + } + + + /** + Adds an action to the alert. + + - parameter title: The Title for the alert. + - parameter style: The Alertstyle. If none if given `.Default` will be used + - parameter handler: The handler that will be invoked. + */ + public func addAction(title: String, style: UIAlertActionStyle = .Default, handler: AlertOperation -> Void = { + _ in }) { + let action = UIAlertAction(title: title, style: style) { + [weak self] _ in + if let strongSelf = self { + handler(strongSelf) + } + + self?.finish() + } + + alertController.addAction(action) + } + + + override public func execute() { + if let presentationContext = presentationContext { + dispatch_async(dispatch_get_main_queue()) { + if self.alertController.actions.isEmpty { + self.addAction("OK") + } + + presentationContext.presentViewController(self.alertController, animated: true, completion: nil) + } + } else { + finish() + } + } +} + +#endif diff --git a/Source/BlockOperation.swift b/Source/Operation/BlockOperation.swift similarity index 83% rename from Source/BlockOperation.swift rename to Source/Operation/BlockOperation.swift index abc4bac..16e280a 100644 --- a/Source/BlockOperation.swift +++ b/Source/Operation/BlockOperation.swift @@ -9,12 +9,14 @@ This code shows how to create a simple subclass of Operation. import Foundation /// A closure type that takes a closure as its parameter. -typealias OperationBlock = (Void -> Void) -> Void +public typealias OperationBlock = (Void -> Void) -> Void /// A sublcass of `Operation` to execute a closure. -class BlockOperation: Operation { - private let block: OperationBlock? + +public class BlockOperation: Operation { + private let block: OperationBlock? + /** The designated initializer. @@ -24,11 +26,11 @@ class BlockOperation: Operation { will never finish executing. If this parameter is `nil`, the operation will immediately finish. */ - init(block: OperationBlock? = nil) { + public init(block: OperationBlock? = nil) { self.block = block super.init() } - + /** A convenience initializer to execute a block on the main queue. @@ -37,23 +39,26 @@ class BlockOperation: Operation { the designated initializer). The operation will be automatically ended after the `mainQueueBlock` is executed. */ - convenience init(mainQueueBlock: dispatch_block_t) { - self.init(block: { continuation in + public convenience init(mainQueueBlock: dispatch_block_t) { + self.init(block: { + continuation in dispatch_async(dispatch_get_main_queue()) { mainQueueBlock() continuation() } }) } - - override func execute() { + + + override public func execute() { guard let block = block else { finish() return } - + block { self.finish() } } + } diff --git a/Source/DelayOperation.swift b/Source/Operation/DelayOperation.swift similarity index 81% rename from Source/DelayOperation.swift rename to Source/Operation/DelayOperation.swift index e366840..f11529b 100644 --- a/Source/DelayOperation.swift +++ b/Source/Operation/DelayOperation.swift @@ -20,42 +20,43 @@ import Foundation If the interval is negative, or the `NSDate` is in the past, then this operation immediately finishes. */ -class DelayOperation: Operation { + +public class DelayOperation: Operation { // MARK: Types private enum Delay { case Interval(NSTimeInterval) case Date(NSDate) } - + // MARK: Properties - + private let delay: Delay - + // MARK: Initialization - - init(interval: NSTimeInterval) { + + public init(interval: NSTimeInterval) { delay = .Interval(interval) super.init() } - - init(until date: NSDate) { + + public init(until date: NSDate) { delay = .Date(date) super.init() } - - override func execute() { + + override public func execute() { let interval: NSTimeInterval - + // Figure out how long we should wait for. switch delay { - case .Interval(let theInterval): - interval = theInterval + case .Interval(let theInterval): + interval = theInterval - case .Date(let date): - interval = date.timeIntervalSinceNow + case .Date(let date): + interval = date.timeIntervalSinceNow } - + guard interval > 0 else { finish() return @@ -69,8 +70,8 @@ class DelayOperation: Operation { } } } - - override func cancel() { + + override public func cancel() { super.cancel() // Cancelling the operation means we don't want to wait anymore. self.finish() diff --git a/Source/GroupOperation.swift b/Source/Operation/GroupOperation.swift similarity index 80% rename from Source/GroupOperation.swift rename to Source/Operation/GroupOperation.swift index 88ff78c..602d327 100644 --- a/Source/GroupOperation.swift +++ b/Source/Operation/GroupOperation.swift @@ -21,60 +21,62 @@ import Foundation subsequent operations (still within the outer `GroupOperation`) that will all be executed before the rest of the operations in the initial chain of operations. */ -class GroupOperation: Operation { + +public class GroupOperation: Operation { + private let internalQueue = OperationQueue() private let finishingOperation = NSBlockOperation(block: {}) private var aggregatedErrors = [NSError]() - - convenience init(operations: NSOperation...) { + + public convenience init(operations: NSOperation...) { self.init(operations: operations) } - - init(operations: [NSOperation]) { + + public init(operations: [NSOperation]) { super.init() - + internalQueue.suspended = true - + internalQueue.delegate = self for operation in operations { internalQueue.addOperation(operation) } } - - override func cancel() { + + override public func cancel() { internalQueue.cancelAllOperations() super.cancel() } - - override func execute() { + + override public func execute() { internalQueue.suspended = false internalQueue.addOperation(finishingOperation) } - - func addOperation(operation: NSOperation) { + + public func addOperation(operation: NSOperation) { internalQueue.addOperation(operation) } - + /** Note that some part of execution has produced an error. Errors aggregated through this method will be included in the final array of errors reported to observers and to the `finished(_:)` method. */ - final func aggregateError(error: NSError) { + public final func aggregateError(error: NSError) { aggregatedErrors.append(error) } - - func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) { + + public func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) { // For use by subclassers. } } extension GroupOperation: OperationQueueDelegate { - final func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) { + final public func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) { assert(!finishingOperation.finished && !finishingOperation.executing, "cannot add new operations to a group after the group has completed") - + /* Some operation in this group has produced a new operation to execute. We want to allow that operation to execute before the group completes, @@ -84,15 +86,14 @@ extension GroupOperation: OperationQueueDelegate { finishingOperation.addDependency(operation) } } - - final func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) { + + final public func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) { aggregatedErrors.extend(errors) - + if operation === finishingOperation { internalQueue.suspended = true finish(aggregatedErrors) - } - else { + } else { operationDidFinish(operation, withErrors: errors) } } diff --git a/Source/LocationOperation.swift b/Source/Operation/LocationOperation.swift similarity index 76% rename from Source/LocationOperation.swift rename to Source/Operation/LocationOperation.swift index 5aee710..493d7c5 100644 --- a/Source/LocationOperation.swift +++ b/Source/Operation/LocationOperation.swift @@ -15,24 +15,25 @@ import CoreLocation prompt for `WhenInUse` location authorization, if the app does not already have it. */ -class LocationOperation: Operation, CLLocationManagerDelegate { + +public class LocationOperation: Operation, CLLocationManagerDelegate { // MARK: Properties - + private let accuracy: CLLocationAccuracy private var manager: CLLocationManager? private let handler: CLLocation -> Void - + // MARK: Initialization - - init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) { + + public init(accuracy: CLLocationAccuracy, locationHandler: CLLocation -> Void) { self.accuracy = accuracy self.handler = locationHandler super.init() addCondition(LocationCondition(usage: .WhenInUse)) addCondition(MutuallyExclusive()) } - - override func execute() { + + override public func execute() { dispatch_async(dispatch_get_main_queue()) { /* `CLLocationManager` needs to be created on a thread with an active @@ -42,34 +43,34 @@ class LocationOperation: Operation, CLLocationManagerDelegate { manager.desiredAccuracy = self.accuracy manager.delegate = self manager.startUpdatingLocation() - + self.manager = manager } } - - override func cancel() { + + override public func cancel() { dispatch_async(dispatch_get_main_queue()) { self.stopLocationUpdates() super.cancel() } } - + private func stopLocationUpdates() { manager?.stopUpdatingLocation() manager = nil } - + // MARK: CLLocationManagerDelegate - - func locationManager(manager: CLLocationManager, didUpdateLocations locations: [AnyObject]) { - if let locations = locations as? [CLLocation], location = locations.last where location.horizontalAccuracy <= accuracy { + + public func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + if let location = locations.last where location.horizontalAccuracy <= accuracy { stopLocationUpdates() handler(location) finish() } } - - func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { + + public func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { stopLocationUpdates() finishWithError(error) } diff --git a/Source/Operation.swift b/Source/Operation/Operation.swift similarity index 82% rename from Source/Operation.swift rename to Source/Operation/Operation.swift index dbd689c..10f5e32 100644 --- a/Source/Operation.swift +++ b/Source/Operation/Operation.swift @@ -14,59 +14,60 @@ import Foundation extended readiness requirements, as well as notify many interested parties about interesting operation state changes */ -class Operation: NSOperation { - + +public class Operation: NSOperation { + // use the KVO mechanism to indicate that changes to "state" affect other properties as well class func keyPathsForValuesAffectingIsReady() -> Set { return ["state"] } - + class func keyPathsForValuesAffectingIsExecuting() -> Set { return ["state"] } - + class func keyPathsForValuesAffectingIsFinished() -> Set { return ["state"] } - + class func keyPathsForValuesAffectingIsCancelled() -> Set { return ["state"] } - + // MARK: State Management - + private enum State: Int, Comparable { /// The initial state of an `Operation`. case Initialized - + /// The `Operation` is ready to begin evaluating conditions. case Pending - + /// The `Operation` is evaluating conditions. case EvaluatingConditions - + /** The `Operation`'s conditions have all been satisfied, and it is ready to execute. */ case Ready - + /// The `Operation` is executing. case Executing - + /** Execution of the `Operation` has finished, but it has not yet notified the queue of this. */ case Finishing - + /// The `Operation` has finished executing. case Finished - + /// The `Operation` has been cancelled. case Cancelled } - + /** Indicates that the Operation can now begin to evaluate readiness conditions, if appropriate. @@ -74,7 +75,7 @@ class Operation: NSOperation { func willEnqueue() { state = .Pending } - + /// Private storage for the `state` property that will be KVO observed. private var _state = State.Initialized @@ -82,45 +83,45 @@ class Operation: NSOperation { get { return _state } - + set(newState) { // Manually fire the KVO notifications for state change, since this is "private". willChangeValueForKey("state") switch (_state, newState) { - case (.Cancelled, _): - break // cannot leave the cancelled state - case (.Finished, _): - break // cannot leave the finished state - default: - assert(_state != newState, "Performing invalid cyclic state transition.") - _state = newState + case (.Cancelled, _ ): + break // cannot leave the cancelled state + case (.Finished, _ ): + break // cannot leave the finished state + default: + assert(_state != newState, "Performing invalid cyclic state transition.") + _state = newState } - + didChangeValueForKey("state") } } - + // Here is where we extend our definition of "readiness". - override var ready: Bool { + override public var ready: Bool { switch state { - case .Pending: - if super.ready { - evaluateConditions() - } - - return false - - case .Ready: - return super.ready - - default: - return false + case .Pending: + if super.ready { + evaluateConditions() + } + + return false + + case .Ready: + return super.ready + + default: + return false } } - - var userInitiated: Bool { + + public var userInitiated: Bool { get { return qualityOfService == .UserInitiated } @@ -131,74 +132,74 @@ class Operation: NSOperation { qualityOfService = newValue ? .UserInitiated : .Default } } - - override var executing: Bool { + + override public var executing: Bool { return state == .Executing } - - override var finished: Bool { + + override public var finished: Bool { return state == .Finished } - - override var cancelled: Bool { + + override public var cancelled: Bool { return state == .Cancelled } - + private func evaluateConditions() { assert(state == .Pending, "evaluateConditions() was called out-of-order") state = .EvaluatingConditions - - OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in + + OperationConditionEvaluator.evaluate(conditions, operation: self) { + failures in if failures.isEmpty { // If there were no errors, we may proceed. self.state = .Ready - } - else { + } else { self.state = .Cancelled self.finish(failures) } } } - + // MARK: Observers and Conditions - + private(set) var conditions = [OperationCondition]() - func addCondition(condition: OperationCondition) { + public func addCondition(condition: OperationCondition) { assert(state < .EvaluatingConditions, "Cannot modify conditions after execution has begun.") conditions.append(condition) } - + private(set) var observers = [OperationObserver]() - - func addObserver(observer: OperationObserver) { + + public func addObserver(observer: OperationObserver) { assert(state < .Executing, "Cannot modify observers after execution has begun.") - + observers.append(observer) } - - override func addDependency(operation: NSOperation) { + + override public func addDependency(operation: NSOperation) { assert(state <= .Executing, "Dependencies cannot be modified after execution has begun.") super.addDependency(operation) } - + // MARK: Execution and Cancellation - - override final func start() { + + override public final func start() { assert(state == .Ready, "This operation must be performed on an operation queue.") state = .Executing - + for observer in observers { observer.operationDidStart(self) } - + execute() } - + /** `execute()` is the entry point of execution for all `Operation` subclasses. If you subclass `Operation` and wish to customize its execution, you would @@ -209,33 +210,33 @@ class Operation: NSOperation { finished its execution, and that operations dependent on yours can re-evaluate their readiness state. */ - func execute() { + public func execute() { print("\(self.dynamicType) must override `execute()`.") finish() } - + private var _internalErrors = [NSError]() - override func cancel() { + override public func cancel() { cancelWithError() } - - func cancelWithError(error: NSError? = nil) { + + public func cancelWithError(error: NSError? = nil) { if let error = error { _internalErrors.append(error) } - + state = .Cancelled } - - final func produceOperation(operation: NSOperation) { + + public final func produceOperation(operation: NSOperation) { for observer in observers { observer.operation(self, didProduceOperation: operation) } } - + // MARK: Finishing - + /** Most operations may finish with a single error, if they have one at all. This is a convenience method to simplify calling the actual `finish()` @@ -244,15 +245,14 @@ class Operation: NSOperation { for how an error from an `NSURLSession` is passed along via the `finishWithError()` method. */ - final func finishWithError(error: NSError?) { + public final func finishWithError(error: NSError?) { if let error = error { finish([error]) - } - else { + } else { finish() } } - + /** A private property to ensure we only notify the observers once that the operation has finished. @@ -262,29 +262,29 @@ class Operation: NSOperation { if !hasFinishedAlready { hasFinishedAlready = true state = .Finishing - + let combinedErrors = _internalErrors + errors finished(combinedErrors) - + for observer in observers { observer.operationDidFinish(self, errors: combinedErrors) } - + state = .Finished } } - + /** Subclasses may override `finished(_:)` if they wish to react to the operation finishing with errors. For example, the `LoadModelOperation` implements this method to potentially inform the user about an error when trying to bring up the Core Data stack. */ - func finished(errors: [NSError]) { + public func finished(errors: [NSError]) { // No op. } - - override func waitUntilFinished() { + + override public func waitUntilFinished() { /* Waiting on operations is almost NEVER the right thing to do. It is usually superior to use proper locking constructs, such as `dispatch_semaphore_t` @@ -298,7 +298,7 @@ class Operation: NSOperation { */ fatalError("Waiting on operations is an anti-pattern. Remove this ONLY if you're absolutely sure there is No Other Way™.") } - + } // Simple operator functions to simplify the assertions used above. diff --git a/Source/URLSessionTaskOperation.swift b/Source/Operation/URLSessionTaskOperation.swift similarity index 77% rename from Source/URLSessionTaskOperation.swift rename to Source/Operation/URLSessionTaskOperation.swift index b0f9e76..d88324d 100644 --- a/Source/URLSessionTaskOperation.swift +++ b/Source/Operation/URLSessionTaskOperation.swift @@ -21,33 +21,37 @@ private var URLSessionTaksOperationKVOContext = 0 An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`. */ -class URLSessionTaskOperation: Operation { - let task: NSURLSessionTask + +public class URLSessionTaskOperation: Operation { - init(task: NSURLSessionTask) { + public let task: NSURLSessionTask + + public init(task: NSURLSessionTask) { assert(task.state == .Suspended, "Tasks must be suspended.") self.task = task super.init() } - - override func execute() { + + override public func execute() { assert(task.state == .Suspended, "Task was resumed by something other than \(self).") task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext) - + task.resume() } - - override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer) { - guard context == &URLSessionTaksOperationKVOContext else { return } - + + override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject:AnyObject]?, context: UnsafeMutablePointer) { + guard context == &URLSessionTaksOperationKVOContext else { + return + } + if object === task && keyPath == "state" && task.state == .Completed { task.removeObserver(self, forKeyPath: "state") finish() } } - - override func cancel() { + + override public func cancel() { task.cancel() super.cancel() } diff --git a/Source/ExclusivityController.swift b/Source/OperationQueue/ExclusivityController.swift similarity index 92% rename from Source/ExclusivityController.swift rename to Source/OperationQueue/ExclusivityController.swift index 5353063..0e0b267 100644 --- a/Source/ExclusivityController.swift +++ b/Source/OperationQueue/ExclusivityController.swift @@ -14,19 +14,20 @@ import Foundation We use a singleton because mutual exclusivity must be enforced across the entire app, regardless of the `OperationQueue` on which an `Operation` was executed. */ -class ExclusivityController { + +internal class ExclusivityController { static let sharedExclusivityController = ExclusivityController() - + private let serialQueue = dispatch_queue_create("Operations.ExclusivityController", DISPATCH_QUEUE_SERIAL) - private var operations: [String: [Operation]] = [:] - + private var operations: [String:[Operation]] = [:] + private init() { /* A private initializer effectively prevents any other part of the app from accidentally creating an instance. */ } - + /// Registers an operation as being mutually exclusive func addOperation(operation: Operation, categories: [String]) { /* @@ -40,7 +41,7 @@ class ExclusivityController { } } } - + /// Unregisters an operation from being mutually exclusive. func removeOperation(operation: Operation, categories: [String]) { dispatch_async(serialQueue) { @@ -49,31 +50,31 @@ class ExclusivityController { } } } - - + + // MARK: Operation Management - + private func noqueue_addOperation(operation: Operation, category: String) { var operationsWithThisCategory = operations[category] ?? [] - + if let last = operationsWithThisCategory.last { operation.addDependency(last) } - + operationsWithThisCategory.append(operation) operations[category] = operationsWithThisCategory } - + private func noqueue_removeOperation(operation: Operation, category: String) { let matchingOperations = operations[category] if var operationsWithThisCategory = matchingOperations, - let index = operationsWithThisCategory.indexOf(operation) { + let index = operationsWithThisCategory.indexOf(operation) { operationsWithThisCategory.removeAtIndex(index) operations[category] = operationsWithThisCategory } } - + } diff --git a/Source/OperationQueue.swift b/Source/OperationQueue/OperationQueue.swift similarity index 75% rename from Source/OperationQueue.swift rename to Source/OperationQueue/OperationQueue.swift index 724024e..e3a8c15 100644 --- a/Source/OperationQueue.swift +++ b/Source/OperationQueue/OperationQueue.swift @@ -18,8 +18,10 @@ import Foundation For example, `GroupOperation` is the delegate of its own internal `OperationQueue` and uses it to manage dependencies. */ -@objc protocol OperationQueueDelegate: NSObjectProtocol { + +@objc public protocol OperationQueueDelegate: NSObjectProtocol { optional func operationQueue(operationQueue: OperationQueue, willAddOperation operation: NSOperation) + optional func operationQueue(operationQueue: OperationQueue, operationDidFinish operation: NSOperation, withErrors errors: [NSError]) } @@ -31,43 +33,52 @@ import Foundation - Extracting generated dependencies from operation conditions - Setting up dependencies to enforce mutual exclusivity */ -class OperationQueue: NSOperationQueue { - weak var delegate: OperationQueueDelegate? + +public class OperationQueue: NSOperationQueue { - override func addOperation(operation: NSOperation) { + public weak var delegate: OperationQueueDelegate? + + override public func addOperation(operation: NSOperation) { + if let op = operation as? Operation { + // Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method. let delegate = BlockObserver( - startHandler: nil, - produceHandler: { [weak self] in - self?.addOperation($1) - }, - finishHandler: { [weak self] in - if let q = self { - q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1) + startHandler: nil, + produceHandler: { + [weak self] in + self?.addOperation($1) + }, + finishHandler: { + [weak self] in + if let q = self { + q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1) + } } - } ) op.addObserver(delegate) - + // Extract any dependencies needed by this operation. let dependencies = op.conditions.flatMap { $0.dependencyForOperation(op) } - + for dependency in dependencies { op.addDependency(dependency) self.addOperation(dependency) } - + /* With condition dependencies added, we can now see if this needs dependencies to enforce mutual exclusivity. */ - let concurrencyCategories: [String] = op.conditions.flatMap { condition in - if !condition.dynamicType.isMutuallyExclusive { return nil } - + let concurrencyCategories: [String] = op.conditions.flatMap { + condition in + if !condition.dynamicType.isMutuallyExclusive { + return nil + } + return "\(condition.dynamicType)" } @@ -76,37 +87,40 @@ class OperationQueue: NSOperationQueue { let exclusivityController = ExclusivityController.sharedExclusivityController exclusivityController.addOperation(op, categories: concurrencyCategories) - - op.addObserver(BlockObserver { operation, _ in + + op.addObserver(BlockObserver { + operation, _ in exclusivityController.removeOperation(operation, categories: concurrencyCategories) }) } - + /* Indicate to the operation that we've finished our extra work on it and it's now it a state where it can proceed with evaluating conditions, if appropriate. */ op.willEnqueue() - } - else { + } else { /* For regular `NSOperation`s, we'll manually call out to the queue's delegate we don't want to just capture "operation" because that would lead to the operation strongly referencing itself and that's the pure definition of a memory leak. */ - operation.addCompletionBlock { [weak self, weak operation] in - guard let queue = self, let operation = operation else { return } + operation.addCompletionBlock { + [weak self, weak operation] in + guard let queue = self, let operation = operation else { + return + } queue.delegate?.operationQueue?(queue, operationDidFinish: operation, withErrors: []) } } - + delegate?.operationQueue?(self, willAddOperation: operation) - super.addOperation(operation) + super.addOperation(operation) } - - override func addOperations(operations: [NSOperation], waitUntilFinished wait: Bool) { + + override public func addOperations(operations: [NSOperation], waitUntilFinished wait: Bool) { /* The base implementation of this method does not call `addOperation()`, so we'll call it ourselves. @@ -114,11 +128,12 @@ class OperationQueue: NSOperationQueue { for operation in operations { addOperation(operation) } - + if wait { for operation in operations { - operation.waitUntilFinished() + operation.waitUntilFinished() } } } + }