so the the time it took to process those five lines of text in TCA was 9 seconds of CPU time with multi store solution was .9 [Music] seconds he so my name is Kristof zaboski and Shai started us with TCA which makes my life a little easier because I'm going to talk about how TCA Works in bigger projects and the kind of problems you run into the best practices you should be aware of a little bit about me I do a lot of Open Source I work as a consultant so if you if you are building Swift applications and you want to make your team more efficient or just have easier live developing features and be uh better at scaling the business I'm the person you can hire and what I focus on is I I come into companies and I work on architecture and developer tooling to make day-to-day workflows easier for engineers and the open source that I do is used by over 880,000 applications from Disney Airbnb big companies I led the New York Times before and I worked at the browser company before as well on the biggest TCA project and TCA is interesting because back in 2016 I did one of the first Community talks about architecture and even though I was recommending mvvm with coordinators I was very excited about the idea of Redux the promise of Redux and there was a framework called R Swift that um was the first implementation of this pattern and I predicted that it's going to be the direction that iOS will probably go forwards Swift UI didn't exist back then so we had this issue where we had imperative UI framework but we were going into like okay what is the easier State Management solution and I think point three delivered really well with their framework of composable architecture on the whole premise of U Redux and Elm and so the the pattern itself is not just based on Redux but also Elm microarchitecture I don't know if anyone is familiar with Elm but basically it's un Direction data flow there's single source of Ro as Shai said which is really important because most of the bugs in our applications are because there's mutable State and so the way you synchronize state is is really really important and TCA is very good at ergonomics and consistency across the whole code base I like to look at this graph because I'm going to talk about a lot of issues that happen in TCA at larger scale so for that to really reason about you need to understand how the flow of data and uh actions happen in the system so Shai showed you how to actually implement it but this is the the way it really interacts so we have state which is basically like you could think about it as snapshot when it gets fed into a view that it just random right it's a static snapshot of what the current state is then actions are fed from views to reducers and a register you can think it's an object now but you can think about it as a pure function input output uh inputs are State and the action and the output is the new version of State plus side effects so reducer is the thing that's mutating stuff and there's environment which are the dependencies which you can use for the other side effects so every act every kind of mutation you have to do has to come through action so there's a lot of action processing which will which will come to bite Us in the ass in uh later slides so TCA framework itself very well written very well documented now even more so than before um it's fully tested it has great ergonomics as I showed you and obviously because point3 is a tutorial website they go in depth and not you just you don't just get the framework but you also get the reasoning and thinking behind why it works the way it works which I think is really valuable so if you are using TCA I would recommend you also watch their videos to understand the you know the all the requirements and thinking that came with the framework because there's always constant Pros to all architectures so being being able to understand why uh decisions were made is really important and I collaborated with the point fre team as I work at the browser company so some of the some of the improvements that I'm going to talk about already ended up in the Upstream some of them are available in a fork that's uh public my experience with TCA is I built uh five full applications with TCA like large scale applications I build one for the New York Times which got killed unfortunately I worked at the browser company for almost two years which is the biggest TCA um code base and um I consulted with a bunch of companies because as I started writing about the problems that you'll face with TCA people started reaching out through my blog and uh hiring me to help their their issues solve their issues and I also worked with point3 and the community to make the ergonomics better and uh fix a bunch of issues as well and there's four areas that I want to talk about in in terms of TCA which is the stability maintainability of our code bases the performance of TCA and what you have to be careful there and the kind of tooling you can build for um this kind of architecture so as Shai showed you the the default for TCA has always been exhaustive testing and that means is that in your test you have to reproduce everything that your actual implementation does so if you are scheduling timers if you're doing any any kind of side effects any kind of mutations you have to reproduce that in every test that actually verifies that logic which means that if you have a single action going into the system you have to create a test that tests all of it right which is fine for simple applications it's not really it's really it actually goes against TD tddd best practices because in when you do a lot of testing as I do with open source like sorcery you have to like the the recommendation is one test per assertion so that if you have complex implementation and something breaks you know immediately what broke and why rather than you have to go and read the test body and analyze what the problem is so with test D development the biggest thing that I want is I want the confidence to change my implementation detail let's say I have an application that does so like the pomodora timer right I know what the user Behavior are I could be implementing that behavior very differently in the actual internal details of the application and as long as I have test coverage on the on the user stories I should not care how the implementation details change my test should still run if I change something in the implementation detail but the behavior is the same the test should not change if the test change that's fragility and that actually means means that the tests are bad and it's an antipattern tdd and it's also one of the main reasons people say that tests are not worth it because when they wrote fragile test and every time they change implementation details they have to change the test there is no value in it because it it removes that confidence so test should be focused uh if test fails you immediately know what's broken you don't analyze it and if you have a single user action that generates 10 different side effects I don't want to have a single test that does those 10 different iterations I want 10 tests that each individually checks the side effect that I want to test because then if I make a mistake it's usually one side effect that breaks not all of them so I immediately would know okay I broke archive so in the browser context like if you tap a um one of the websites it will do snapshots for the for the archive page it will update the last active date it will do a bunch of other side effects and usually when we make a mistake it would break one thing not all of them but large step end up us having to read them over and over again and try to understand what is going on and this is an antipattern called the giant so definitely don't want to do this so that that that's really what exhaustive testing did and I wrote an article about this and I had a talk with the point free guys like this is really not how you want to do it especially like even here with this simple application you had only two levels of nesting and if you if you tested the sheet individually and then embedded into a a parent reducer and want to test for that you now have to duplicate the sheet implementation in the embedded reducer test as well and in the larger applications you will have multiple levels of reducer nesting and so you won't even have like duplication of the internal implementation once you will have it up to the depth of your tree so if I if I have 10 levels of nesting and I want to have full coverage it means that my child like the lowest think is will be one duplication and then every embedder will have to duplicate again so I I I had a lot of discussions with the team of uh Point free so Brandon and Steven and I finally convinced them to to actually not do exhaustive testing that give us the option to do non-exhaustive version I had an open source version that I um published myself but now it's in a fork and the other problem with that is like as you saw even Shai like when he was writing text what he was looking is at the the information why the test didn't fail what happens in larger projects if with exhaustive testing people will see this failure they will look at the locks and the locks are great like they did very good ergonomics there but they will copy those messages and just paste them into test so that the tests pass so it removes the component of analyzing did I actually break something or is this expected which is another another problem to to have and I originally had this like uh my open source version but now it's a part of TCA because we we work together to merge it off you can easily uh turn off exhaustivity there's actually multiple levels so there's a exhaustive testing off exhausting testing and um there's like a debug thing where it will allow you to have nonexhaustive testing but it will warn you I generally would prefer to turn it off because the warnings look weird and it's very stressful to me um so either Choose exhaustivity Or Not and my recommendation would be the same with with normal programming and normal testing if you're doing a library do exhaustive testing do tddd on the library if you're building a whole app so if you do a larger abstraction then you probably want to do Behavior driven development so I would do like at the leaf level exhaustive non-leaf level turnoff exhaustivity so that you can embed without duplicating the implementation and that means that you will have multiple Focus tests for each side effect instead of something like this this wouldn't even fit like the actual test was couple hundred lines I don't want tests that are couple hundred lines because it's just unmaintainable in my opinion the when we talk about maintainability tests are one thing but the other thing is boundaries across features so TCA does um support composition right like as as sh showed it's pretty easy to embed things the problem is it's not the framework it's it's part of the framework but it's also the problem with the fact that we use actions which are enom and there is no access control for enumerations enumeration is either fully visible or fully invisible what that means in TCA is that if you are embedding something you can you can see all the internal implementation details of those uh features so all the actions that a feature do do the EMB better CES which could be a feature like you can do cool stuff with it like you can can do higher order reducer like analytics at the top level of application and then just implement it once but this actually becomes a big issue in bigger teams because in architectures like UDF architectures to interactional data flow architectures the source of Truth is not actions it's state but what happens is a lot of people are observing those actions as triggers to do other work which is very gimmicky and the default setup doesn't do any kind of nesting in TCA most people write actions like this where you have convention like for example on it something which is a view action and then you have things like login results which would be like a something comes back from the side effect like a shy showed like you do a network call and then you get their login results or schedule the timer which you know you can pretty pretty much tell which actions are view driven or private here but this is just convention and it's very simple feature we had at the browser when I worked at the browser we had action set that were 200 actions trust me it's not clear which are the view actions which are the private actions at that point so that's not really nice so because the state is the source of Truth the lack of access controls causes the the problem that people actually see everything and they can observe everything as we saw you can observe but it's not explicit most people don't really model their data they just show in like oh I'm just gonna add this state and I'm gonna add this state and it's going to be fine and it's fine for very small applications but as you scale your team to 10 20 30 people or your state explodes this becomes a big issue so like you really want to have explicit data modeling and you still like even though the state is source of Truth you still want to have ability to delegate actions right like as we do in Normal architectures you have delegate pattern and you you have a child tell you that something happened that you need to react to it's not always State driven and it would be Overkill to try to model it as a state so we want some some some kind of solution here and for me what I did is the first thing I did is I I established a convention with actual types so I created this protocol called feature action which all the features in TCA would Implement and it categorize actions into three different sets you have VI actions you have delegate which is very explicit and local actions and that is pretty straightforward local is basically like the side effects so any kind of network calls view is what the view layer does and the delegate is what I want the par to be aware of and what this looks like is then I can you know I can do this this kind of implementation I basically use protocol witness feature of uh Swift so now I have explicitly uh grouped things this gives me a bit of power because if I group things together now I can write Swift lint rules I can change the lay the view layer API to be have better ergonomics and if I ever see someone trying to observe something that's not a delegate action in an embedder I know they're looking into internal implementation details as we establish not a thing we should be aware of like as we embed features I don't want to know how it's implemented because then I'm making it possible for me to make mistakes or misuse it definitely not what I want so basically there are things that are allowed and there are things that are disallowed you could simply use convention but because it's structurized you can write Swift lint rules for this and at the view layer you can create conventions that actually hide all the other actions so now when I'm implementing My Views I actually have better intelligence as well so I see less actions I see only actions that I meant to be using so I can never mistaken call something else which is always nice and with swift lint I actually get compile time errors so if if anyone tries to be sneaky and like oh I'm going to look at something other than a delegate because I know there's this action that someone pressed a button I want to do something with it no that just doesn't compile so it make it makes the code base more maintainable which is very straightforward um all of the stuff I'm talking about I actually have articles that go in depth so you can copy the code you can copy the Swift L rols and start using it in your projects the biggest issue and the hardest to solve in TCA other than the boundaries which is a big problem in larger code bases is performance so as you saw in the graph every state mutation in TCA is an action it has to be an action center system and if you looked closely at the way Shai was writing the code when he decid when he declared State he made it equatable but that is not actually the requirement of TCA uh TCA doesn't actually require you to make your state equatable and that has the side effect of every action that that's being sent through the system has to assume that your state has mutated there are no checks there which means that if I'm sending a lot of actions through the system and I'm assuming that every action causes State mutation and I have those those calls like scope IFL all of those functions have to reexecute so for small applications you might have one view store two view stores visible for the browser when you opened the single window you had 100 over 100 view stores and 200 stores because there was uh duplication for that as well so all the projected State all the computer properties all of that has to reexecute so in imperative programming when we um when we had high performance code like tight Loops you would throw an autorities pool you would have to optimize that there were very spares right we didn't actually in most projects we didn't have a lot of places where we had to be very careful in TCA every scoping function is a tight Loop every single scoping function if you add a small sleep in one of the scoping functions your whole application might not even launch because the problem is with the vanilla TCA with single star what happens is let's say you know I have this app and it's working very performance and we do very quick development and we had this small feature somewhere and someone makes a mistake and they do like comp comput expensive U structure navigation like a tree or list or doing some some other computation in one Le feature because of the way TCA is structured this will slow down the whole application because all of those functions have to execute and all of that is single threaded so even a noop action has a massive cost to your project and what I mean by that is I did some U measurements for a noop action so an action being sent through the system doesn't do any state mutation doesn't do anything for the browser before I optimized it it was 6 milliseconds just to send an action not change any UI not do anything simply send an action to the system if you want to achieve 60 frames per second you have 16 milliseconds on normal machines if you have promotion you are screwed because promotion is 120 right so 6 milliseconds for not doing anything it's terrible I optimize it down I think before I left the company I optimized it to 2.5 3 milliseconds depending on the architecture for mobile projects that's better Arc Mobile and some of the other projects that I worked on it's 2.3 milliseconds but I had clients that had performance issues on iOS even so you have to be really careful there there are a couple of easy wins you can apply in your projects without changing any of the architecture for example in TCA a lot of the large objects rarely get mutated but are very heavily um involved in diffing so all the view stor stuff it it does it diffs the state so what you can do is you can use copy and write semantics you could write your own wrapper but that does that makes no sense because the array type in Swift is very heavily optimized and will keep getting better so the the best thing you can do is create this wrapper that just puts the your actual object into an array a single object array and I I did this for feature flags at the browser company and in the first minute of running the browser we had 50,000 diffs and zero mutations so this cut order of magnitude of of CP time for comparing those objects that's very very straightforward thing to do another thing you can do which we have in the the Fark of DCA in browser is we added the ability to cut off the tree updates using diffing at a star level so even though the vanilla TCA doesn't have requirement of equality you can easily extend it and we apply this on couple of places so at the window level just cutting at the window level we optimized 30% of CPU time I applied it across other objects as well and it added another 30% gain so very very useful very simple code to do and you can use it's available publicly as well but the real gains and not just in performance but in maintainability of the code bases if we go back from the vanilla TCA from Redux like Redux is a an architecture that came out from flux flux was a multistore architecture that uh was simplified into a single star solution so if we go back it's actually much better and so one of the features that the browser had was native nodes so I did a simple test with the vanilla TCA single store architecture versus using multi store so well creating separate stars with and separate State pieces and the test was simply typing five lines of text into uh text view very simple code so the the time it took that was before the optimizations I did but the time it took to process those five lines of text in TCA was 9 seconds of CPU time with multistore solution was 0.9 seconds so still slow but much faster and the thing is before this the only way we could actually have notes working was to add throttling at the view layer which goes against the data consistency and we go to back to the problem where the actual data versus what the view display is not consistent so this is a massive difference to have between multi store and U single store and other than performance I showed you how you can do conventions with single single store vanilla TCA with lint all this stuff if you do multi star you can actually make your features fully black booxed input and outputs your feature only exposes interface for configuring it and then getting a delegate back and you can still Implement those features with TCA right with multi star you can have a separate store for each feature and you connect it through normal dependency injection so this gives you like 100% safety like no one is going to look at your implementation details because they simply won't be able to because those all those symbols will be private and this also lowers your exposure to TCA so if if you scale the project and project grows and you realize well TCA is really not where we want to go like it it sounded great when we were smaller but as we grew it became a problem by having this black boxes you can keep some of the features in TCA but change your underlying architecture of your project or do it iteratively much safer much easier better for business in my opinion and you can also Implement some of the features completely differently some features just don't work in TCA because of the performance implications like any kind of high frequency data processing TCA would be terrible for especially single store like if you had like let's say you have some um sensors that are sending like 60 frames per second or even higher data uh through the system if you were to do that through actions and you have large application you will be paying costs everywhere for something that is very isolated so don't do that uh highly discouraged the biggest con of TCA you cannot of multistar TCA you cannot do higher order reducer like you cannot do an analytics reducer at the top level of the application because simply you don't see the actions other than delegate I don't think it's a big con I think it's it's it's really not that big deal and the other thing is force forces the engineers to think about that data modeling which again I don't think it's a con I think it's something that we should be doing um it's a little too to easy not to do it in TCA and in other architectures as well but like we as Engineers we try to keep things simple but when it comes to data modeling especially in architectures like this is really important so I would say it's not a big con it integration is pretty straightforward especially with the new reducer protocol and dependency system uh I have an article that goes over how to do it um on change there's like a higher order reducer called on change which lets you observe state in TCA it's not vanilla TCA but it's very commonly used in the community it's very inefficient it added 40% of CPU time for Intel machines for the browser much easier to flatten it like the the cost of reducer protocol and although existential comparison and all this stuff is very expensive so if you see performance issues in TCA try to see if you can flatten it to a simple if statements like a bunch of if statements so much faster and there I also experimented with more aggressive changes to TCA like creating explicit dependency trees so a lot of the architectures from the uh web Technologies like relay JS and even Redux has this system called selectors where you specify what your dependencies are to the feature and only if those dependencies change you actually reexecute those copying functions all this stuff TCA doesn't do do that as I explained before but I actually had a fork where I did it explicit it's I would say at this point you're no longer using TCA so i' is it worth it I think like the adct complexity kills the the benefits I think there are other architecture approaches you could do so I'm like I have an experiment if someone is interested I can I can show it in the future but generally I would say probably not one to do it memorization also not something that I found useful I have an article where I memoize state so that I basically created like a exter Al cach to TCA and that works but the fragility of this kind of solutions I generally think that an architecture has to work you have to it has to be reliable so this kind of experimental stuff is interesting just from like an engineering perspective but if you cannot fully automate it that it's going to be reliable every time you could have cash misses stuff like that don't do it uh highly discouraged because finding those bugs is is really hard um one of the big benefits of TCA and how I actually as I developed this stuff for clients I had to build a lot of tools because the the the the biggest promise of TCA of like even unidirectional data flow is and that that's a really big sell to Engineers like you will be able to understand what's going on in your application right that's the biggest promise like I really want to know what is happening unfortunately that is actually not the case in larger codebases because once your state starts growing very heavily you have so many side effects there's so many things happening so many nestings of reducers that it's really hard to reason what's going on in the application so to be able to do that I buil a bunch of tools that let me track this stuff and with tools you can do it but with like the vanilla implementation it's really hard to follow so one of the tools that I built is uh performance dashboard so it tracks the cost of different phases of TCA so the the amount it takes to scope those uh substates the quality cost the propagation of actions so on the left you can see like all the individual costs attributed to specific stores so I can find which stores are expensive on the right I built a store tree so I can see how my application is structured this is really important for bigger applications like um in iOS usually have one active screen so it's pretty straightforward to understand how it how it happens but on Mac apps you have multiple window Windows you have very u a lot of nested components and so being able to see the tree of how the actual state is being uh configured is very useful the the nice thing about TCA because if you have consistent architecture building tooling is straightforward so building those tools was not hard the other tool that I built is uh something that Shai showed was the print changes so I built a visual version of this where you can actually filter actions because in larger applic you will have a lot of actions that are noise generators like for Mac apps for example you can have hover as an action you can have the the window did activate all those actions that's a lot of noise you don't really want to look at that most of the time so I build this spy dashboard I'm going to write an OP Source version of this now that I'm not no longer uh bound to a company so I'm going to make an iOS and Mac crossplatform solution for this so everyone will be able to use it um you can watch my blog subscribe to my blog you'll get access to it um this is very convenient this helped us find a lot of weird things the green actions here are noop actions are actions that been sent to the system but didn't actually mutate State and if you select one of the red ones it will actually show you the same div that uh Shai showed you with the print changes the nice thing about this is this costs nothing so you can have it in production application and you can have a hidden way to show it and then it's just an if statement and it shows this window and you can if you have a client that says that the app is misbehaving and it's very slow you could send them a URL that like a dipl link URL that activates this this window and you could see the what kind of actions are going through the system so you can find like timers that are running off wild stuff like that and the other thing is I one of the thing I built is I buil a custom lifetime tracker for the browser and I connected TCA through it so you can see here how many view stores and stores are alive when we just launched the application and and what what's really nice about this other than being able to just know how many there are is because if you have this consistent architecture that those objects exist everywhere you don't actually have to integrate lifetime tracker in many places you can just simply add it at the architectural level and now I close the window and if those things are still alive I have leaks so it immediately tells me oh I'm actually leaking memory all over the place so it's really powerful to be able to build tools with consistent architecture and uh it's really convenient to um well it's really convenient but it's also necessary to have those kind of tooling because it's really hard to reason in larger code bases what's going on and in summary like I think that the composable architecture framework itself is really well written very well done I don't think it scales very well for larger applications so be careful there but if you if you like it I would highly recommend using multi store I'm not the only person using that like the ramp company that does the credit cards they also do multi store TCA from the beginning and they're very happy with it because it has very clear boundaries better performance Less business risk so I highly recommend uh if you do TCA consider multi star it's a lot easier to start with this than to refactor existing application into using multi store because refactoring requires you to remodel your whole data which is a pain in the ass so start fresh you know be be aware of those problems I'm available for Consulting if anyone needs help with either TCA or any kind of architectures I often get hired to audit the whole code bases and find uh practical solutions to improve development uh for the whole team and you can follow me on Twitter or GitHub I write at bwing info I write a lot of Articles and thank you