diff --git a/AlecrimAsyncKit.podspec b/AlecrimAsyncKit.podspec index dbf47cd..ba8ed8a 100644 --- a/AlecrimAsyncKit.podspec +++ b/AlecrimAsyncKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimAsyncKit" - s.version = "1.1.3" + s.version = "1.2" s.summary = "Bringing async and await to Swift world with some flavouring." s.homepage = "https://github.com/Alecrim/AlecrimAsyncKit" diff --git a/README.md b/README.md index f306173..b4c96ef 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,24 @@ The three main differences in this case: a `task` parameter is used as parameter import Foundation import CloudKit +// Some code running in background. + +let database: CKDatabase = ... + +do { + let records = try await(database.asyncPerformQuery(query, inZoneWithID: zoneID)) + + for record in records { + // ... + } +} +catch let error { + // do a nice error handling here +} + + +// A convenience `CKDatabase` extension. + extension CKDatabase { public func asyncPerformQuery(query: CKQuery, inZoneWithID zoneID: CKRecordZoneID?) -> Task<[CKRecord]> { @@ -255,7 +273,7 @@ If you want to contribute, please feel free to fork the repository and send pull The main areas the framework needs improvement: - Correct the README, code and examples for English mistakes; -- Write code documentation; +- Write more and better code documentation; - Write unit tests; - Write more conditions and observers; - Replace some pieces of code with more "elegant" ones. diff --git a/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj b/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj index 4fe4882..1618242 100644 --- a/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj +++ b/Source/AlecrimAsyncKit.xcodeproj/project.pbxproj @@ -7,32 +7,48 @@ objects = { /* Begin PBXBuildFile section */ - 140A15081B7FA79D0028DBCE /* AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140A15041B7FA79D0028DBCE /* AsyncAwait.swift */; }; - 140A15091B7FA79D0028DBCE /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140A15051B7FA79D0028DBCE /* Task.swift */; }; - 141067E81B9B75AA0030AABB /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141067E71B9B75AA0030AABB /* Errors.swift */; settings = {ASSET_TAGS = (); }; }; - 14B139901B88824F00131B01 /* TaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140A15061B7FA79D0028DBCE /* TaskCondition.swift */; }; - 14B139911B8889DA00131B01 /* SilentTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148F1DFA1B7FAA7B0026C36D /* SilentTaskCondition.swift */; }; - 14B139921B8889DA00131B01 /* NegateTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148F1DFC1B7FAE800026C36D /* NegateTaskCondition.swift */; }; - 14B139931B8889DA00131B01 /* DelayTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148F1DFE1B7FAF210026C36D /* DelayTaskCondition.swift */; }; - 14B139941B8889DA00131B01 /* MutuallyExclusiveTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148F1E001B7FB16F0026C36D /* MutuallyExclusiveTaskCondition.swift */; }; - 14B139951B888B7700131B01 /* TaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140A15071B7FA79D0028DBCE /* TaskObserver.swift */; }; - 14B139961B888E1600131B01 /* TimeoutTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142E25E91B7FBF4200B6C952 /* TimeoutTaskObserver.swift */; }; - 14B139971B888E1600131B01 /* NetworkActivityTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142E25EB1B7FC58A00B6C952 /* NetworkActivityTaskObserver.swift */; }; + 14749B841B9C437700661E98 /* EventStorePermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14749B831B9C437700661E98 /* EventStorePermissionTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 14749B861B9C4DA300661E98 /* BooleanTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14749B851B9C4DA300661E98 /* BooleanTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 147B5F371B9CFB0700F9F2CE /* ApplicationBackgroundTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147B5F361B9CFB0700F9F2CE /* ApplicationBackgroundTaskObserver.swift */; settings = {ASSET_TAGS = (); }; }; + 1485139D1B9BD15100FAB0FA /* DelayTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1485138C1B9BD15100FAB0FA /* DelayTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 1485139E1B9BD15100FAB0FA /* NegateTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1485138D1B9BD15100FAB0FA /* NegateTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 1485139F1B9BD15100FAB0FA /* SilentTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1485138E1B9BD15100FAB0FA /* SilentTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A01B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513901B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A11B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513911B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A21B9BD15100FAB0FA /* ReachabilityTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513921B9BD15100FAB0FA /* ReachabilityTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A31B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513931B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A41B9BD15100FAB0FA /* AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513951B9BD15100FAB0FA /* AsyncAwait.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A51B9BD15100FAB0FA /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513961B9BD15100FAB0FA /* Errors.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A61B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513971B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A71B9BD15100FAB0FA /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513981B9BD15100FAB0FA /* Task.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A81B9BD15100FAB0FA /* TaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513991B9BD15100FAB0FA /* TaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513A91B9BD15100FAB0FA /* TaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1485139A1B9BD15100FAB0FA /* TaskObserver.swift */; settings = {ASSET_TAGS = (); }; }; + 148513AC1B9BD19700FAB0FA /* NetworkActivityTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513AB1B9BD19700FAB0FA /* NetworkActivityTaskObserver.swift */; settings = {ASSET_TAGS = (); }; }; + 148513AE1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513AD1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift */; settings = {ASSET_TAGS = (); }; }; + 148513B11B9BD9A500FAB0FA /* TimeoutTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148513B01B9BD9A500FAB0FA /* TimeoutTaskObserver.swift */; settings = {ASSET_TAGS = (); }; }; 14E585BF1B7803EC003EC4DC /* AlecrimAsyncKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 14E585BE1B7803EC003EC4DC /* AlecrimAsyncKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 140A15041B7FA79D0028DBCE /* AsyncAwait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncAwait.swift; sourceTree = ""; }; - 140A15051B7FA79D0028DBCE /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; - 140A15061B7FA79D0028DBCE /* TaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCondition.swift; sourceTree = ""; }; - 140A15071B7FA79D0028DBCE /* TaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskObserver.swift; sourceTree = ""; }; - 141067E71B9B75AA0030AABB /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; - 142E25E91B7FBF4200B6C952 /* TimeoutTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeoutTaskObserver.swift; sourceTree = ""; }; - 142E25EB1B7FC58A00B6C952 /* NetworkActivityTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkActivityTaskObserver.swift; sourceTree = ""; }; - 148F1DFA1B7FAA7B0026C36D /* SilentTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilentTaskCondition.swift; sourceTree = ""; }; - 148F1DFC1B7FAE800026C36D /* NegateTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NegateTaskCondition.swift; sourceTree = ""; }; - 148F1DFE1B7FAF210026C36D /* DelayTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayTaskCondition.swift; sourceTree = ""; }; - 148F1E001B7FB16F0026C36D /* MutuallyExclusiveTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutuallyExclusiveTaskCondition.swift; sourceTree = ""; }; + 14749B831B9C437700661E98 /* EventStorePermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventStorePermissionTaskCondition.swift; sourceTree = ""; }; + 14749B851B9C4DA300661E98 /* BooleanTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BooleanTaskCondition.swift; sourceTree = ""; }; + 147B5F361B9CFB0700F9F2CE /* ApplicationBackgroundTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationBackgroundTaskObserver.swift; sourceTree = ""; }; + 1485138C1B9BD15100FAB0FA /* DelayTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelayTaskCondition.swift; sourceTree = ""; }; + 1485138D1B9BD15100FAB0FA /* NegateTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NegateTaskCondition.swift; sourceTree = ""; }; + 1485138E1B9BD15100FAB0FA /* SilentTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilentTaskCondition.swift; sourceTree = ""; }; + 148513901B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassLibraryAvailableCondition.swift; sourceTree = ""; }; + 148513911B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotosPermissionTaskCondition.swift; sourceTree = ""; }; + 148513921B9BD15100FAB0FA /* ReachabilityTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityTaskCondition.swift; sourceTree = ""; }; + 148513931B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteNotificationPermissionTaskCondition.swift; sourceTree = ""; }; + 148513951B9BD15100FAB0FA /* AsyncAwait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncAwait.swift; sourceTree = ""; }; + 148513961B9BD15100FAB0FA /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 148513971B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutuallyExclusiveTaskCondition.swift; sourceTree = ""; }; + 148513981B9BD15100FAB0FA /* Task.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; + 148513991B9BD15100FAB0FA /* TaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCondition.swift; sourceTree = ""; }; + 1485139A1B9BD15100FAB0FA /* TaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskObserver.swift; sourceTree = ""; }; + 148513AB1B9BD19700FAB0FA /* NetworkActivityTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkActivityTaskObserver.swift; sourceTree = ""; }; + 148513AD1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationPermissionTaskCondition.swift; sourceTree = ""; }; + 148513B01B9BD9A500FAB0FA /* TimeoutTaskObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeoutTaskObserver.swift; sourceTree = ""; }; 14E585BB1B7803EC003EC4DC /* AlecrimAsyncKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimAsyncKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 14E585BE1B7803EC003EC4DC /* AlecrimAsyncKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimAsyncKit.h; sourceTree = ""; }; 14E585C01B7803EC003EC4DC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -49,45 +65,58 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 140A15031B7FA79D0028DBCE /* Core */ = { + 1485138B1B9BD15100FAB0FA /* Additional Conditions */ = { isa = PBXGroup; children = ( - 140A15041B7FA79D0028DBCE /* AsyncAwait.swift */, - 140A15051B7FA79D0028DBCE /* Task.swift */, - 140A15061B7FA79D0028DBCE /* TaskCondition.swift */, - 140A15071B7FA79D0028DBCE /* TaskObserver.swift */, - 141067E71B9B75AA0030AABB /* Errors.swift */, + 14749B851B9C4DA300661E98 /* BooleanTaskCondition.swift */, + 1485138C1B9BD15100FAB0FA /* DelayTaskCondition.swift */, + 1485138D1B9BD15100FAB0FA /* NegateTaskCondition.swift */, + 1485138E1B9BD15100FAB0FA /* SilentTaskCondition.swift */, ); - path = Core; + path = "Additional Conditions"; sourceTree = ""; }; - 148F1DF81B7FAA4E0026C36D /* Convenience */ = { + 1485138F1B9BD15100FAB0FA /* Convenience Conditions */ = { isa = PBXGroup; children = ( - 148F1DF91B7FAA4E0026C36D /* Conditions */, - 148F1E021B7FBA530026C36D /* Observers */, + 14749B831B9C437700661E98 /* EventStorePermissionTaskCondition.swift */, + 148513AD1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift */, + 148513901B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift */, + 148513911B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift */, + 148513921B9BD15100FAB0FA /* ReachabilityTaskCondition.swift */, + 148513931B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift */, ); - path = Convenience; + path = "Convenience Conditions"; + sourceTree = ""; + }; + 148513941B9BD15100FAB0FA /* Core */ = { + isa = PBXGroup; + children = ( + 148513951B9BD15100FAB0FA /* AsyncAwait.swift */, + 148513981B9BD15100FAB0FA /* Task.swift */, + 148513961B9BD15100FAB0FA /* Errors.swift */, + 148513991B9BD15100FAB0FA /* TaskCondition.swift */, + 1485139A1B9BD15100FAB0FA /* TaskObserver.swift */, + 148513971B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift */, + ); + path = Core; sourceTree = ""; }; - 148F1DF91B7FAA4E0026C36D /* Conditions */ = { + 148513AA1B9BD19700FAB0FA /* Convenience Observers */ = { isa = PBXGroup; children = ( - 148F1DFA1B7FAA7B0026C36D /* SilentTaskCondition.swift */, - 148F1DFC1B7FAE800026C36D /* NegateTaskCondition.swift */, - 148F1DFE1B7FAF210026C36D /* DelayTaskCondition.swift */, - 148F1E001B7FB16F0026C36D /* MutuallyExclusiveTaskCondition.swift */, + 147B5F361B9CFB0700F9F2CE /* ApplicationBackgroundTaskObserver.swift */, + 148513AB1B9BD19700FAB0FA /* NetworkActivityTaskObserver.swift */, ); - path = Conditions; + path = "Convenience Observers"; sourceTree = ""; }; - 148F1E021B7FBA530026C36D /* Observers */ = { + 148513AF1B9BD9A500FAB0FA /* Additional Observers */ = { isa = PBXGroup; children = ( - 142E25E91B7FBF4200B6C952 /* TimeoutTaskObserver.swift */, - 142E25EB1B7FC58A00B6C952 /* NetworkActivityTaskObserver.swift */, + 148513B01B9BD9A500FAB0FA /* TimeoutTaskObserver.swift */, ); - path = Observers; + path = "Additional Observers"; sourceTree = ""; }; 14E585B11B7803EC003EC4DC = { @@ -110,8 +139,11 @@ isa = PBXGroup; children = ( 14E585BE1B7803EC003EC4DC /* AlecrimAsyncKit.h */, - 140A15031B7FA79D0028DBCE /* Core */, - 148F1DF81B7FAA4E0026C36D /* Convenience */, + 148513941B9BD15100FAB0FA /* Core */, + 1485138B1B9BD15100FAB0FA /* Additional Conditions */, + 148513AF1B9BD9A500FAB0FA /* Additional Observers */, + 1485138F1B9BD15100FAB0FA /* Convenience Conditions */, + 148513AA1B9BD19700FAB0FA /* Convenience Observers */, 14E585C61B7803F5003EC4DC /* Supporting Files */, ); path = AlecrimAsyncKit; @@ -204,17 +236,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 14B139961B888E1600131B01 /* TimeoutTaskObserver.swift in Sources */, - 14B139951B888B7700131B01 /* TaskObserver.swift in Sources */, - 14B139921B8889DA00131B01 /* NegateTaskCondition.swift in Sources */, - 14B139941B8889DA00131B01 /* MutuallyExclusiveTaskCondition.swift in Sources */, - 141067E81B9B75AA0030AABB /* Errors.swift in Sources */, - 14B139971B888E1600131B01 /* NetworkActivityTaskObserver.swift in Sources */, - 140A15091B7FA79D0028DBCE /* Task.swift in Sources */, - 14B139931B8889DA00131B01 /* DelayTaskCondition.swift in Sources */, - 14B139911B8889DA00131B01 /* SilentTaskCondition.swift in Sources */, - 14B139901B88824F00131B01 /* TaskCondition.swift in Sources */, - 140A15081B7FA79D0028DBCE /* AsyncAwait.swift in Sources */, + 148513A11B9BD15100FAB0FA /* PhotosPermissionTaskCondition.swift in Sources */, + 148513A01B9BD15100FAB0FA /* PassLibraryAvailableCondition.swift in Sources */, + 148513A31B9BD15100FAB0FA /* RemoteNotificationPermissionTaskCondition.swift in Sources */, + 148513A81B9BD15100FAB0FA /* TaskCondition.swift in Sources */, + 147B5F371B9CFB0700F9F2CE /* ApplicationBackgroundTaskObserver.swift in Sources */, + 1485139E1B9BD15100FAB0FA /* NegateTaskCondition.swift in Sources */, + 148513A61B9BD15100FAB0FA /* MutuallyExclusiveTaskCondition.swift in Sources */, + 148513B11B9BD9A500FAB0FA /* TimeoutTaskObserver.swift in Sources */, + 148513A21B9BD15100FAB0FA /* ReachabilityTaskCondition.swift in Sources */, + 148513A71B9BD15100FAB0FA /* Task.swift in Sources */, + 148513A51B9BD15100FAB0FA /* Errors.swift in Sources */, + 148513A91B9BD15100FAB0FA /* TaskObserver.swift in Sources */, + 148513A41B9BD15100FAB0FA /* AsyncAwait.swift in Sources */, + 148513AE1B9BD38100FAB0FA /* LocationPermissionTaskCondition.swift in Sources */, + 1485139D1B9BD15100FAB0FA /* DelayTaskCondition.swift in Sources */, + 1485139F1B9BD15100FAB0FA /* SilentTaskCondition.swift in Sources */, + 14749B861B9C4DA300661E98 /* BooleanTaskCondition.swift in Sources */, + 148513AC1B9BD19700FAB0FA /* NetworkActivityTaskObserver.swift in Sources */, + 14749B841B9C437700661E98 /* EventStorePermissionTaskCondition.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -263,7 +303,8 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos watchsimulator"; + SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; VALID_ARCHS = "x86_64 arm64 armv7 armv7s armv7k"; VERSIONING_SYSTEM = "apple-generic"; @@ -307,7 +348,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; - SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos"; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator watchos watchsimulator"; + SWIFT_INSTALL_OBJC_HEADER = NO; VALID_ARCHS = "x86_64 arm64 armv7 armv7s armv7k"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -321,7 +363,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 87; + CURRENT_PROJECT_VERSION = 99; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -343,7 +385,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 87; + CURRENT_PROJECT_VERSION = 99; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/Source/AlecrimAsyncKit/Additional Conditions/BooleanTaskCondition.swift b/Source/AlecrimAsyncKit/Additional Conditions/BooleanTaskCondition.swift new file mode 100644 index 0000000..9efe323 --- /dev/null +++ b/Source/AlecrimAsyncKit/Additional Conditions/BooleanTaskCondition.swift @@ -0,0 +1,30 @@ +// +// BooleanTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-06. +// Copyright © 2015 Alecrim. All rights reserved. +// + +import Foundation + +/// A condition that is satisfied according a boolean value. +public final class BooleanTaskCondition: TaskCondition { + + /// Initializes a `BooleanTaskCondition` that will be satisfied if the passed closure returns `true`. + /// + /// - parameter valueClosure: The closure that will return `true` if the condition is satisfied, `false` otherwise. + /// + /// - returns: An initialized `BooleanTaskCondition` that will be satisfied if the passed closure returns `true` + public init(@autoclosure(escaping) _ valueClosure: () -> Bool) { + super.init() { result in + if valueClosure() { + result(.Satisfied) + } + else { + result(.NotSatisfied) + } + } + } + +} diff --git a/Source/AlecrimAsyncKit/Convenience/Conditions/DelayTaskCondition.swift b/Source/AlecrimAsyncKit/Additional Conditions/DelayTaskCondition.swift similarity index 72% rename from Source/AlecrimAsyncKit/Convenience/Conditions/DelayTaskCondition.swift rename to Source/AlecrimAsyncKit/Additional Conditions/DelayTaskCondition.swift index afab8f9..e277227 100644 --- a/Source/AlecrimAsyncKit/Convenience/Conditions/DelayTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Additional Conditions/DelayTaskCondition.swift @@ -8,8 +8,15 @@ import Foundation +/// A condition that will simply wait for a given time interval to be satisfied. public final class DelayTaskCondition: TaskCondition { + /// Initializes a condition that will wait for a given time interval to be satisfied. + /// + /// - parameter timeInterval: The time interval to wait. + /// - parameter tolerance: The tolerance time interval (optional, defaults to 0). + /// + /// - returns: A condition that will wait for a given time interval to be satisfied. public init(timeInterval: NSTimeInterval, tolerance: NSTimeInterval = 0) { super.init() { result in let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL) @@ -29,4 +36,4 @@ public final class DelayTaskCondition: TaskCondition { } } -} \ No newline at end of file +} diff --git a/Source/AlecrimAsyncKit/Convenience/Conditions/NegateTaskCondition.swift b/Source/AlecrimAsyncKit/Additional Conditions/NegateTaskCondition.swift similarity index 71% rename from Source/AlecrimAsyncKit/Convenience/Conditions/NegateTaskCondition.swift rename to Source/AlecrimAsyncKit/Additional Conditions/NegateTaskCondition.swift index 01a8a1c..5bb71f7 100644 --- a/Source/AlecrimAsyncKit/Convenience/Conditions/NegateTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Additional Conditions/NegateTaskCondition.swift @@ -8,8 +8,14 @@ import Foundation +/// A simple condition that negates the evaluation of another condition. public final class NegateTaskCondition: TaskCondition { + /// Initializes a condition that negates the evaluation of another condition. + /// + /// - parameter otherCondition: The condition to be negated. + /// + /// - returns: A condition that negates the evaluation of another condition. public init(_ otherCondition: TaskCondition) { super.init(subconditions: otherCondition.subconditions, dependencyTask: otherCondition.dependencyTaskClosure(), evaluationClosure: otherCondition.evaluationClosure) } diff --git a/Source/AlecrimAsyncKit/Convenience/Conditions/SilentTaskCondition.swift b/Source/AlecrimAsyncKit/Additional Conditions/SilentTaskCondition.swift similarity index 54% rename from Source/AlecrimAsyncKit/Convenience/Conditions/SilentTaskCondition.swift rename to Source/AlecrimAsyncKit/Additional Conditions/SilentTaskCondition.swift index 1f538f5..cd8cc76 100644 --- a/Source/AlecrimAsyncKit/Convenience/Conditions/SilentTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Additional Conditions/SilentTaskCondition.swift @@ -8,8 +8,14 @@ import Foundation +/// A simple condition that causes another condition to not run its dependency task. public final class SilentTaskCondition: TaskCondition { + /// Initializes a condition that causes another condition to not run its dependency task. + /// + /// - parameter otherCondition: The condition that `dependencyTask` will not run. + /// + /// - returns: A condition that causes another condition to not run its dependency task. public init(_ otherCondition: TaskCondition) { super.init(subconditions: otherCondition.subconditions, dependencyTask: nil, evaluationClosure: otherCondition.evaluationClosure) } diff --git a/Source/AlecrimAsyncKit/Convenience/Observers/TimeoutTaskObserver.swift b/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift similarity index 64% rename from Source/AlecrimAsyncKit/Convenience/Observers/TimeoutTaskObserver.swift rename to Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift index 008901c..8ada5a1 100644 --- a/Source/AlecrimAsyncKit/Convenience/Observers/TimeoutTaskObserver.swift +++ b/Source/AlecrimAsyncKit/Additional Observers/TimeoutTaskObserver.swift @@ -8,9 +8,14 @@ import Foundation - +/// A task observer that causes the observed failable task to be cancelled if not finished before a specified time interval. public final class TimeoutTaskObserver: TaskObserver { + /// Initializes a task observer that causes the observed failable task to be cancelled if not finished before a specified time interval. + /// + /// - parameter timeout: The timeout time interval. + /// + /// - returns: An observer that causes a failable task to be cancelled if not finished before a specified time interval. public init(timeout: NSTimeInterval) { super.init() diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/EventStorePermissionTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/EventStorePermissionTaskCondition.swift new file mode 100644 index 0000000..42b55bb --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/EventStorePermissionTaskCondition.swift @@ -0,0 +1,58 @@ +// +// EventStorePermissionTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-06. +// Copyright © 2015 Alecrim. All rights reserved. +// + +import Foundation +import EventKit + +// `EKEventStore` takes a while to initialize, so we use a shared instance. +private let _sharedEventStore = EKEventStore() + +/// A condition for verifying access to the user's calendar. +public final class EventStorePermissionTaskCondition: TaskCondition { + + private static func asyncRequestAuthorization(entityType: EKEntityType) -> Task { + return asyncEx(condition: MutuallyExclusiveTaskCondition(.Alert)) { task in + let status = EKEventStore.authorizationStatusForEntityType(entityType) + + switch status { + case .NotDetermined: + dispatch_async(dispatch_get_main_queue()) { + _sharedEventStore.requestAccessToEntityType(entityType) { _, error in + if let error = error { + task.finishWithError(error) + } + else { + task.finish() + } + } + } + + default: + task.finish() + } + } + } + + /// Initializes a condition for verifying access to the user's calendar. + /// + /// - parameter entityType: The authorization needed (event or reminder). + /// + /// - returns: A condition for verifying access to the user's calendar. + public init(entityType: EKEntityType) { + super.init(dependencyTask: EventStorePermissionTaskCondition.asyncRequestAuthorization(entityType)) { result in + switch EKEventStore.authorizationStatusForEntityType(entityType) { + case .Authorized: + result(.Satisfied) + + default: + result(.NotSatisfied) + } + } + } + +} diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/LocationPermissionTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/LocationPermissionTaskCondition.swift new file mode 100644 index 0000000..7c09fb5 --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/LocationPermissionTaskCondition.swift @@ -0,0 +1,115 @@ +// +// LocationPermissionTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-05. +// Copyright © 2015 Alecrim. All rights reserved. +// + +#if os(iOS) + +import Foundation +import CoreLocation + +/// A condition for verifying access to the user's location. +public final class LocationPermissionTaskCondition: TaskCondition { + + public enum Usage { + case WhenInUse + case Always + } + + private static func asyncRequestAuthorizationIfNeededForUsage(usage: LocationPermissionTaskCondition.Usage) -> Task { + return asyncEx(condition: MutuallyExclusiveTaskCondition(.Alert)) { task in + /* + 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): + let locationManager = LocationManager() + locationManager.didChangeAuthorizationStatusClosure = { status in + task.finish() + } + + let key: String + + switch usage { + case .WhenInUse: + key = "NSLocationWhenInUseUsageDescription" + dispatch_async(dispatch_get_main_queue()) { + locationManager.requestWhenInUseAuthorization() + } + + case .Always: + key = "NSLocationAlwaysUsageDescription" + dispatch_async(dispatch_get_main_queue()) { + locationManager.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") + + + default: + task.finish() + } + } + } + + /// Initializes a condition for verifying access to the user's location. + /// + /// - parameter usage: The needed usage (when app is in use only or always). + /// + /// - returns: A condition for verifying access to the user's location. + public init(usage: LocationPermissionTaskCondition.Usage) { + super.init(dependencyTask: LocationPermissionTaskCondition.asyncRequestAuthorizationIfNeededForUsage(usage)) { result in + let enabled = CLLocationManager.locationServicesEnabled() + let actual = CLLocationManager.authorizationStatus() + + // 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. + result(.Satisfied) + + case (true, .WhenInUse, .AuthorizedWhenInUse): + // The service is enabled, and we have and need "WhenInUse" permission -> condition satisfied. + result(.Satisfied) + + 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`. + */ + result(.NotSatisfied) + } + } + } + +} + +private final class LocationManager: CLLocationManager, CLLocationManagerDelegate { + + private var didChangeAuthorizationStatusClosure: ((CLAuthorizationStatus) -> Void)? = nil + + private override init() { + super.init() + self.delegate = self + } + + @objc private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) { + self.didChangeAuthorizationStatusClosure?(status) + + self.delegate = nil + self.didChangeAuthorizationStatusClosure = nil + } + +} + +#endif diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/PassLibraryAvailableCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/PassLibraryAvailableCondition.swift new file mode 100644 index 0000000..f4623cc --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/PassLibraryAvailableCondition.swift @@ -0,0 +1,33 @@ +// +// PassLibraryAvailableCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-05. +// Copyright © 2015 Alecrim. All rights reserved. +// + +#if os(iOS) + +import Foundation +import PassKit + +/// A condition for verifying that Passbook exists and is accessible. +public final class PassLibraryAvailableCondition: TaskCondition { + + /// Initializes a condition for verifying that Passbook exists and is accessible. + /// + /// - returns: A condition for verifying that Passbook exists and is accessible. + public init() { + super.init() { result in + if PKPassLibrary.isPassLibraryAvailable() { + result(.Satisfied) + } + else { + result(.NotSatisfied) + } + } + } + +} + +#endif diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/PhotosPermissionTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/PhotosPermissionTaskCondition.swift new file mode 100644 index 0000000..6ecea89 --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/PhotosPermissionTaskCondition.swift @@ -0,0 +1,52 @@ +// +// PhotosPermissionTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-05. +// Copyright © 2015 Alecrim. All rights reserved. +// + +#if os(iOS) + +import Foundation +import Photos + +/// A condition for verifying access to the user's Photos library. +public final class PhotosPermissionTaskCondition: TaskCondition { + + private static func asyncRequestAuthorizationIfNeeded() -> Task { + return asyncEx(condition: MutuallyExclusiveTaskCondition(.Alert)) { task in + let authorizationStatus = PHPhotoLibrary.authorizationStatus() + + if case .NotDetermined = authorizationStatus { + dispatch_async(dispatch_get_main_queue()) { + PHPhotoLibrary.requestAuthorization { _ in + task.finish() + } + } + } + else { + task.finish() + } + } + } + + /// Initializes a condition for verifying access to the user's Photos library. + /// + /// - returns: A condition for verifying access to the user's Photos library. + public init() { + super.init(dependencyTask: PhotosPermissionTaskCondition.asyncRequestAuthorizationIfNeeded()) { result in + let authorizationStatus = PHPhotoLibrary.authorizationStatus() + + if case .Authorized = authorizationStatus { + result(.Satisfied) + } + else { + result(.NotSatisfied) + } + } + } + +} + +#endif diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/ReachabilityTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/ReachabilityTaskCondition.swift new file mode 100644 index 0000000..391e90a --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/ReachabilityTaskCondition.swift @@ -0,0 +1,71 @@ +// +// ReachabilityTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-05. +// Copyright © 2015 Alecrim. All rights reserved. +// + +#if os(OSX) || os(iOS) + +import Foundation +import SystemConfiguration + +/// A condition that performs network reachability check. +public final class ReachabilityTaskCondition: TaskCondition { + + private static var reachabilityRefs = [String : SCNetworkReachability]() + + private static func asyncRequestReachabilityForURL(URL: NSURL) -> NonFailableTask { + return async { + guard let host = URL.host else { return false } + + var ref = self.reachabilityRefs[host] + + if ref == nil { + 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) { + /* + Note that this is a very basic "is reachable" check. + Your app may choose to allow for other considerations, + such as whether or not the connection would require + VPN, a cellular connection, etc. + */ + reachable = flags.contains(.Reachable) + } + + return reachable + } + else { + return false + } + } + } + + /// Initializes a condition that performs network reachability check. + /// + /// - parameter URL: The URL containing the host to test the reachability. + /// + /// - returns: A condition that performs network reachability check. + public init(URL: NSURL) { + super.init() { result in + if await(ReachabilityTaskCondition.asyncRequestReachabilityForURL(URL)) { + result(.Satisfied) + } + else { + result(.NotSatisfied) + } + } + } + +} + +#endif diff --git a/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift b/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift new file mode 100644 index 0000000..b254fc7 --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Conditions/RemoteNotificationPermissionTaskCondition.swift @@ -0,0 +1,110 @@ +// +// RemoteNotificationPermissionTaskCondition.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-08-05. +// Copyright (c) 2015 Alecrim. All rights reserved. +// + +#if os(iOS) + +import Foundation +import UIKit + +private enum RemoteRegistrationResult { + case Unknown + case Waiting + case Token(NSData) + case Error(ErrorType) +} + +/// A condition for verifying that the app has the ability to receive push notifications. +public final class RemoteNotificationPermissionTaskCondition: TaskCondition { + + private static let remoteNotificationPermissionName = "com.alecrim.AlecrimAsyncKit.RemoteNotificationPermissionNotification" + private static var result = RemoteRegistrationResult.Unknown + + /// This method has to be called inside the `UIApplicationDelegate` response to the registration success. + /// + /// - parameter deviceToken: The received device token. + public static func didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: NSData) { + self.result = .Token(deviceToken) + NSNotificationCenter.defaultCenter().postNotificationName(self.remoteNotificationPermissionName, object: nil, userInfo: ["token": deviceToken]) + } + + /// This method has to be called inside the `UIApplicationDelegate` response to the registration error. + /// + /// - parameter error: The received error. + public static func didFailToRegisterForRemoteNotificationsWithError(error: NSError) { + self.result = .Error(error) + NSNotificationCenter.defaultCenter().postNotificationName(self.remoteNotificationPermissionName, object: nil, userInfo: ["error": error]) + } + + private static func asyncWaitResponseFromApplication(application: UIApplication) -> Task { + return asyncEx(condition: MutuallyExclusiveTaskCondition(.Alert)) { task in + switch self.result { + case .Unknown: + self.result = .Waiting + + var observer: AnyObject! + observer = NSNotificationCenter().addObserverForName(RemoteNotificationPermissionTaskCondition.remoteNotificationPermissionName, object: nil, queue: nil) { notification in + NSNotificationCenter.defaultCenter().removeObserver(observer) + observer = nil + + if let userInfo = notification.userInfo { + if let _ = userInfo["token"] as? NSData { + task.finish() + } + else if let error = userInfo["error"] as? NSError { + task.finishWithError(error) + } + else { + fatalError("Received a notification without a token and without an error.") + } + } + else { + fatalError("userInfo is nil.") + } + } + + dispatch_async(dispatch_get_main_queue()) { + application.registerForRemoteNotifications() + } + + case .Token: + task.finish() + + case .Error(let error): + task.finishWithError(error) + + default: + break + } + } + } + + /// Initializes a condition for verifying that the app has the ability to receive push notifications. + /// + /// - parameter application: The application instance. + /// + /// - returns: A condition for verifying that the app has the ability to receive push notifications. + /// + /// - note: Usually you will pass `UIApplication.sharedApplication()` as parameter. This is needed because the framework is marked to allow app extension API only. + public init(application: UIApplication) { + super.init(dependencyTask: RemoteNotificationPermissionTaskCondition.asyncWaitResponseFromApplication(application)) { result in + switch RemoteNotificationPermissionTaskCondition.result { + case .Token: + result(.Satisfied) + + case .Error(let error): + result(.Failed(error)) + + default: + result(.NotSatisfied) + } + } + } + +} + +#endif diff --git a/Source/AlecrimAsyncKit/Convenience Observers/ApplicationBackgroundTaskObserver.swift b/Source/AlecrimAsyncKit/Convenience Observers/ApplicationBackgroundTaskObserver.swift new file mode 100644 index 0000000..89d9a66 --- /dev/null +++ b/Source/AlecrimAsyncKit/Convenience Observers/ApplicationBackgroundTaskObserver.swift @@ -0,0 +1,85 @@ +// +// ApplicationBackgroundTaskObserver.swift +// AlecrimAsyncKit +// +// Created by Vanderlei Martinelli on 2015-09-06. +// Copyright © 2015 Alecrim. All rights reserved. +// + +import Foundation + +/// A task observer that will automatically begin and end a *background task* if the application transitions to the background. +public final class ApplicationBackgroundTaskObserver: TaskObserver { + + private let application: UIApplication + private var isInBackground = false + private var identifier = UIBackgroundTaskInvalid + + /// Initializes a task observer that will automatically begin and end a *background task* if the application transitions to the background. + /// + /// - parameter application: The application instance. + /// + /// - returns: A task observer that will automatically begin and end a *background task* if the application transitions to the background. + /// + /// - note: Usually you will pass `UIApplication.sharedApplication()` as parameter. This is needed because the framework is marked to allow app extension API only. + /// - note: The *"background task"* term as used here is in the context of of `UIApplication`. In this observer it will be related but it is not the same as `Task` or `NonFailableTask`. + public init(application: UIApplication) { + // + self.application = application + + // + super.init() + + // We need to know when the application moves to/from the background. + NSNotificationCenter.defaultCenter().addObserver(self, selector: "didEnterBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil) + NSNotificationCenter.defaultCenter().addObserver(self, selector: "didBecomeActive:", name: UIApplicationDidBecomeActiveNotification, object: nil) + + // + self.isInBackground = self.application.applicationState == .Background + + // If we're in the background already, immediately begin the *background task*. + if self.isInBackground { + self.startBackgroundTask() + } + + // + self.didFinish { [unowned self] _ in + self.endBackgroundTask() + } + } + + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) + NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidBecomeActiveNotification, object: nil) + } + + private func startBackgroundTask() { + if self.identifier == UIBackgroundTaskInvalid { + self.identifier = self.application.beginBackgroundTaskWithName("AAK.ABTO." + NSUUID().UUIDString, expirationHandler: { + self.endBackgroundTask() + }) + } + } + + private func endBackgroundTask() { + if self.identifier != UIBackgroundTaskInvalid { + self.application.endBackgroundTask(self.identifier) + self.identifier = UIBackgroundTaskInvalid + } + } + + @objc private func didEnterBackground(notification: NSNotification) { + if !self.isInBackground { + self.isInBackground = true + self.startBackgroundTask() + } + } + + @objc private func didBecomeActive(notification: NSNotification) { + if self.isInBackground { + self.isInBackground = false + self.endBackgroundTask() + } + } + +} diff --git a/Source/AlecrimAsyncKit/Convenience/Observers/NetworkActivityTaskObserver.swift b/Source/AlecrimAsyncKit/Convenience Observers/NetworkActivityTaskObserver.swift similarity index 74% rename from Source/AlecrimAsyncKit/Convenience/Observers/NetworkActivityTaskObserver.swift rename to Source/AlecrimAsyncKit/Convenience Observers/NetworkActivityTaskObserver.swift index 43f570c..c2236b0 100644 --- a/Source/AlecrimAsyncKit/Convenience/Observers/NetworkActivityTaskObserver.swift +++ b/Source/AlecrimAsyncKit/Convenience Observers/NetworkActivityTaskObserver.swift @@ -13,6 +13,7 @@ import Foundation private var _activitySpinLock = OS_SPINLOCK_INIT private var _activity: Int = 0 +/// A task observer that will cause the network activity indicator to appear as long as the observed task is executing. public final class NetworkActivityTaskObserver: TaskObserver { private let delay: NSTimeInterval = 0.5 @@ -50,7 +51,13 @@ public final class NetworkActivityTaskObserver: TaskObserver { } } - + /// Initializes a task observer that will cause the network activity indicator to appear as long as the observed task is executing. + /// + /// - parameter application: The application where the network activity indicator belongs to. + /// + /// - returns: A task observer that will cause the network activity indicator to appear as long as the observed task is executing. + /// + /// - note: Usually you will pass `UIApplication.sharedApplication()` as parameter. This is needed because the framework is marked to allow app extension API only. public init(application: UIApplication) { self.application = application super.init() diff --git a/Source/AlecrimAsyncKit/Core/AsyncAwait.swift b/Source/AlecrimAsyncKit/Core/AsyncAwait.swift index 194a14c..df615c2 100644 --- a/Source/AlecrimAsyncKit/Core/AsyncAwait.swift +++ b/Source/AlecrimAsyncKit/Core/AsyncAwait.swift @@ -30,6 +30,12 @@ private let _defaultRunTaskCompletionQueue: NSOperationQueue = NSOperationQueue. // MARK: - async +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, closure: () throws -> V) -> Task { return Task(queue: queue, observers: nil, conditions: nil) { (task: Task) -> Void in @@ -43,6 +49,12 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, closure: () th } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: nil, conditions: nil, closure: closure) @@ -50,6 +62,13 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, closure: (Ta // +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter condition: A condition that determines if the task will be started or not. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, condition: TaskCondition, closure: () throws -> V) -> Task { return Task(queue: queue, observers: nil, conditions: [condition]) { (task: Task) -> Void in @@ -63,6 +82,13 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, condition: Tas } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter condition: A condition that determines if the task will be started or not. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, condition: TaskCondition, closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: nil, conditions: [condition], closure: closure) @@ -70,6 +96,13 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, condition: T // +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter conditions: The conditions that determine if the task will be started or not. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, conditions: [TaskCondition], closure: () throws -> V) -> Task { return Task(queue: queue, observers: nil, conditions: conditions) { (task: Task) -> Void in @@ -83,6 +116,13 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, conditions: [T } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter conditions: The conditions that determine if the task will be started or not. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, conditions: [TaskCondition], closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: nil, conditions: conditions, closure: closure) @@ -90,6 +130,13 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, conditions: // +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, observers: [TaskObserver], closure: () throws -> V) -> Task { return Task(queue: queue, observers: observers, conditions: nil) { (task: Task) -> Void in @@ -103,6 +150,13 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, observers: [Ta } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, observers: [TaskObserver], closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: observers, conditions: nil, closure: closure) @@ -110,6 +164,14 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, observers: [ // +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter condition: A condition that determines if the task will be started or not. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, condition: TaskCondition, observers: [TaskObserver], closure: () throws -> V) -> Task { return Task(queue: queue, observers: observers, conditions: [condition]) { (task: Task) -> Void in @@ -123,6 +185,14 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, condition: Tas } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter condition: A condition that determines if the task will be started or not. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, condition: TaskCondition, observers: [TaskObserver], closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: observers, conditions: [condition], closure: closure) @@ -130,6 +200,14 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, condition: T // +/// Creates and returns a `Task` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter conditions: The conditions that determine if the task will be started or not. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value or throw errors. +/// +/// - returns: A `Task` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, conditions: [TaskCondition], observers: [TaskObserver], closure: () throws -> V) -> Task { return Task(queue: queue, observers: observers, conditions: conditions) { (task: Task) -> Void in @@ -143,6 +221,14 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, conditions: [T } } +/// Creates and returns a `Task` instance that can be finished or cancelled in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter conditions: The conditions that determine if the task will be started or not. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value or an error. +/// +/// - returns: A `Task` instance that can be finished or cancelled in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, conditions: [TaskCondition], observers: [TaskObserver], closure: (Task) -> Void) -> Task { return Task(queue: queue, observers: observers, conditions: conditions, closure: closure) @@ -150,6 +236,12 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, conditions: // MARK: - async - non failable task +/// Creates and returns a `NonFailableTask` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value. +/// +/// - returns: A `NonFailableTask` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, closure: () -> V) -> NonFailableTask { return NonFailableTask(queue: queue, observers: nil) { (task: NonFailableTask) -> Void in @@ -158,6 +250,12 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, closure: () -> } } +/// Creates and returns a `NonFailableTask` instance that can be finished in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value. +/// +/// - returns: A `NonFailableTask` instance that can be finished in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, closure: (NonFailableTask) -> Void) -> NonFailableTask { return NonFailableTask(queue: queue, observers: nil, closure: closure) @@ -165,6 +263,13 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, closure: (No // +/// Creates and returns a `NonFailableTask` instance with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that will return the tasks's associated value. +/// +/// - returns: A `NonFailableTask` instance with the specified parameters. @warn_unused_result public func async(queue: NSOperationQueue = _defaultTaskQueue, observers: [TaskObserver], closure: () -> V) -> NonFailableTask { return NonFailableTask(queue: queue, observers: observers) { (task: NonFailableTask) -> Void in @@ -173,6 +278,13 @@ public func async(queue: NSOperationQueue = _defaultTaskQueue, observers: [Ta } } +/// Creates and returns a `Task` instance that can be finished in any thread with the specified parameters. +/// +/// - parameter queue: The queue where the task will run. +/// - parameter observers: Observer instances that will be notified when a task is about to start and when a task is finished. +/// - parameter closure: The closure that will be executed and that, at some thread or context, will finish the task with a value. +/// +/// - returns: A `Task` instance that can be finished in any thread with the specified parameters. @warn_unused_result public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, observers: [TaskObserver], closure: (NonFailableTask) -> Void) -> NonFailableTask { return NonFailableTask(queue: queue, observers: observers, closure: closure) @@ -180,14 +292,34 @@ public func asyncEx(queue: NSOperationQueue = _defaultTaskQueue, observers: [ // MARK: - await +/// Waits for the completion of a task and then returns its value. +/// +/// - parameter closure: The closure that returns a initialized task. +/// +/// - throws: Any error occurred while task was executing. +/// +/// - returns: The task's associated value. public func await(@noescape closure: () -> Task) throws -> V { return try closure().waitForCompletionAndReturnValue() } +/// Waits for the completion of a task and then returns its value. +/// +/// - parameter task: A initialized task. +/// +/// - throws: Any error occurred while task was executing. +/// +/// - returns: The task's associated value. public func await(task: Task) throws -> V { return try task.waitForCompletionAndReturnValue() } +/// Runs a task in a background queue and the call the completion handler with its associated value or error if occurred. +/// +/// - parameter task: The task to run. +/// - parameter queue: The queue where the task will be "awaited". +/// - parameter completionQueue: The queue where the completion handler will be called. +/// - parameter completionHandler: The optional completion handler. public func runTask(task: Task, queue: NSOperationQueue = _defaultRunTaskQueue, completionQueue: NSOperationQueue = _defaultRunTaskCompletionQueue, completion completionHandler: ((V!, ErrorType?) -> Void)? = nil) { queue.addOperationWithBlock { do { @@ -212,14 +344,30 @@ public func runTask(task: Task, queue: NSOperationQueue = _defaultRunTaskQ // MARK: - await - non failable task +/// Waits for the completion of a non-failable task and then returns its value. +/// +/// - parameter closure: The closure that returns a initialized non-failable task. +/// +/// - returns: The non-failable task's associated value. public func await(@noescape closure: () -> NonFailableTask) -> V { return closure().waitForCompletionAndReturnValue() } +/// Waits for the completion of a non-failable task and then returns its value. +/// +/// - parameter task: A initialized non-failable task. +/// +/// - returns: The non-failable task's associated value. public func await(task: NonFailableTask) -> V { return task.waitForCompletionAndReturnValue() } +/// Runs a non-failable task in a background queue and the call the completion handler with its associated value. +/// +/// - parameter task: The non-failable task to run. +/// - parameter queue: The queue where the task will be "awaited". +/// - parameter completionQueue: The queue where the completion handler will be called. +/// - parameter completionHandler: The optional completion handler. public func runTask(task: NonFailableTask, queue: NSOperationQueue = _defaultRunTaskQueue, completionQueue: NSOperationQueue = _defaultRunTaskCompletionQueue, completion completionHandler: ((V) -> Void)? = nil) { queue.addOperationWithBlock { let value = task.waitForCompletionAndReturnValue() @@ -231,3 +379,4 @@ public func runTask(task: NonFailableTask, queue: NSOperationQueue = _defa } } } + diff --git a/Source/AlecrimAsyncKit/Core/Errors.swift b/Source/AlecrimAsyncKit/Core/Errors.swift index 86c89d4..f1ae321 100644 --- a/Source/AlecrimAsyncKit/Core/Errors.swift +++ b/Source/AlecrimAsyncKit/Core/Errors.swift @@ -10,9 +10,13 @@ import Foundation // MARK: - +/// The possible errors related to task condition evalution. +/// +/// - NotSatisfied: The condition was not satisfied. +/// - Failed: The condition evaluation was failed with an error. public enum TaskConditionError: ErrorType { case NotSatisfied - case ExecutionFailed(innerError: ErrorType) + case Failed(ErrorType) } // MARK: - diff --git a/Source/AlecrimAsyncKit/Convenience/Conditions/MutuallyExclusiveTaskCondition.swift b/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift similarity index 69% rename from Source/AlecrimAsyncKit/Convenience/Conditions/MutuallyExclusiveTaskCondition.swift rename to Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift index 053c845..13125b9 100644 --- a/Source/AlecrimAsyncKit/Convenience/Conditions/MutuallyExclusiveTaskCondition.swift +++ b/Source/AlecrimAsyncKit/Core/MutuallyExclusiveTaskCondition.swift @@ -8,8 +8,12 @@ import Foundation +/// A condition for describing kinds of operations that may not execute concurrently. public final class MutuallyExclusiveTaskCondition: TaskCondition { + /// An enumeration with the default categories used by the condition. + /// + /// - Alert: The category that represents a potential modal alert to the user. public enum DefaultCategory: String { case Alert = "com.alecrim.AlecrimAsyncKit.MutuallyExclusiveTaskCondition.DefaultCategory.Alert" } @@ -17,12 +21,23 @@ public final class MutuallyExclusiveTaskCondition: TaskCondition { private static var mutuallyExclusiveSemaphores = [String: (semaphore: dispatch_semaphore_t, count: Int)]() private static var spinlock = OS_SPINLOCK_INIT + /// The category name that will define the condition exclusivity group. + public let categoryName: String + + /// Initialize a condition for describing kinds of operations that may not execute concurrently. + /// + /// - parameter defaultCategory: The default category enumeration member that will define the condition exclusivity group. + /// + /// - returns: A condition for describing kinds of operations that may not execute concurrently. public convenience init(_ defaultCategory: MutuallyExclusiveTaskCondition.DefaultCategory) { self.init(defaultCategory.rawValue) } - public let categoryName: String - + /// Initializes a condition for describing kinds of operations that may not execute concurrently. + /// + /// - parameter categoryName: The category name that will define the condition exclusivity group. + /// + /// - returns: A condition for describing kinds of operations that may not execute concurrently. public init(_ categoryName: String) { self.categoryName = categoryName diff --git a/Source/AlecrimAsyncKit/Core/Task.swift b/Source/AlecrimAsyncKit/Core/Task.swift index c3a97a5..be0dca3 100644 --- a/Source/AlecrimAsyncKit/Core/Task.swift +++ b/Source/AlecrimAsyncKit/Core/Task.swift @@ -8,27 +8,46 @@ import Foundation -// MARK: - protocols needed to support task observers in this version +// MARK: - Protocols needed to support task observers in this version. +/// The basic task type protocol. public protocol TaskType: class { + var finished: Bool { get } } +/// The failable task type protocol. public protocol FailableTaskType: TaskType { + var cancelled: Bool { get } func cancel() } +/// The non-failable task type protocol. public protocol NonFailableTaskType: TaskType { } -// MARK: - +// MARK: - Core task classes. +/// The "abstract" base class for all type of tasks. Not intended to be used directly. public class BaseTask: TaskType { + /// The value associated to the successfully task completion. public private(set) var value: V! + + /// The error occurred while the task was executing, if any. public private(set) var error: ErrorType? + /// If either `value` or `error` properties are not `nil`, this property will return `true`. + public var finished: Bool { + var f = false + + withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) + f = self.value != nil || self.error != nil + withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + + return f + } + private let dispatchGroup: dispatch_group_t = dispatch_group_create() - private var waiting = true private var spinlock = OS_SPINLOCK_INIT private var deferredClosures: Array<() -> Void>? @@ -38,9 +57,7 @@ public class BaseTask: TaskType { } deinit { - withUnsafeMutablePointer(&self.spinlock, OSSpinLockLock) - assert(!self.waiting, "Either value or error were never assigned or task was never cancelled.") - withUnsafeMutablePointer(&self.spinlock, OSSpinLockUnlock) + assert(self.finished, "Either value or error were never assigned or task was never cancelled.") } private final func waitForCompletion() { @@ -73,14 +90,13 @@ public class BaseTask: TaskType { self.error = nil } - self.waiting = false - // dispatch_group_leave(self.dispatchGroup) } // MARK: - + /// Finishes a task that has its generic type as `Void`. If the generic type of the task is not `Void` a fatal error will occurs. public final func finish() { if V.self is Void.Type { self.setValue((() as! V), error: nil) @@ -90,6 +106,9 @@ public class BaseTask: TaskType { } } + /// Finished the task with an associated value. + /// + /// - parameter value: The associated value representing the final result of the task. public final func finishWithValue(value: V) { self.setValue(value, error: nil) } @@ -106,9 +125,10 @@ public class BaseTask: TaskType { } } - +/// An asynchronous failable task. A "failable" task in the context of this framework is a task that can returns or throws an error. public final class Task: BaseTask, FailableTaskType { + /// If the `error` property is not `nil` and the error code is `NSUserCancelledError`, this property will return `true`. public var cancelled: Bool { var c = false @@ -167,7 +187,7 @@ public final class Task: BaseTask, FailableTaskType { catch TaskConditionError.NotSatisfied { self.cancel() } - catch TaskConditionError.ExecutionFailed(let innerError) { + catch TaskConditionError.Failed(let innerError) { self.finishWithError(innerError) } catch let error { @@ -188,10 +208,17 @@ public final class Task: BaseTask, FailableTaskType { } } + /// Finishes the task with an error. + /// + /// - parameter error: The error occurred while executing the task. public func finishWithError(error: ErrorType) { self.setValue(nil, error: error) } + /// Finishes the task with a value or an error (not both and not none of them). + /// + /// - parameter value: The value representing the final value associated with the task. If this parameter is not nil, the `error` parameter must be nil. + /// - parameter error: The error occurred while executing the task. If this parameter is not nil, the `value` parameter must be nil. public func finishWithValue(value: V?, error: ErrorType?) { self.setValue(value, error: error) } @@ -199,12 +226,18 @@ public final class Task: BaseTask, FailableTaskType { // MARK: - + /// Cancels the execution of the current task. This is the same as finishing the task with an error with `NSUserCancelledError` code. + /// + /// - note: After a task is cancelled no action to stop it will be taken by the framework. You will have to check the `cancelled` property and stops any activity as soon as possible after it returns `true`. public func cancel() { self.setValue(nil, error: taskCancelledError) } // MARK: - + /// Waits for the execution of another task of the same generic type. + /// + /// - parameter task: The task to be executed and "awaited". public func continueWithTask(task: Task) { do { let value = try task.waitForCompletionAndReturnValue() @@ -217,6 +250,7 @@ public final class Task: BaseTask, FailableTaskType { } +/// An asynchronous non-failable task. A "non-failable" task in the context of this framework is a task that cannot return or throw an error. public final class NonFailableTask: BaseTask, NonFailableTaskType { internal init(queue: NSOperationQueue, observers: [TaskObserver]?, closure: (NonFailableTask) -> Void) { @@ -243,6 +277,9 @@ public final class NonFailableTask: BaseTask, NonFailableTaskType { // MARK: - + /// Waits for the execution of another task of the same generic type. + /// + /// - parameter task: The task to be executed and "awaited". public func continueWithTask(task: NonFailableTask) { let value = task.waitForCompletionAndReturnValue() self.finishWithValue(value) diff --git a/Source/AlecrimAsyncKit/Core/TaskCondition.swift b/Source/AlecrimAsyncKit/Core/TaskCondition.swift index 1dab54c..c3bd65d 100644 --- a/Source/AlecrimAsyncKit/Core/TaskCondition.swift +++ b/Source/AlecrimAsyncKit/Core/TaskCondition.swift @@ -18,49 +18,91 @@ private let _defaultTaskConditionQueue: NSOperationQueue = { }() +/// An enumeration with the possible condition results. +/// +/// - Satisfied: The condition was satisfied. +/// - NotSatisfied: The condition was not satisfied. +/// - Failed: An error was occurred while evaluating the condition. public enum TaskConditionResult { case Satisfied case NotSatisfied - case ExecutionFailed(error: ErrorType) + case Failed(ErrorType) } - +/// A condition determines if a task can be executed or not. public class TaskCondition { internal let subconditions: [TaskCondition]? internal let dependencyTaskClosure: () -> Task? internal let evaluationClosure: ((TaskConditionResult) -> Void) -> Void + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = nil self.dependencyTaskClosure = { return nil } self.evaluationClosure = evaluationClosure } + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter dependencyTaskClosure: A failable task that to be runned before the condition evaluation. If the task finishes with an error, the condition evalution closure is not executed and the condition is mark as failed. The dependency task will be only started, if needed, at the moment of the evaluation. + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(@autoclosure(escaping) dependencyTask dependencyTaskClosure: () -> Task?, evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = nil self.dependencyTaskClosure = dependencyTaskClosure self.evaluationClosure = evaluationClosure } + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter subcondition: A subcondition that determines if the condition itself will be evaluated. + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(subcondition: TaskCondition, evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = [subcondition] self.dependencyTaskClosure = { return nil } self.evaluationClosure = evaluationClosure } + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter subcondition: A subcondition that determines if the condition itself will be evaluated. + /// - parameter dependencyTaskClosure: A failable task that to be runned before the condition evaluation. If the task finishes with an error, the condition evalution closure is not executed and the condition is mark as failed. The dependency task will be only started, if needed, at the moment of the evaluation. + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(subcondition: TaskCondition, @autoclosure(escaping) dependencyTask dependencyTaskClosure: () -> Task?, evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = [subcondition] self.dependencyTaskClosure = dependencyTaskClosure self.evaluationClosure = evaluationClosure } + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter subconditions: The subconditions that will determine if the condition itself will be evaluated. + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(subconditions: [TaskCondition], evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = subconditions self.dependencyTaskClosure = { return nil } self.evaluationClosure = evaluationClosure } + /// Initializes a condition that will determine if a task can be executed or not. + /// + /// - parameter subconditions: The subconditions that will determine if the condition itself will be evaluated. + /// - parameter dependencyTaskClosure: A failable task that to be runned before the condition evaluation. If the task finishes with an error, the condition evalution closure is not executed and the condition is mark as failed. The dependency task will be only started, if needed, at the moment of the evaluation. + /// - parameter evaluationClosure: The evaluation closure returning a `TaskConditionResult` enumeration member. + /// + /// - returns: A condition that will determine if a task can be executed or not. public init(subconditions: [TaskCondition]?, @autoclosure(escaping) dependencyTask dependencyTaskClosure: () -> Task?, evaluationClosure: ((TaskConditionResult) -> Void) -> Void) { self.subconditions = subconditions self.dependencyTaskClosure = dependencyTaskClosure @@ -77,8 +119,8 @@ public class TaskCondition { case .NotSatisfied: task.finishWithError(TaskConditionError.NotSatisfied) - case .ExecutionFailed(let error): - task.finishWithError(TaskConditionError.ExecutionFailed(innerError: error)) + case .Failed(let error): + task.finishWithError(TaskConditionError.Failed(error)) } } } diff --git a/Source/AlecrimAsyncKit/Core/TaskObserver.swift b/Source/AlecrimAsyncKit/Core/TaskObserver.swift index 675e133..c40eea3 100644 --- a/Source/AlecrimAsyncKit/Core/TaskObserver.swift +++ b/Source/AlecrimAsyncKit/Core/TaskObserver.swift @@ -8,19 +8,24 @@ import Foundation +/// An observer that will be notified about the task start and finish. public class TaskObserver { - // MARK: - + // MARK: - Private properties. private var didStartClosures = Array<((TaskType) -> Void)>() private var didFinishClosures = Array<((TaskType) -> Void)>() - // MARK: - + // MARK: - Initializers. + + /// Initializes an observer that will be notified about the task start and finish. + /// + /// - returns: An observer that will be notified about the task start and finish. public init() { - + } - // MARK: - + // MARK: - Internal methods. internal final func taskDidStart(task: TaskType) { self.didStartClosures.forEach { $0(task) } @@ -30,13 +35,23 @@ public class TaskObserver { self.didFinishClosures.forEach { $0(task) } } - // MARK: - + // MARK: - Public methods. + /// Adds a closure that will be run when the task is started. + /// + /// - parameter closure: The closure that will be run when the task is started. + /// + /// - returns: The observer itself. public final func didStart(closure: (TaskType) -> Void) -> Self { self.didStartClosures.append(closure) return self } + /// Adds a closure that will be run when the task is finished. + /// + /// - parameter closure: The closure that will be run when the task is finished. + /// + /// - returns: The observer itself. public final func didFinish(closure: (TaskType) -> Void) -> Self { self.didFinishClosures.append(closure) return self diff --git a/Source/AlecrimAsyncKit/Info.plist b/Source/AlecrimAsyncKit/Info.plist index 1094680..5abfbb2 100644 --- a/Source/AlecrimAsyncKit/Info.plist +++ b/Source/AlecrimAsyncKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1.3 + 1.2 CFBundleSignature ???? CFBundleVersion