diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.pbxproj b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a947abc3 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.pbxproj @@ -0,0 +1,362 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 2A32A3EC264C227E0056C23B /* DerivedBehaviorApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A32A3EB264C227E0056C23B /* DerivedBehaviorApp.swift */; }; + 2A32A3EE264C227E0056C23B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A32A3ED264C227E0056C23B /* ContentView.swift */; }; + 2A32A3F0264C227E0056C23B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A32A3EF264C227E0056C23B /* Assets.xcassets */; }; + 2A32A3F3264C227E0056C23B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2A32A3F2264C227E0056C23B /* Preview Assets.xcassets */; }; + 4B6429EC264C3801006FB282 /* TcaContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6429EB264C3801006FB282 /* TcaContentView.swift */; }; + 4B6429EF264C386F006FB282 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = 4B6429EE264C386F006FB282 /* ComposableArchitecture */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2A32A3E8264C227E0056C23B /* DerivedBehavior.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DerivedBehavior.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2A32A3EB264C227E0056C23B /* DerivedBehaviorApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DerivedBehaviorApp.swift; sourceTree = ""; }; + 2A32A3ED264C227E0056C23B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 2A32A3EF264C227E0056C23B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 2A32A3F2264C227E0056C23B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2A32A3F4264C227E0056C23B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4B6429EB264C3801006FB282 /* TcaContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TcaContentView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 2A32A3E5264C227E0056C23B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B6429EF264C386F006FB282 /* ComposableArchitecture in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2A32A3DF264C227E0056C23B = { + isa = PBXGroup; + children = ( + 2A32A3EA264C227E0056C23B /* DerivedBehavior */, + 2A32A3E9264C227E0056C23B /* Products */, + ); + sourceTree = ""; + }; + 2A32A3E9264C227E0056C23B /* Products */ = { + isa = PBXGroup; + children = ( + 2A32A3E8264C227E0056C23B /* DerivedBehavior.app */, + ); + name = Products; + sourceTree = ""; + }; + 2A32A3EA264C227E0056C23B /* DerivedBehavior */ = { + isa = PBXGroup; + children = ( + 2A32A3EB264C227E0056C23B /* DerivedBehaviorApp.swift */, + 2A32A3ED264C227E0056C23B /* ContentView.swift */, + 4B6429EB264C3801006FB282 /* TcaContentView.swift */, + 2A32A3EF264C227E0056C23B /* Assets.xcassets */, + 2A32A3F4264C227E0056C23B /* Info.plist */, + 2A32A3F1264C227E0056C23B /* Preview Content */, + ); + path = DerivedBehavior; + sourceTree = ""; + }; + 2A32A3F1264C227E0056C23B /* Preview Content */ = { + isa = PBXGroup; + children = ( + 2A32A3F2264C227E0056C23B /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 2A32A3E7264C227E0056C23B /* DerivedBehavior */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2A32A3F7264C227E0056C23B /* Build configuration list for PBXNativeTarget "DerivedBehavior" */; + buildPhases = ( + 2A32A3E4264C227E0056C23B /* Sources */, + 2A32A3E5264C227E0056C23B /* Frameworks */, + 2A32A3E6264C227E0056C23B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DerivedBehavior; + packageProductDependencies = ( + 4B6429EE264C386F006FB282 /* ComposableArchitecture */, + ); + productName = DerivedBehavior; + productReference = 2A32A3E8264C227E0056C23B /* DerivedBehavior.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 2A32A3E0264C227E0056C23B /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + 2A32A3E7264C227E0056C23B = { + CreatedOnToolsVersion = 12.5; + }; + }; + }; + buildConfigurationList = 2A32A3E3264C227E0056C23B /* Build configuration list for PBXProject "DerivedBehavior" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 2A32A3DF264C227E0056C23B; + packageReferences = ( + 4B6429ED264C386F006FB282 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, + ); + productRefGroup = 2A32A3E9264C227E0056C23B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 2A32A3E7264C227E0056C23B /* DerivedBehavior */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2A32A3E6264C227E0056C23B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A32A3F3264C227E0056C23B /* Preview Assets.xcassets in Resources */, + 2A32A3F0264C227E0056C23B /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 2A32A3E4264C227E0056C23B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2A32A3EE264C227E0056C23B /* ContentView.swift in Sources */, + 4B6429EC264C3801006FB282 /* TcaContentView.swift in Sources */, + 2A32A3EC264C227E0056C23B /* DerivedBehaviorApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 2A32A3F5264C227E0056C23B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + 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; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 2A32A3F6264C227E0056C23B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + 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; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 2A32A3F8264C227E0056C23B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"DerivedBehavior/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = DerivedBehavior/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.DerivedBehavior; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 2A32A3F9264C227E0056C23B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"DerivedBehavior/Preview Content\""; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = DerivedBehavior/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = co.pointfree.DerivedBehavior; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2A32A3E3264C227E0056C23B /* Build configuration list for PBXProject "DerivedBehavior" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A32A3F5264C227E0056C23B /* Debug */, + 2A32A3F6264C227E0056C23B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2A32A3F7264C227E0056C23B /* Build configuration list for PBXNativeTarget "DerivedBehavior" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2A32A3F8264C227E0056C23B /* Debug */, + 2A32A3F9264C227E0056C23B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 4B6429ED264C386F006FB282 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.18.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 4B6429EE264C386F006FB282 /* ComposableArchitecture */ = { + isa = XCSwiftPackageProductDependency; + package = 4B6429ED264C386F006FB282 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; + productName = ComposableArchitecture; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 2A32A3E0264C227E0056C23B /* Project object */; +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AccentColor.colorset/Contents.json b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AppIcon.appiconset/Contents.json b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/Contents.json b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/ContentView.swift b/0151-tca-performance/DerivedBehavior/DerivedBehavior/ContentView.swift new file mode 100644 index 00000000..bb52ec28 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/ContentView.swift @@ -0,0 +1,123 @@ +import Combine +import SwiftUI + +class AppViewModel: ObservableObject { + let counter: CounterViewModel + let profile: ProfileViewModel + var cancellables: Set = [] + + init( + counter: CounterViewModel = .init(), + profile: ProfileViewModel = .init() + ) { + self.counter = counter + self.profile = profile + + self.counter.objectWillChange + .sink { [weak self] in self?.objectWillChange.send() } + .store(in: &self.cancellables) + self.profile.objectWillChange + .sink { [weak self] in self?.objectWillChange.send() } + .store(in: &self.cancellables) + + var counterIsUpdating = false + var profileIsUpdating = false + + self.counter.$favorites + .sink { [weak self] in + guard !counterIsUpdating else { return } + profileIsUpdating = true + defer { profileIsUpdating = false } + self?.profile.favorites = $0 + } + .store(in: &self.cancellables) + + self.profile.$favorites + .sink { [weak self] in + counterIsUpdating = true + defer { counterIsUpdating = false } + guard !profileIsUpdating else { return } + self?.counter.favorites = $0 + } + .store(in: &self.cancellables) + } +} + +struct VanillaContentView: View { + @ObservedObject var viewModel: AppViewModel +// @ObservedObject var counterViewModel: CounterViewModel +// @ObservedObject var profileViewModel: ProfileViewModel + + var body: some View { + TabView { + VanillaCounterView(viewModel: self.viewModel.counter) + .tabItem { Text("Counter \(self.viewModel.counter.count)") } + + VanillaProfileView(viewModel: self.viewModel.profile) + .tabItem { Text("Profile \(self.viewModel.profile.favorites.count)") } + } + } +} + +class CounterViewModel: ObservableObject { + @Published var count = 0 + @Published var favorites: Set = [] +} + +struct VanillaCounterView: View { + @ObservedObject var viewModel: CounterViewModel + + var body: some View { +// self.$viewModel.count + + VStack { + HStack { + Button("-") { self.viewModel.count -= 1 } + Text("\(self.viewModel.count)") + Button("+") { self.viewModel.count += 1 } + } + + if self.viewModel.favorites.contains(self.viewModel.count) { + Button("Remove") { + self.viewModel.favorites.remove(self.viewModel.count) + } + } else { + Button("Save") { + self.viewModel.favorites.insert(self.viewModel.count) + } + } + } + } +} + +class ProfileViewModel: ObservableObject { + @Published var favorites: Set = [] +} + +struct VanillaProfileView: View { + @ObservedObject var viewModel: ProfileViewModel + + var body: some View { + List { + ForEach(self.viewModel.favorites.sorted(), id: \.self) { number in + HStack { + Text("\(number)") + Spacer() + Button("Remove") { + self.viewModel.favorites.remove(number) + } + } + } + } + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + VanillaContentView( + viewModel: .init() +// counterViewModel: .init(), +// profileViewModel: .init() + ) + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/DerivedBehaviorApp.swift b/0151-tca-performance/DerivedBehavior/DerivedBehavior/DerivedBehaviorApp.swift new file mode 100644 index 00000000..f3d0e24d --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/DerivedBehaviorApp.swift @@ -0,0 +1,21 @@ +// +// DerivedBehaviorApp.swift +// DerivedBehavior +// +// Created by Point-Free on 5/12/21. +// + +import SwiftUI + +@main +struct DerivedBehaviorApp: App { + var body: some Scene { + WindowGroup { + VanillaContentView( + viewModel: .init() +// counterViewModel: .init(), +// profileViewModel: .init() + ) + } + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/Info.plist b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Info.plist new file mode 100644 index 00000000..efc211a0 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UIApplicationSupportsIndirectInputEvents + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/Preview Content/Preview Assets.xcassets/Contents.json b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/0151-tca-performance/DerivedBehavior/DerivedBehavior/TcaContentView.swift b/0151-tca-performance/DerivedBehavior/DerivedBehavior/TcaContentView.swift new file mode 100644 index 00000000..a718839c --- /dev/null +++ b/0151-tca-performance/DerivedBehavior/DerivedBehavior/TcaContentView.swift @@ -0,0 +1,240 @@ +import ComposableArchitecture +import SwiftUI + +struct AppState: Equatable { + var count = 0 + var favorites: Set = [] + var slider = SliderState() + + var counter: CounterState { + get { + .init(count: self.count, favorites: self.favorites) + } + set { + self.count = newValue.count + self.favorites = newValue.favorites + } + } + var profile: ProfileState { + get { + .init(favorites: self.favorites) + } + set { + self.favorites = newValue.favorites + } + } +} + +enum AppAction { + case counter(CounterAction) + case profile(ProfileAction) + case slider(SliderAction) +} + +struct AppEnvironment {} + +let appReducer = Reducer.combine( + counterReducer.pullback( + state: \AppState.counter, + action: /AppAction.counter, + environment: { (_: AppEnvironment) in CounterEnvironment() } + ), + profileReducer.pullback( + state: \AppState.profile, + action: /AppAction.profile, + environment: { (_: AppEnvironment) in ProfileEnvironment() } + ), + sliderReducer.pullback( + state: \.slider, + action: /AppAction.slider, + environment: { (_: AppEnvironment) in SliderEnvironment() } + ) +) + +struct TcaContentView: View { + let store: Store + +// @ObservedObject var viewStore: ViewStore +// +// init(store: Store) { +// self.store = store +// self.viewStore = ViewStore(self.store) +// } + + struct ViewState: Equatable { + let count: Int +// let favorites: Set + let favoritesCount: Int + + init(state: AppState) { + self.count = state.count +// self.favorites = state.favorites + self.favoritesCount = state.favorites.count + } + } + + var body: some View { + WithViewStore(self.store.scope(state: ViewState.init)) { viewStore in + TabView { + TcaCounterView( + store: self.store + .scope( + state: \.counter, + action: AppAction.counter + ) + ) + .tabItem { Text("Counter \(viewStore.count)") } + + TcaProfileView( + store: self.store + .scope( + state: \.profile, + action: AppAction.profile + ) + ) + .tabItem { Text("Profile \(viewStore.favoritesCount)") } + + SliderView( + store: self.store.scope( + state: \.slider, + action: AppAction.slider + ) + ) + .tabItem { Text("Slider") } + } + } + .debug("ContentView") + } +} + +struct CounterState: Equatable { + var count = 0 + var favorites: Set = [] +} +enum CounterAction { + case incrementButtonTapped + case decrementButtonTapped + case saveButtonTapped + case removeButtonTapped +} +struct CounterEnvironment {} + +let counterReducer = Reducer { state, action, _ in + switch action { + case .incrementButtonTapped: + state.count += 1 + return .none + case .decrementButtonTapped: + state.count -= 1 + return .none + case .saveButtonTapped: + state.favorites.insert(state.count) + return .none + case .removeButtonTapped: + state.favorites.remove(state.count) + return .none + } +} + +struct TcaCounterView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + VStack { + HStack { + Button("-") { viewStore.send(.decrementButtonTapped) } + Text("\(viewStore.count)") + Button("+") { viewStore.send(.incrementButtonTapped) } + } + + if viewStore.favorites.contains(viewStore.count) { + Button("Remove") { + viewStore.send(.removeButtonTapped) + } + } else { + Button("Save") { + viewStore.send(.saveButtonTapped) + } + } + } + } + .debug("CounterView") + } +} + +struct ProfileState: Equatable { + var favorites: Set = [] +} +enum ProfileAction { + case removeButtonTapped(Int) +} +struct ProfileEnvironment {} + +let profileReducer = Reducer { state, action, _ in + switch action { + case let .removeButtonTapped(number): + state.favorites.remove(number) + return .none + } +} + +struct TcaProfileView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + List { + ForEach(viewStore.favorites.sorted(), id: \.self) { number in + HStack { + Text("\(number)") + Spacer() + Button("Remove") { + viewStore.send(.removeButtonTapped(number)) + } + } + } + } + } + .debug("ProfileView") + } +} + +struct SliderState: Equatable { + var value = 0.0 +} +enum SliderAction { + case setValue(Double) +} +struct SliderEnvironment {} + +let sliderReducer = Reducer { state, action, _ in + switch action { + case let .setValue(value): + state.value = value + return .none + } +} + +struct SliderView: View { + let store: Store + + var body: some View { + WithViewStore(self.store) { viewStore in + Slider(value: viewStore.binding(get: \.value, send: SliderAction.setValue)) + } + .debug("SliderView") + } +} + +struct TcaContentView_Previews: PreviewProvider { + static var previews: some View { + TcaContentView( + store: Store( + initialState: AppState(), + reducer: appReducer, + environment: AppEnvironment() + ) + ) + } +} diff --git a/0151-tca-performance/README.md b/0151-tca-performance/README.md new file mode 100644 index 00000000..9c662499 --- /dev/null +++ b/0151-tca-performance/README.md @@ -0,0 +1,7 @@ +## [Point-Free](https://www.pointfree.co) + +> #### This directory contains code from Point-Free Episode: [Composable Architecture Performance: View Stores and Scoping](https://www.pointfree.co/episodes/ep151-composable-architecture-performance-view-stores-and-scoping) +> +> Did you know the Composable Architecture’s `scope` operation and `ViewStore` are performance tools? We’ll explore how to diagnose your app’s performance, how `scope` can help, and fix a few long-standing performance issues in the library itself. + +For the section of the episode where we refactor the Composable Architecture, see [this GitHub pull request](https://github.com/pointfreeco/swift-composable-architecture/pull/616). diff --git a/README.md b/README.md index f9068c17..5b0af290 100644 --- a/README.md +++ b/README.md @@ -152,3 +152,4 @@ This repository is the home of code written on episodes of [Point-Free](https:// 1. [Derived Behavior: Collections](0148-derived-behavior-pt3) 1. [Derived Behavior: Optionals and Enums](0149-derived-behavior-pt4) 1. [Derived Behavior: The Point](0150-derived-behavior-pt5) +1. [Composable Architecture Performance: View Stores and Scoping](0151-tca-performance)