Compare commits

...

6 Commits

Author SHA1 Message Date
Tanner Bennett b908d66b94 Argument input view work 2020-10-18 17:09:47 -05:00
Tanner Bennett e6c324d761 FLEXMethod.imagePath should use the method's IMP
We can already tell which image the class comes from, and that is usually the same as the original method implementation. What is more useful to know is the image of the _current_ implementation.
2020-10-18 17:04:44 -05:00
Tanner Bennett 784e030e44 Silence new weird documentation warning… 2020-10-18 17:04:44 -05:00
Tanner Bennett 046eb49f7b Commit FLEXTests scheme 2020-10-18 17:04:44 -05:00
Tanner Bennett 6ec1814f2e Xcode 12 upgrade check for main scheme 2020-10-18 17:04:44 -05:00
Tanner Bennett f179545972 Add FLEX_EXIT_IF_NO_CTORS 2020-10-18 17:02:09 -05:00
21 changed files with 238 additions and 41 deletions
@@ -11,7 +11,7 @@
#import "FLEXRuntimeUtility.h"
#import "FLEXTypeEncodingParser.h"
@interface FLEXArgumentInputStructView ()
@interface FLEXArgumentInputStructView () <FLEXArgumentInputViewDelegate>
@property (nonatomic) NSArray<FLEXArgumentInputView *> *argumentInputViews;
@@ -30,8 +30,11 @@
NSUInteger fieldIndex,
NSUInteger fieldOffset) {
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory argumentInputViewForTypeEncoding:fieldTypeEncoding];
FLEXArgumentInputView *inputView = [FLEXArgumentInputViewFactory
argumentInputViewForTypeEncoding:fieldTypeEncoding
];
inputView.targetSize = FLEXArgumentInputViewSizeSmall;
inputView.delegate = self;
if (fieldIndex < customTitles.count) {
inputView.title = customTitles[fieldIndex];
@@ -125,15 +128,45 @@
return boxedStruct;
}
- (BOOL)inputViewIsFirstResponder {
BOOL isFirstResponder = NO;
- (FLEXArgumentInputView *)firstResponderInputView {
for (FLEXArgumentInputView *inputView in self.argumentInputViews) {
if ([inputView inputViewIsFirstResponder]) {
isFirstResponder = YES;
break;
return inputView;
}
}
return isFirstResponder;
return nil;
}
- (BOOL)resignFirstResponder {
FLEXArgumentInputView *responder = [self firstResponderInputView];
if (responder) {
return [responder resignFirstResponder];
} else {
return [super resignFirstResponder];
}
}
#pragma mark - FLEXArgumentInputViewDelegate
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
// Nothing to see here
}
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
if (self.argumentInputViews.lastObject == inputView) {
// If this is our last or only input view,
// notify the delegate or dismiss the keyboard
if (self.delegate) {
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
} else {
[inputView resignFirstResponder];
}
} else {
NSInteger idx = [self.argumentInputViews indexOfObject:inputView];
[self.argumentInputViews[idx+1] becomeFirstResponder];
}
}
@@ -8,11 +8,15 @@
#import "FLEXArgumentInputView.h"
NS_ASSUME_NONNULL_BEGIN
@interface FLEXArgumentInputTextView : FLEXArgumentInputView <UITextViewDelegate>
// For subclass eyes only
@property (nonatomic, readonly) UITextView *inputTextView;
@property (nonatomic) NSString *inputPlaceholderText;
@property (nonatomic, nullable) NSString *inputPlaceholderText;
@end
NS_ASSUME_NONNULL_END
@@ -31,7 +31,7 @@
self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo;
self.inputTextView.delegate = self;
self.inputTextView.inputAccessoryView = [self createToolBar];
self.inputAccessoryView = [self createToolBar];
if (@available(iOS 11, *)) {
[self.inputTextView.layer setValue:@YES forKey:@"continuousCorners"];
} else {
@@ -51,6 +51,14 @@
return self;
}
- (UIToolbar *)inputAccessoryView {
return (id)self.inputTextView.inputAccessoryView;
}
- (void)setInputAccessoryView:(UIToolbar *)inputAccessoryView {
self.inputTextView.inputAccessoryView = inputAccessoryView;
}
#pragma mark - Private
- (UIToolbar *)createToolBar {
@@ -62,7 +70,7 @@
];
UIBarButtonItem *pasteItem = [[UIBarButtonItem alloc]
initWithTitle:@"Paste" style:UIBarButtonItemStyleDone
target:self.inputTextView action:@selector(paste:)
target:self action:@selector(paste:)
];
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
@@ -91,6 +99,16 @@
return self.placeholderLabel.text;
}
- (void)paste:(id)sender {
[self.inputTextView paste:sender];
if (self.delegate) {
[self.delegate argumentInputViewWantsNextAsFirstResponder:self];
} else {
[self.inputTextView resignFirstResponder];
}
}
#pragma mark - Superclass Overrides
@@ -98,6 +116,18 @@
return self.inputTextView.isFirstResponder;
}
- (BOOL)becomeFirstResponder {
return [self.inputTextView becomeFirstResponder];
}
- (BOOL)resignFirstResponder {
if (self.inputViewIsFirstResponder) {
return self.inputTextView.resignFirstResponder;
} else {
return [super resignFirstResponder];
}
}
#pragma mark - Layout and Sizing
@@ -19,6 +19,8 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
@protocol FLEXArgumentInputViewDelegate;
NS_ASSUME_NONNULL_BEGIN
@interface FLEXArgumentInputView : UIView
- (instancetype)initWithArgumentTypeEncoding:(const char *)typeEncoding;
@@ -31,23 +33,25 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
/// Primitive types and structs should/will be boxed in NSValue containers.
/// Concrete subclasses should override both the setter and getter for this property.
/// Subclasses can call super.inputValue to access a backing store for the value.
@property (nonatomic) id inputValue;
@property (nonatomic, nullable) id inputValue;
/// Setting this value to large will make some argument input views increase the size of their input field(s).
/// Useful to increase the use of space if there is only one input view on screen (i.e. for property and ivar editing).
@property (nonatomic) FLEXArgumentInputViewSize targetSize;
/// Users of the input view can get delegate callbacks for incremental changes in user input.
@property (nonatomic, weak) id <FLEXArgumentInputViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <FLEXArgumentInputViewDelegate> delegate;
// Subclasses can override
@property (nonatomic, nullable) UIToolbar *inputAccessoryView;
/// If the input view has one or more text views, returns YES when one of them is focused.
@property (nonatomic, readonly) BOOL inputViewIsFirstResponder;
/// For subclasses to indicate that they can handle editing a field the give type and value.
/// Used by FLEXArgumentInputViewFactory to create appropriate input views.
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(id)value;
+ (BOOL)supportsObjCType:(const char *)type withCurrentValue:(nullable id)value;
// For subclass eyes only
@@ -59,6 +63,11 @@ typedef NS_ENUM(NSUInteger, FLEXArgumentInputViewSize) {
@protocol FLEXArgumentInputViewDelegate <NSObject>
//- (void)
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView;
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)argumentInputView;
@end
NS_ASSUME_NONNULL_END
@@ -46,6 +46,7 @@
];
inputView.backgroundColor = self.view.backgroundColor;
inputView.inputValue = currentValue;
inputView.delegate = self;
self.fieldEditorView.argumentInputViews = @[inputView];
}
@@ -118,8 +118,8 @@
[self exploreObjectOrPopViewController:self.currentValue];
}
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)argumentInputView {
if ([argumentInputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
if ([inputView isKindOfClass:[FLEXArgumentInputSwitchView class]]) {
[self actionButtonPressed:nil];
}
}
@@ -64,6 +64,7 @@
inputView.backgroundColor = self.view.backgroundColor;
inputView.title = methodComponent;
inputView.delegate = self;
[argumentInputViews addObject:inputView];
argumentIndex++;
}
@@ -6,19 +6,20 @@
// Copyright (c) 2020 Flipboard. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "FLEXArgumentInputView.h"
@class FLEXFieldEditorView;
@class FLEXArgumentInputView;
/// Provides a screen for editing or configuring one or more variables.
@interface FLEXVariableEditorViewController : UIViewController
@interface FLEXVariableEditorViewController : UIViewController <FLEXArgumentInputViewDelegate>
+ (instancetype)target:(id)target;
- (id)initWithTarget:(id)target;
// Convenience accessor since many subclasses only use one input view
@property (nonatomic, readonly) FLEXArgumentInputView *firstInputView;
// Also a convenience accessor
@property (nonatomic, readonly) NSArray<FLEXArgumentInputView *> *inputViews;
// For subclass use only.
@property (nonatomic, readonly) id target;
@@ -54,8 +54,8 @@
#pragma mark - UIViewController methods
- (void)keyboardDidShow:(NSNotification *)notification {
CGRect keyboardRectInWindow = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGSize keyboardSize = [self.view convertRect:keyboardRectInWindow fromView:nil].size;
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGSize keyboardSize = [self.view convertRect:keyboardRect fromView:nil].size;
UIEdgeInsets scrollInsets = self.scrollView.contentInset;
scrollInsets.bottom = keyboardSize.height;
self.scrollView.contentInset = scrollInsets;
@@ -114,7 +114,11 @@
#pragma mark - Public
- (FLEXArgumentInputView *)firstInputView {
return [self.fieldEditorView argumentInputViews].firstObject;
return self.fieldEditorView.argumentInputViews.firstObject;
}
- (NSArray<FLEXArgumentInputView *> *)inputViews {
return self.fieldEditorView.argumentInputViews;
}
- (void)actionButtonPressed:(id)sender {
@@ -134,4 +138,20 @@
}
}
#pragma mark - FLEXArgumentInputViewDelegate
- (void)argumentInputViewValueDidChange:(FLEXArgumentInputView *)inputView {
// Subclasses might want to do something here but we don't
}
- (void)argumentInputViewWantsNextAsFirstResponder:(FLEXArgumentInputView *)inputView {
if (self.inputViews.lastObject == inputView) {
// If this is our last or only input view, dismiss the keyboard
[inputView resignFirstResponder];
} else {
NSInteger idx = [self.inputViews indexOfObject:inputView];
[self.inputViews[idx+1] becomeFirstResponder];
}
}
@end
@@ -15,7 +15,7 @@
@implementation FLEXShortcutsFactory (UIApplication)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// sharedApplication class property possibly not added
// as a literal class property until iOS 10
FLEXRuntimeUtilityTryAddObjectProperty(
@@ -40,7 +40,7 @@
@implementation FLEXShortcutsFactory (Views)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// A quirk of UIView and some other classes: a lot of the `@property`s are
// not actually properties from the perspective of the runtime.
//
@@ -131,7 +131,7 @@
@implementation FLEXShortcutsFactory (ViewControllers)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// toolbarItems is not really a property, make it one
FLEXRuntimeUtilityTryAddObjectProperty(3, toolbarItems, UIViewController.class, NSArray);
@@ -151,7 +151,7 @@
@implementation FLEXShortcutsFactory (UIImage)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.methods(@[
@"CGImage", @"CIImage"
]).properties(@[
@@ -171,7 +171,7 @@
@implementation FLEXShortcutsFactory (NSBundle)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.properties(@[
@"bundleIdentifier", @"principalClass",
@"infoDictionary", @"bundlePath",
@@ -186,7 +186,7 @@
@implementation FLEXShortcutsFactory (Classes)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.classMethods(@[@"new", @"alloc"]).forClass(NSObject.flex_metaclass);
}
@@ -197,7 +197,7 @@
@implementation FLEXShortcutsFactory (Activities)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// Property was added in iOS 10 but we want it on iOS 9 too
FLEXRuntimeUtilityTryAddNonatomicProperty(9, item, UIActivityItemProvider.class, id, PropertyKey(ReadOnly));
@@ -217,7 +217,7 @@
@implementation FLEXShortcutsFactory (Blocks)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.methods(@[@"invoke"]).forClass(NSClassFromString(@"NSBlock"));
}
@@ -227,7 +227,7 @@
@implementation FLEXShortcutsFactory (Foundation)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
self.append.properties(@[
@"configuration", @"delegate", @"delegateQueue", @"sessionDescription",
]).methods(@[
@@ -13,7 +13,7 @@
@interface NSDictionary (ObjcRuntime)
/// \c kFLEXPropertyAttributeKeyTypeEncoding is the only required key.
/// Keys representing a boolean value should have a value of \c @YES instead of an empty string.
/// Keys representing a boolean value should have a value of \c YES instead of an empty string.
- (NSString *)propertyAttributesString;
+ (instancetype)attributesDictionaryForProperty:(objc_property_t)property;
@@ -146,7 +146,7 @@ NSArray<FLEXMethod *> *FLEXGetAllMethods(_Nullable Class cls, BOOL instance) {
@interface NSProxy (AnyObjectAdditions) @end
@implementation NSProxy (AnyObjectAdditions)
+ (void)load { FLEX_EXIT_IF_TESTING()
+ (void)load { FLEX_EXIT_IF_NO_CTORS()
// We need to get all of the methods in this file and add them to NSProxy.
// To do this we we need the class itself and it's metaclass.
// Edit: also add them to Swift._SwiftObject
@@ -12,7 +12,7 @@
@interface NSString (Utilities)
/// A dictionary of property attributes if the receiver is a valid property attributes string.
/// Values are either a string or \c @YES. Boolean attributes which are false will not be
/// Values are either a string or \c YES. Boolean attributes which are false will not be
/// present in the dictionary. See this link on how to construct a proper attributes string:
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
///
+12 -2
View File
@@ -9,8 +9,18 @@
#ifndef FLEXMacros_h
#define FLEXMacros_h
// Used to prevent loading of pre-registered shortcuts and runtime categories in a test environment
#define FLEX_EXIT_IF_TESTING() if (NSClassFromString(@"XCTest")) return;
#define flex_keywordify class NSObject;
#define ctor flex_keywordify __attribute__((constructor)) void __flex_ctor_##__LINE__()
#define dtor flex_keywordify __attribute__((destructor)) void __flex_dtor_##__LINE__()
// A macro to check if we are running in a test environment
#define FLEX_IS_TESTING() (NSClassFromString(@"XCTest") != nil)
/// Whether we want the majority of constructors to run upon load or not.
extern BOOL FLEXConstructorsShouldRun();
/// A macro to return from the current procedure if we don't want to run constructors
#define FLEX_EXIT_IF_NO_CTORS() if (!FLEXConstructorsShouldRun()) return;
/// Rounds down to the nearest "point" coordinate
NS_INLINE CGFloat FLEXFloor(CGFloat x) {
+15 -1
View File
@@ -11,8 +11,22 @@
#import "FLEXResources.h"
#import "FLEXWindow.h"
#import <ImageIO/ImageIO.h>
#import <zlib.h>
#import <objc/runtime.h>
#import <zlib.h>
BOOL FLEXConstructorsShouldRun() {
static BOOL _FLEXConstructorsShouldRun_storage = YES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *key = @"FLEX_SKIP_INIT";
if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
_FLEXConstructorsShouldRun_storage = NO;
}
});
return _FLEXConstructorsShouldRun_storage;
}
@implementation FLEXUtility
@@ -183,7 +183,7 @@
- (NSString *)imagePath {
if (!_imagePath) {
Dl_info exeInfo;
if (dladdr(_objc_method, &exeInfo)) {
if (dladdr(_implementation, &exeInfo)) {
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : @"";
}
}
@@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
/// A human-readable version of the property attributes.
@property (nonatomic, readonly) NSString *fullDeclaration;
/// A dictionary of the property attributes.
/// Values are either a string or \c @YES. Boolean attributes
/// Values are either a string or \c YES. Boolean attributes
/// which are false will not be present in the dictionary.
@property (nonatomic, readonly) NSDictionary *dictionary;
@@ -64,6 +64,6 @@
@property (nonatomic, readonly) NSString *typeEncoding;
/// The method's return type.
@property (nonatomic, readonly) FLEXTypeEncoding returnType;
/// \c @YES if this is an instance method, \c @NO if it is a class method, or \c nil if unspecified
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
@property (nonatomic, readonly) NSNumber *instance;
@end
+7 -1
View File
@@ -1641,7 +1641,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = FLEX;
LastUpgradeCheck = 1110;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = Flipboard;
TargetAttributes = {
1C27A8B51F0E5A0300F0D02D = {
@@ -1888,6 +1888,7 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = U3LST7M92S;
INFOPLIST_FILE = FLEXTests/Info.plist;
@@ -1904,6 +1905,7 @@
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
DEVELOPMENT_TEAM = U3LST7M92S;
INFOPLIST_FILE = FLEXTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
@@ -1936,6 +1938,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -1997,6 +2000,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -2033,6 +2037,7 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -2065,6 +2070,7 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1110"
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C27A8B51F0E5A0300F0D02D"
BuildableName = "FLEXTests.xctest"
BlueprintName = "FLEXTests"
ReferencedContainer = "container:FLEX.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "FLEX_SKIP_INIT"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1C27A8B51F0E5A0300F0D02D"
BuildableName = "FLEXTests.xctest"
BlueprintName = "FLEXTests"
ReferencedContainer = "container:FLEX.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>