diff --git a/Examples/coSwiftDemo/Podfile b/Examples/coSwiftDemo/Podfile index 64bee74..50ace74 100644 --- a/Examples/coSwiftDemo/Podfile +++ b/Examples/coSwiftDemo/Podfile @@ -6,7 +6,7 @@ target 'coSwiftDemo' do use_frameworks! pod 'coswift', :path => '../../' - pod 'coobjc', :path => '../../' + pod 'cocore', :path => '../../' # Pods for coSwiftDemo end @@ -15,6 +15,8 @@ target 'coSwiftDemoTests' do use_frameworks! pod 'coswift', :path => '../../' - pod 'coobjc', :path => '../../' + pod 'cocore', :path => '../../' + pod 'Quick', '~> 1.3.0' + pod 'Nimble', '~> 7.3.0' end diff --git a/Examples/coSwiftDemo/coSwiftDemo.xcodeproj/project.pbxproj b/Examples/coSwiftDemo/coSwiftDemo.xcodeproj/project.pbxproj index 8dcb001..f1170d6 100644 --- a/Examples/coSwiftDemo/coSwiftDemo.xcodeproj/project.pbxproj +++ b/Examples/coSwiftDemo/coSwiftDemo.xcodeproj/project.pbxproj @@ -14,7 +14,7 @@ A35C048A21E73C1200AC7D6E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A35C048821E73C1200AC7D6E /* LaunchScreen.storyboard */; }; A35C049221E73DB500AC7D6E /* DataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35C049121E73DB500AC7D6E /* DataService.swift */; }; A362488992902709BC2684EF /* Pods_coSwiftDemoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7777908A066FA35360524D20 /* Pods_coSwiftDemoTests.framework */; }; - A3D67CFD21F086F5009328CC /* coSwiftDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D67CFC21F086F5009328CC /* coSwiftDemoTests.swift */; }; + A3D67CFD21F086F5009328CC /* coSwiftPromiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D67CFC21F086F5009328CC /* coSwiftPromiseTests.swift */; }; BE3A5FB7A33301FC4AF57A86 /* Pods_coSwiftDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C730B8DC19A4897343C94B94 /* Pods_coSwiftDemo.framework */; }; /* End PBXBuildFile section */ @@ -42,7 +42,7 @@ A35C048B21E73C1200AC7D6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A35C049121E73DB500AC7D6E /* DataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataService.swift; sourceTree = ""; }; A3D67CFA21F086F5009328CC /* coSwiftDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = coSwiftDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A3D67CFC21F086F5009328CC /* coSwiftDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = coSwiftDemoTests.swift; sourceTree = ""; }; + A3D67CFC21F086F5009328CC /* coSwiftPromiseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = coSwiftPromiseTests.swift; sourceTree = ""; }; A3D67CFE21F086F5009328CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C730B8DC19A4897343C94B94 /* Pods_coSwiftDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_coSwiftDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C8132E3B863C5D961133D7D6 /* Pods-coSwiftDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-coSwiftDemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-coSwiftDemoTests/Pods-coSwiftDemoTests.debug.xcconfig"; sourceTree = ""; }; @@ -125,7 +125,7 @@ A3D67CFB21F086F5009328CC /* coSwiftDemoTests */ = { isa = PBXGroup; children = ( - A3D67CFC21F086F5009328CC /* coSwiftDemoTests.swift */, + A3D67CFC21F086F5009328CC /* coSwiftPromiseTests.swift */, A3D67CFE21F086F5009328CC /* Info.plist */, ); path = coSwiftDemoTests; @@ -260,13 +260,13 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-coSwiftDemo/Pods-coSwiftDemo-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/coobjc-iOS11.0/coobjc.framework", + "${BUILT_PRODUCTS_DIR}/cocore-iOS11.0/cocore.framework", "${BUILT_PRODUCTS_DIR}/coswift-iOS11.0/coswift.framework", "${BUILT_PRODUCTS_DIR}/fishhook-iOS11.0/fishhook.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coobjc.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cocore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coswift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fishhook.framework", ); @@ -300,15 +300,19 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-coSwiftDemoTests/Pods-coSwiftDemoTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/coobjc-iOS12.1/coobjc.framework", + "${BUILT_PRODUCTS_DIR}/cocore-iOS12.1/cocore.framework", "${BUILT_PRODUCTS_DIR}/coswift-iOS12.1/coswift.framework", "${BUILT_PRODUCTS_DIR}/fishhook-iOS12.1/fishhook.framework", + "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coobjc.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cocore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coswift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fishhook.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -332,7 +336,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - A3D67CFD21F086F5009328CC /* coSwiftDemoTests.swift in Sources */, + A3D67CFD21F086F5009328CC /* coSwiftPromiseTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftDemoTests.swift b/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftDemoTests.swift deleted file mode 100644 index eee28c4..0000000 --- a/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftDemoTests.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// coSwiftDemoTests.swift -// coSwiftDemoTests -// -// Copyright © 2018 Alibaba Group Holding Limited All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import XCTest -import coswift - -class coSwiftDemoTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftPromiseTests.swift b/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftPromiseTests.swift new file mode 100644 index 0000000..56fce42 --- /dev/null +++ b/Examples/coSwiftDemo/coSwiftDemoTests/coSwiftPromiseTests.swift @@ -0,0 +1,208 @@ +// +// coSwiftDemoTests.swift +// coSwiftDemoTests +// +// Copyright © 2018 Alibaba Group Holding Limited All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import XCTest +import coswift +import Quick +import Nimble + +func testPromise1() -> Promise { + let promise = Promise(); + + DispatchQueue.main.async { + promise.fulfill(value: "123") + } + return promise +} + +func testPromise2() -> Promise { + let promise = Promise(); + + DispatchQueue.main.async { + promise.reject(error: NSError(domain: "aa", code: -1, userInfo: nil)) + } + return promise +} + +func testPromise3() -> Promise { + let promise = Promise(); + + + let timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { (timer) in + promise.fulfill(value: "1234") + } + + promise.onCancel { (promise) in + timer.invalidate() + } + return promise +} + +class PromiseSpec: QuickSpec { + + + override func spec() { + describe("Promise tests") { + + it("fulfill return the value") { + + let co = co_launch { + + let result = try await { testPromise1() } + + switch result { + case .fulfilled(let str): + expect(str).to(equal("123")) + break + case .rejected( _): + expect(false).to(beTrue()) + break + } + } + + waitUntil { done in + + co_launch { + + co.join() + done() + } + + } + } + + + + it("reject should return nil, and error.") { + + let co = co_launch { + + let result = try await { testPromise2() } + + switch result { + case .fulfilled(_ ): + expect(false).to(beTrue()) + break + case .rejected(let error): + expect(error as NSError).to(equal(NSError(domain: "aa", code: -1, userInfo: nil))) + break + } + } + + waitUntil(timeout: 2) { done in + + co_launch { + + co.join() + done() + } + + } + } + + it("test simple chained promise") { + + let promise = testPromise1() + + promise + .then(work: { (retStr) -> Data in + expect(retStr).to(equal("123")) + return retStr.data(using: String.Encoding.utf8)! + }) + .then(work: { (data) -> String in + + let retStr = String(data: data, encoding: String.Encoding.utf8)! + expect(retStr).to(equal("123")) + return retStr + }) + + var step = 0; + promise + .catch(reject: { (error) in + expect(false).to(beTrue()) + }) + .then(work: { (retStr) -> Data in + step = 1; + expect(retStr).to(equal("123")) + return retStr.data(using: String.Encoding.utf8)! + }) + .then(work: { (data) -> String in + step = 2; + let retStr = String(data: data, encoding: String.Encoding.utf8)! + expect(retStr).to(equal("123")) + return retStr + }) + + waitUntil(timeout: 3, action: { (done) in + + expect(step).to(equal(2)) + done() + }) + } + + it("cancel a coroutine when await a promise") { + + let co = co_launch { + + _ = try await{ testPromise3() } + + // should be cancelled + expect(false).to(beTrue()) + } + + waitUntil(timeout: 5, action: { (done) in + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: { + + co.cancel() + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: { + done() + }) + }) + }) + } + + it("cancel a finished coroutine will do nothing") { + + let co = co_launch { + + let result = try await{ testPromise3() } + + switch result { + case .fulfilled(let str): + expect(str).to(equal("1234")) + break + case .rejected( _): + expect(false).to(beTrue()) + break + } + } + + waitUntil(timeout: 5, action: { (done) in + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(3100), execute: { + + co.cancel() + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: { + done() + }) + }) + }) + } + } + } +} + diff --git a/Examples/coobjcBaseExample/Podfile b/Examples/coobjcBaseExample/Podfile index 5cce1a0..c7a0437 100644 --- a/Examples/coobjcBaseExample/Podfile +++ b/Examples/coobjcBaseExample/Podfile @@ -5,12 +5,14 @@ use_frameworks! target 'coobjcBaseExample' do pod 'coobjc', :path => '../../' +pod 'cocore', :path => '../../' end target 'coobjcBaseExampleTests' do pod 'coobjc', :path => '../../' + pod 'cocore', :path => '../../' pod 'Specta', '~> 1.0' pod 'Expecta', '~> 1.0' # expecta matchers diff --git a/Examples/coobjcBaseExample/coobjcBaseExample.xcodeproj/project.pbxproj b/Examples/coobjcBaseExample/coobjcBaseExample.xcodeproj/project.pbxproj index 22276a1..88e36b3 100644 --- a/Examples/coobjcBaseExample/coobjcBaseExample.xcodeproj/project.pbxproj +++ b/Examples/coobjcBaseExample/coobjcBaseExample.xcodeproj/project.pbxproj @@ -699,6 +699,7 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-coobjcBaseExampleTests/Pods-coobjcBaseExampleTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/cocore/cocore.framework", "${BUILT_PRODUCTS_DIR}/coobjc/coobjc.framework", "${BUILT_PRODUCTS_DIR}/fishhook/fishhook.framework", "${BUILT_PRODUCTS_DIR}/Expecta/Expecta.framework", @@ -707,6 +708,7 @@ ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cocore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coobjc.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fishhook.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Expecta.framework", @@ -743,11 +745,13 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-coobjcBaseExample/Pods-coobjcBaseExample-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/cocore/cocore.framework", "${BUILT_PRODUCTS_DIR}/coobjc/coobjc.framework", "${BUILT_PRODUCTS_DIR}/fishhook/fishhook.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cocore.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/coobjc.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fishhook.framework", ); diff --git a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseArcTests.m b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseArcTests.m index f99bf0a..b11b680 100644 --- a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseArcTests.m +++ b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseArcTests.m @@ -22,7 +22,6 @@ #import #import #import -#import static COActor *countActor = nil; diff --git a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseTests.m b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseTests.m index b711753..0e72fbc 100644 --- a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseTests.m +++ b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcAutoreleaseTests.m @@ -22,7 +22,6 @@ #import #import #import -#import # define RR_PUSH() objc_autoreleasePoolPush() # define RR_POP(p) objc_autoreleasePoolPop(p) diff --git a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcPromiseTests.m b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcPromiseTests.m index adc9f1a..b717d08 100644 --- a/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcPromiseTests.m +++ b/Examples/coobjcBaseExample/coobjcBaseExampleTests/coobjcPromiseTests.m @@ -216,7 +216,7 @@ static id testPromise3() { id result = await(testPromise3()); if (!result) { NSError *error = co_getError(); - expect(error.code).to.equal(-2341); + expect([COPromise isPromiseCancelled:error]).to.beTruthy(); expect(co_isActive()).to.equal(NO); expect(co_isCancelled()).to.equal(YES); val = 12; diff --git a/cocore.podspec b/cocore.podspec new file mode 100644 index 0000000..02a721f --- /dev/null +++ b/cocore.podspec @@ -0,0 +1,29 @@ +Pod::Spec.new do |s| + s.name = "cocore" + s.version = "1.1.0" + s.summary = "coobjc's core implement" + + s.description = <<-DESC + This library provides coroutine core support for Objective-C and Swift. coobjc and coswift depend on this sdk. + DESC + + s.homepage = "https://github.com/alibaba/coobjc" + s.license = { + :type => 'Copyright', + :text => <<-LICENSE + Alibaba-INC copyright + LICENSE + } + + s.author = { "pengyutang125" => "pengyutang125@sina.com" } + s.platform = :ios + + s.ios.deployment_target = '8.0' + + s.source = { :git => "https://github.com/alibaba/coobjc.git", :tag => '1.0.0' } + s.source_files = ['coobjc/core/*.{h,m,s,c,mm}', 'coobjc/util/*.{h,m}', 'coobjc/csp/*.{h,m}', 'coobjc/objc/co_autorelease.{h,mm}'] + s.requires_arc = ['coobjc/api/*.m', 'coobjc/core/*.m', 'coobjc/csp/*.m', 'coobjc/promise/*.m', 'coobjc/util/*.m'] + + s.library = 'c++' + s.dependency 'fishhook', '~> 0.2.0' +end diff --git a/coobjc.podspec b/coobjc.podspec index 257369d..812e039 100644 --- a/coobjc.podspec +++ b/coobjc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "coobjc" - s.version = "1.0.1" + s.version = "1.1.0" s.summary = "A coroutine framework for Objective-C" s.description = <<-DESC @@ -22,9 +22,9 @@ Pod::Spec.new do |s| s.requires_arc = true s.source = { :git => "https://github.com/alibaba/coobjc.git", :tag => '1.0.0' } - s.source_files = 'coobjc/**/*.{h,m,s,c,mm}' - s.requires_arc = ['coobjc/api/*.m', 'coobjc/core/*.m', 'coobjc/csp/*.m', 'coobjc/promise/*.m', 'coobjc/util/*.m'] + s.source_files = ['coobjc/api/*.{h,m}', 'coobjc/promise/*.{h,m}', 'coobjc/objc/co_tuple.{h,m}'] + s.requires_arc = ['coobjc/api/*.m', 'coobjc/promise/*.m'] - s.library = 'c++' s.dependency 'fishhook', '~> 0.2.0' + s.dependency 'cocore', '~> 1.1.0' end diff --git a/coobjc/api/COCoroutine.h b/coobjc/api/COCoroutine.h index 5067fc1..0d4c499 100644 --- a/coobjc/api/COCoroutine.h +++ b/coobjc/api/COCoroutine.h @@ -17,7 +17,7 @@ // limitations under the License. #import -#import +#import #import #import diff --git a/coobjc/api/coobjc.h b/coobjc/api/coobjc.h index 17b482f..77a915c 100644 --- a/coobjc/api/coobjc.h +++ b/coobjc/api/coobjc.h @@ -20,10 +20,10 @@ #import #import #import -#import -#import +#import +#import #import -#import +#import /** Mark a function with `CO_ASYNC`, which means the function may suspend, diff --git a/coobjc/csp/co_csp.h b/coobjc/csp/co_csp.h index c46b9bf..2151c15 100644 --- a/coobjc/csp/co_csp.h +++ b/coobjc/csp/co_csp.h @@ -23,7 +23,7 @@ #ifndef co_csp_h #define co_csp_h #include -#include +#include /** Define the channel's op code. send/receive. diff --git a/coobjc/objc/co_autorelease.mm b/coobjc/objc/co_autorelease.mm index bef07d3..ed88395 100644 --- a/coobjc/objc/co_autorelease.mm +++ b/coobjc/objc/co_autorelease.mm @@ -18,7 +18,7 @@ // limitations under the License. #import "co_autorelease.h" -#import "coobjc.h" +#import "coroutine.h" #include #include #include diff --git a/coobjc/promise/COPromise.h b/coobjc/promise/COPromise.h index 9f4db5f..0b42bdf 100644 --- a/coobjc/promise/COPromise.h +++ b/coobjc/promise/COPromise.h @@ -157,6 +157,14 @@ typedef void (^COPromiseConstructor)(COPromiseFullfill fullfill, COPromiseReject */ - (COPromise *)catch:(COPromiseCatchWorkBlock)reject; +/** + Tell if the error is promise cancelled error + + @param error the error object + @return is cancellled error. + */ ++ (BOOL)isPromiseCancelled:(NSError *)error; + @end /** diff --git a/coobjc/promise/COPromise.m b/coobjc/promise/COPromise.m index 9c265f5..e169bc6 100644 --- a/coobjc/promise/COPromise.m +++ b/coobjc/promise/COPromise.m @@ -31,6 +31,12 @@ typedef NS_ENUM(NSInteger, COPromiseState) { COPromiseStateRejected, }; +NSString *const COPromiseErrorDomain = @"COPromiseErrorDomain"; + +enum { + COPromiseCancelledError = -2341; +} + typedef void (^COPromiseObserver)(COPromiseState state, id __nullable resolution); @interface COPromise() @@ -178,15 +184,23 @@ - (void)reject:(NSError *)error { } } ++ (BOOL)isPromiseCancelled:(NSError *)error { + if ([error.domain isEqualToString:COPromiseErrorDomain] && error.code == COPromiseCancelledError) { + return YES; + } else { + return NO; + } +} + - (void)cancel { - [self reject:[NSError errorWithDomain:@"COPromiseErrorDomain" code:-2341 userInfo:@{NSLocalizedDescriptionKey: @"Promise was cancelled."}]]; + [self reject:[NSError errorWithDomain:COPromiseErrorDomain code:COPromiseCancelledError userInfo:@{NSLocalizedDescriptionKey: @"Promise was cancelled."}]]; } - (void)onCancel:(COPromiseOnCancelBlock)onCancelBlock { if (onCancelBlock) { __weak typeof(self) weakSelf = self; [self catch:^(NSError * _Nonnull error) { - if ([error.domain isEqualToString:@"COPromiseErrorDomain"] && error.code == -2341) { + if ([COPromise isPromiseCancelled:error]) { onCancelBlock(weakSelf); } }]; diff --git a/coswift.podspec b/coswift.podspec index a51dc77..b6a02eb 100644 --- a/coswift.podspec +++ b/coswift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "coswift" - s.version = "1.0.0" + s.version = "1.1.0" s.summary = "A coroutine framework for swift." s.description = <<-DESC @@ -27,5 +27,5 @@ Pod::Spec.new do |s| s.swift_version = '4.2' s.dependency 'fishhook', '~> 0.2.0' - s.dependency 'coobjc', '~> 1.0.0' + s.dependency 'cocore', '~> 1.1.0' end diff --git a/coswift/Chan.swift b/coswift/Chan.swift index 678b242..2a4cde9 100644 --- a/coswift/Chan.swift +++ b/coswift/Chan.swift @@ -39,14 +39,13 @@ public class Chan { private var buffCount: Int32 private var cchan: UnsafeMutablePointer private var cancelled: Bool = false - private var eleSize: Int32 private var buffList: [T] = [] private let lock = NSRecursiveLock() public init(buffCount: Int32) { self.buffCount = buffCount - eleSize = Int32(MemoryLayout.size) + let eleSize = Int32(MemoryLayout.size) cchan = chancreate(eleSize, buffCount, co_chan_custom_resume) } @@ -66,13 +65,15 @@ public class Chan { public func send(val: T) throws { if let co = Coroutine.current() { - co.currentChan = self as? Chan + co.chanCancelBlock = { + self.cancel() + } lock.lock() buffList.append(val) lock.unlock() chansendul(cchan, 1); - co.currentChan = nil + co.chanCancelBlock = nil if cancelled { throw COError.coroutineCancelled } @@ -80,6 +81,7 @@ public class Chan { } public func send_nonblock(val: T) { + lock.lock() buffList.append(val) lock.unlock() @@ -90,10 +92,11 @@ public class Chan { if let co = Coroutine.current() { - co.currentChan = self as? Chan - + co.chanCancelBlock = { + self.cancel() + } let ret = chanrecvul(cchan); - co.currentChan = nil + co.chanCancelBlock = nil if ret == 1 { @@ -111,7 +114,7 @@ public class Chan { } public func receive_nonblock() -> T? { - + let ret = channbrecvul(cchan) if ret == 1 { lock.lock() diff --git a/coswift/Coroutine.swift b/coswift/Coroutine.swift index acdb8eb..2f46209 100644 --- a/coswift/Coroutine.swift +++ b/coswift/Coroutine.swift @@ -57,17 +57,10 @@ open class Coroutine { */ private var co: UnsafeMutablePointer? - - /** - When COCoroutine as a Generator, this Channel use to yield a value. - */ - public var yieldChan: Chan? - - /** If `COCoroutine is suspend by a Channel, this pointer mark it. */ - public var currentChan: Chan? + public var chanCancelBlock: (() -> Void)? /** @@ -116,7 +109,6 @@ open class Coroutine { if let finishBlock = finishedBlock { finishBlock() } - yieldChan = nil } do { @@ -201,8 +193,8 @@ open class Coroutine { cancelled = true self.co?.pointee.is_cancelled = true - if let chan = self.currentChan { - chan.cancel(); + if let chanCancel = self.chanCancelBlock { + chanCancel(); } } diff --git a/coswift/Promise.swift b/coswift/Promise.swift index 2d3f134..3f948e4 100644 --- a/coswift/Promise.swift +++ b/coswift/Promise.swift @@ -61,7 +61,6 @@ public class Promise { } } - private var constructor: PromiseConstructor? private var promiseObservers: [PromiseObserver] = [] private let lock = NSRecursiveLock() @@ -91,15 +90,26 @@ public class Promise { public init() {} - public init(constructor: @escaping PromiseConstructor) { - self.constructor = constructor + public convenience init(constructor: @escaping PromiseConstructor) { + self.init(constructor: constructor, on: co_get_current_queue()) } - public convenience init(constructor: @escaping PromiseConstructor, on queue: DispatchQueue) { - self.init { (fulfill, reject) in - queue.async { - constructor(fulfill, reject) + public convenience init(constructor: @escaping PromiseConstructor, on queue: DispatchQueue?) { + self.init() + + let fulfill: PromiseFulfill = { (val: T) in + self.fulfill(value: val) + } + let reject: PromiseReject = { (err: Error) in + self.reject(error: err) + } + + if let q = queue { + q.async { + constructor(fulfill, reject); } + } else { + constructor(fulfill, reject) } } @@ -108,17 +118,17 @@ public class Promise { var observers: [PromiseObserver]? = nil var stateTmp: PromiseState? = nil - lock.lock() - - if state == .pending { - state = .fulfilled - stateTmp = state - _value = value - observers = promiseObservers - promiseObservers = [] - constructor = nil + do { + lock.lock() + defer { lock.unlock() } + if state == .pending { + state = .fulfilled + stateTmp = state + _value = value + observers = promiseObservers + promiseObservers = [] + } } - lock.unlock() observers?.forEach({ (observer) in observer(stateTmp!, Resolution.fulfilled(value)) @@ -139,7 +149,6 @@ public class Promise { _value = value observers = promiseObservers promiseObservers = [] - constructor = nil } } @@ -156,9 +165,7 @@ public class Promise { self.catch { [weak self](err) in if let strongSelf = self, let error = err as? COError, error == .promiseCancelled { - if error == .promiseCancelled { - onCancelBlock(strongSelf) - } + onCancelBlock(strongSelf) } } } @@ -230,7 +237,7 @@ public class Promise { } } - private func chainedPromise(chainedFulfill: ((T) -> U)?, chainedReject: ((Error) -> Error)?) -> Promise { + private func chainedPromise(chainedFulfill: @escaping ((T) -> U), chainedReject: ((Error) -> Error)?) -> Promise { let promise = Promise() @@ -244,42 +251,32 @@ public class Promise { self.observe(fulfill: { (val: T) in - if let chainedFulfillBlock = chainedFulfill { - - let ret = chainedFulfillBlock(val) - fulfillr(ret) - } + let ret = chainedFulfill(val) + fulfillr(ret) }) { (err: Error) in if let chainedRejectBlock = chainedReject { let ret = chainedRejectBlock(err) rejectr(ret) + } else { + rejectr(err) } } return promise } + @discardableResult public func then(work: @escaping (T) -> U) -> Promise { - - if let cons: PromiseConstructor = constructor { - - let fulfill: PromiseFulfill = { (val: T) in - self.fulfill(value: val) - } - let reject: PromiseReject = { (err: Error) in - self.reject(error: err) - } - cons(fulfill, reject) - } - return self.chainedPromise(chainedFulfill: work, chainedReject: nil) } @discardableResult public func `catch`(reject: @escaping (Error) -> Void) -> Promise { - return self.chainedPromise(chainedFulfill: nil, chainedReject: { (err) -> Error in + return self.chainedPromise(chainedFulfill: { (val: T) -> T in + return val + }, chainedReject: { (err) -> Error in reject(err) return err diff --git a/coswift/coswift.h b/coswift/coswift.h index fbf3d7e..685905d 100644 --- a/coswift/coswift.h +++ b/coswift/coswift.h @@ -26,6 +26,6 @@ FOUNDATION_EXPORT const unsigned char coswiftVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import -#import -#import -#import +#import +#import +#import diff --git a/coswift/coswift.swift b/coswift/coswift.swift index 107297f..45ec395 100644 --- a/coswift/coswift.swift +++ b/coswift/coswift.swift @@ -57,7 +57,11 @@ public func await(promise: Promise) throws -> Resolution { promise.then(work: { (value) -> Any in chan.send_nonblock(val: Resolution.fulfilled(value)) }).catch { (error) in - chan.send_nonblock(val: Resolution.rejected(error)) + if let err = error as? COError, err == .promiseCancelled { + + } else { + chan.send_nonblock(val: Resolution.rejected(error)) + } } chan.onCancel = { (channel) in