Skip to content

Commit

Permalink
Get rid of ObjC exception handling
Browse files Browse the repository at this point in the history
  • Loading branch information
shoumikhin committed Feb 8, 2018
1 parent 5115132 commit 2c5e847
Show file tree
Hide file tree
Showing 18 changed files with 22 additions and 394 deletions.
5 changes: 2 additions & 3 deletions PromisesObjC.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'PromisesObjC'
s.version = '1.0.0'
s.version = '1.0.1'
s.authors = 'Google Inc.'
s.license = { :type => 'Apache', :file => 'LICENSE' }
s.homepage = 'https://github.com/google/promises'
Expand All @@ -20,8 +20,7 @@ Pod::Spec.new do |s|
s.prefix_header_file = false
s.header_mappings_dir = "Sources/#{s.module_name}/include/"
s.public_header_files = "Sources/#{s.module_name}/include/**/*.h"
s.private_header_files = "Sources/#{s.module_name}/include/FBLPromiseErrorPrivate.h",
"Sources/#{s.module_name}/include/FBLPromisePrivate.h"
s.private_header_files = "Sources/#{s.module_name}/include/FBLPromisePrivate.h"
s.source_files = "Sources/#{s.module_name}/**/*.{h,m}"
s.xcconfig = {
'HEADER_SEARCH_PATHS' => "\"${PODS_TARGET_SRCROOT}/Sources/#{s.module_name}/include\""
Expand Down
18 changes: 7 additions & 11 deletions Sources/FBLPromises/FBLPromise+Async.m
Expand Up @@ -29,17 +29,13 @@ + (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)

FBLPromise *promise = [[[self class] alloc] initPending];
dispatch_group_async([self class].dispatchGroup, queue, ^{
@try {
work(
^(id __nullable value) {
[promise fulfill:value];
},
^(NSError *error) {
[promise reject:error];
});
} @catch (id exception) {
[promise reject:exception];
}
work(
^(id __nullable value) {
[promise fulfill:value];
},
^(NSError *error) {
[promise reject:error];
});
});
return promise;
}
Expand Down
6 changes: 1 addition & 5 deletions Sources/FBLPromises/FBLPromise+Do.m
Expand Up @@ -29,11 +29,7 @@ + (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work {

FBLPromise *promise = [[[self class] alloc] initPending];
dispatch_group_async([self class].dispatchGroup, queue, ^{
@try {
[promise fulfill:work()];
} @catch (id exception) {
[promise reject:exception];
}
[promise fulfill:work()];
});
return promise;
}
Expand Down
44 changes: 6 additions & 38 deletions Sources/FBLPromises/FBLPromise.m
Expand Up @@ -16,8 +16,6 @@

#import "FBLPromisePrivate.h"

#import "FBLPromiseErrorPrivate.h"

/** All states a promise can be in. */
typedef NS_ENUM(NSInteger, FBLPromiseState) {
FBLPromiseStatePending = 0,
Expand Down Expand Up @@ -68,8 +66,6 @@ - (void)fulfill:(nullable id)value {
}];
} else if ([value isKindOfClass:[NSError class]]) {
[self reject:(NSError *)value];
} else if ([value isKindOfClass:[NSException class]]) {
[self reject:FBLNSErrorFromNSException((NSException *)value)];
} else {
@synchronized(self) {
if (_state == FBLPromiseStatePending) {
Expand All @@ -87,9 +83,6 @@ - (void)fulfill:(nullable id)value {
}

- (void)reject:(NSError *)error {
if ([error isKindOfClass:[NSException class]]) {
error = FBLNSErrorFromNSException((NSException *)error);
}
NSAssert([error isKindOfClass:[NSError class]], @"Invalid error type.");

if (![error isKindOfClass:[NSError class]]) {
Expand Down Expand Up @@ -149,9 +142,6 @@ - (instancetype)initWithResolution:(nullable id)resolution {
} else if ([resolution isKindOfClass:[NSError class]]) {
_state = FBLPromiseStateRejected;
_error = (NSError *)resolution;
} else if ([resolution isKindOfClass:[NSException class]]) {
_state = FBLPromiseStateRejected;
_error = FBLNSErrorFromNSException((NSException *)resolution);
} else {
_state = FBLPromiseStateFulfilled;
_value = resolution;
Expand Down Expand Up @@ -253,35 +243,13 @@ - (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {
FBLPromise *promise = [[[self class] alloc] initPending];
FBLPromiseOnFulfillBlock onFulfill;
if (chainedFulfill) {
onFulfill = ^(id __nullable value) {
@try {
[promise fulfill:chainedFulfill(value)];
} @catch (id exception) {
[promise reject:exception];
}
};
} else {
onFulfill = ^(id __nullable value) {
[promise fulfill:value];
};
}
FBLPromiseOnRejectBlock onReject;
if (chainedReject) {
onReject = ^(NSError *error) {
@try {
[promise fulfill:chainedReject(error)];
} @catch (id exception) {
[promise reject:exception];
[self observeOnQueue:queue
fulfill:^(id __nullable value) {
[promise fulfill:chainedFulfill ? chainedFulfill(value) : value];
}
};
} else {
onReject = ^(NSError *error) {
[promise reject:error];
};
}
[self observeOnQueue:queue fulfill:onFulfill reject:onReject];
reject:^(NSError *error) {
[promise fulfill:chainedReject ? chainedReject(error) : error];
}];
return promise;
}

Expand Down
19 changes: 1 addition & 18 deletions Sources/FBLPromises/FBLPromiseError.m
Expand Up @@ -14,23 +14,6 @@
limitations under the License.
*/

#import "FBLPromiseErrorPrivate.h"
#import "FBLPromiseError.h"

NSString *const FBLPromiseErrorDomain = @"com.google.FBLPromises.Error";
NSString *const FBLPromiseErrorUserInfoExceptionNameKey = @"NSExceptionName";
NSString *const FBLPromiseErrorUserInfoExceptionReasonKey = @"NSExceptionReason";
NSString *const FBLPromiseErrorUserInfoExceptionUserInfoKey = @"NSExceptionUserInfo";
NSString *const FBLPromiseErrorUserInfoExceptionReturnAddressesKey = @"NSExceptionReturnAddresses";
NSString *const FBLPromiseErrorUserInfoExceptionCallStackKey = @"NSExceptionCallStack";

NSError *FBLNSErrorFromNSException(NSException *exception) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[FBLPromiseErrorUserInfoExceptionNameKey] = exception.name;
userInfo[FBLPromiseErrorUserInfoExceptionReasonKey] = exception.reason;
userInfo[FBLPromiseErrorUserInfoExceptionUserInfoKey] = exception.userInfo;
userInfo[FBLPromiseErrorUserInfoExceptionReturnAddressesKey] = exception.callStackReturnAddresses;
userInfo[FBLPromiseErrorUserInfoExceptionCallStackKey] = exception.callStackSymbols;
return [[NSError alloc] initWithDomain:FBLPromiseErrorDomain
code:FBLPromiseErrorCodeException
userInfo:userInfo];
}
11 changes: 2 additions & 9 deletions Sources/FBLPromises/include/FBLPromiseError.h
Expand Up @@ -22,19 +22,12 @@ NS_ASSUME_NONNULL_BEGIN
Possible error codes in `FBLPromiseErrorDomain`.
*/
typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) {
/** Objective-C exception was thrown. */
FBLPromiseErrorCodeException = 1,
/** Promise failed to resolve in time. */
FBLPromiseErrorCodeTimedOut = 2,
FBLPromiseErrorCodeTimedOut = 1,
/** Validation predicate returned false. */
FBLPromiseErrorCodeValidationFailure = 3,
FBLPromiseErrorCodeValidationFailure = 2,
} NS_REFINED_FOR_SWIFT;

extern NSString* const FBLPromiseErrorDomain NS_REFINED_FOR_SWIFT;
extern NSString* const FBLPromiseErrorUserInfoExceptionNameKey NS_REFINED_FOR_SWIFT;
extern NSString* const FBLPromiseErrorUserInfoExceptionReasonKey NS_REFINED_FOR_SWIFT;
extern NSString* const FBLPromiseErrorUserInfoExceptionUserInfoKey NS_REFINED_FOR_SWIFT;
extern NSString* const FBLPromiseErrorUserInfoExceptionReturnAddressesKey NS_REFINED_FOR_SWIFT;
extern NSString* const FBLPromiseErrorUserInfoExceptionCallStackKey NS_REFINED_FOR_SWIFT;

NS_ASSUME_NONNULL_END
24 changes: 0 additions & 24 deletions Sources/FBLPromises/include/FBLPromiseErrorPrivate.h

This file was deleted.

1 change: 0 additions & 1 deletion Sources/FBLPromises/include/module.modulemap
Expand Up @@ -31,6 +31,5 @@ module FBLPromises {
header "DotSyntax/FBLPromise+DotSyntax+Validate.h"
header "DotSyntax/FBLPromise+DotSyntax+When.h"

exclude header "FBLPromiseErrorPrivate.h"
exclude header "FBLPromisePrivate.h"
}
17 changes: 0 additions & 17 deletions Sources/FBLPromisesTestHelpers/FBLPromisesTestInteroperability.m
Expand Up @@ -37,14 +37,6 @@ + (FBLPromise *)rejectWithError:(NSError *)error delay:(NSTimeInterval)delay {
return [self promiseWithValue:nil error:error delay:delay];
}

+ (FBLPromise *)rejectWithException:(NSException *)exception {
return [self rejectWithException:exception delay:0.0];
}

+ (FBLPromise *)rejectWithException:(NSException *)exception delay:(NSTimeInterval)delay {
return [self promiseThrowException:exception delay:delay];
}

#pragma mark - Private

/**
Expand All @@ -65,13 +57,4 @@ + (FBLPromise *)promiseWithValue:(nullable id)value
}];
}

+ (FBLPromise *)promiseThrowException:(NSException *__nullable)exception
delay:(NSTimeInterval)delay {
return [FBLPromise async:^(FBLPromiseFulfillBlock __unused _, FBLPromiseRejectBlock __unused __) {
// Can't use asynchronous FBLDelay because the exception will be caught by GCD in that case.
[NSThread sleepForTimeInterval:delay];
@throw exception; // NOLINT
}];
}

@end
Expand Up @@ -28,9 +28,6 @@ NS_ASSUME_NONNULL_BEGIN
+ (FBLPromise<Value> *)rejectWithError:(NSError *)error NS_SWIFT_UNAVAILABLE("");
+ (FBLPromise<Value> *)rejectWithError:(NSError *)error
delay:(NSTimeInterval)delay NS_SWIFT_NAME(reject(_:delay:));
+ (FBLPromise<Value> *)rejectWithException:(NSException *)exception NS_SWIFT_UNAVAILABLE("");
+ (FBLPromise<Value> *)rejectWithException:(NSException *)exception
delay:(NSTimeInterval)delay NS_SWIFT_NAME(reject(_:delay:));

@end

Expand Down
40 changes: 1 addition & 39 deletions Sources/Promises/PromiseError.swift
Expand Up @@ -18,29 +18,16 @@ import FBLPromises
/// Indirectly conforms to `Swift.Error` through conformance to `Swift.CustomNSError` below.
/// Not placing it under extension `Promise` for convenience to avoid collisions with `Swift.Error`.
public enum PromiseError {
case nsException(NSExceptionName, String?, [AnyHashable: Any]?, [NSNumber], [String])
case timedOut
case validationFailure
}

/// Downcasting from `Swift.Error` and `NSException`.
/// Downcasting from `Swift.Error`.
extension PromiseError {
public init?(_ error: Error) {
let error = error as NSError
if error.domain != __FBLPromiseErrorDomain { return nil }
switch error.code {
case __FBLPromiseErrorCode.exception.rawValue:
let errorUserInfo = error.userInfo
guard let name = errorUserInfo[__FBLPromiseErrorUserInfoExceptionNameKey] as? NSExceptionName,
let returnAddresses: [NSNumber] =
errorUserInfo[__FBLPromiseErrorUserInfoExceptionReturnAddressesKey] as? [NSNumber],
let callStack: [String] =
errorUserInfo[__FBLPromiseErrorUserInfoExceptionCallStackKey] as? [String]
else { return nil }
let reason = errorUserInfo[__FBLPromiseErrorUserInfoExceptionReasonKey] as? String
let userInfo =
errorUserInfo[__FBLPromiseErrorUserInfoExceptionUserInfoKey] as? [AnyHashable: Any]
self = .nsException(name, reason, userInfo, returnAddresses, callStack)
case __FBLPromiseErrorCode.timedOut.rawValue:
self = .timedOut
case __FBLPromiseErrorCode.validationFailure.rawValue:
Expand All @@ -49,14 +36,6 @@ extension PromiseError {
return nil
}
}

public init?(_ nsException: NSException) {
self = .nsException(nsException.name,
nsException.reason,
nsException.userInfo,
nsException.callStackReturnAddresses,
nsException.callStackSymbols)
}
}

extension PromiseError: CustomNSError {
Expand All @@ -66,8 +45,6 @@ extension PromiseError: CustomNSError {

public var errorCode: Int {
switch self {
case .nsException:
return __FBLPromiseErrorCode.exception.rawValue
case .timedOut:
return __FBLPromiseErrorCode.timedOut.rawValue
case .validationFailure:
Expand All @@ -76,21 +53,6 @@ extension PromiseError: CustomNSError {
}

public var errorUserInfo: [String: Any] {
if case let .nsException(name, reason, userInfo, returnAddresses, callStack) = self {
return [
__FBLPromiseErrorUserInfoExceptionNameKey: name,
__FBLPromiseErrorUserInfoExceptionReasonKey: reason as Any,
__FBLPromiseErrorUserInfoExceptionUserInfoKey: userInfo as Any,
__FBLPromiseErrorUserInfoExceptionReturnAddressesKey: returnAddresses,
__FBLPromiseErrorUserInfoExceptionCallStackKey: callStack
]
}
return [String: Any]()
}
}

extension PromiseError: Equatable {
public static func == (lhs: PromiseError, rhs: PromiseError) -> Bool {
return (lhs as NSError).isEqual(rhs as NSError)
}
}
16 changes: 0 additions & 16 deletions Tests/FBLPromisesTests/FBLPromise+AsyncTests.m
Expand Up @@ -54,22 +54,6 @@ - (void)testPromiseAsyncReject {
XCTAssertNil(promise.value);
}

- (void)testPromiseAsyncThrow {
// Arrange & Act.
FBLPromise<NSNumber *> *promise =
[FBLPromise async:^(FBLPromiseFulfillBlock __unused _, FBLPromiseRejectBlock __unused __) {
@throw [NSException exceptionWithName:@"name" reason:@"reason" userInfo:nil]; // NOLINT
}];

// Assert.
XCTAssert(FBLWaitForPromisesWithTimeout(10));
XCTAssertEqualObjects(promise.error.domain, FBLPromiseErrorDomain);
XCTAssertEqual(promise.error.code, FBLPromiseErrorCodeException);
XCTAssertEqualObjects(promise.error.userInfo[FBLPromiseErrorUserInfoExceptionNameKey], @"name");
XCTAssertEqualObjects(promise.error.userInfo[FBLPromiseErrorUserInfoExceptionReasonKey],
@"reason");
}

/**
Promise created with `async` should not deallocate until it gets resolved.
*/
Expand Down

0 comments on commit 2c5e847

Please sign in to comment.