um hi everyone welcome to uh your life with and without observation uh hopefully you get the reference if not go fill uh those gaps it's from the movie by the way um so let's start with some history about the observation framework so first of all observation uh the first draft appears on the Swift repo on February and then the first pitch um on the Swift Evolution Forums on March first review in April only then Apple announced it in wwc but then it was accepted for second review only in August which is pretty recent um it's actually part of the Swift SD LE stand Library um which means it's open source which is actually great for us it means we can poke in the code which we're going to do in a few minutes it's implemented for Swift 5.9 as you probably assumed and the main goal is to replace um observable object and then potentially kvo at least based on uh the pitch that they had in the proposal so first of all why do we even need a new framework you know what we had observable object in Combined why was so bad with that um so the biggest issue with with observable object is that changes are at the object level which means views know that some object changed but they have no idea what actually changed in the object it depends on combine which means Darwin only which means Darwin is is Apple which means it cannot go on Windows Linux all that um it is a very easy to cause performance issues um in Swift UI with observable object um if using patterns like Crux and similar patterns um and you're not careful it's very easy to kind of like tank your performance it requires default value so you cannot do optional it requires a single instance so you cannot have an array of observable objects and you cannot have any other collection of observable object in your view and it breaks with nested observable object so you cannot have an observable object inside another observable object that also breaks so just to kind of like as a quick reminder of the syntax um the the observation is opt in so you have to say this is published that is published and I care about both the model and the year of the car and then later we see that we have some view with some car and the body is only using car do model that's the only thing we present on the screen but later when we change the year that view is still refreshes and that's because observable object doesn't know what changed in the car it only know oh car changed I I need to create the the the body it doesn't even matter that I don't it doesn't even know that it doesn't use use the year of the car so just to summarize it's using combine like I said it's using property rppers the ad published um it's optin so you have to declare for each one uh you cannot tell which properties changed um and then any object change uh refreshes all the dependent uh views and no optional collectioned uh blah blah blah all the things I said but it's iOS 13 plus which is great because we can actually use it observation uh observable on the other hand because it's part of STD it means it's usable anywhere Swift is usable it's used in macros the new hotness uh that we heard about yesterday it's opt out so everything is observed by default and you have to opt out to not observe something it does know what changed and so only if the view is actually using a property that changed only then that view will be refreshed and it supports all of those nice things like optional collection nested you can just do all all of that and it just works but for Swift UI it's iOS 17 plus so how does it look we have a new syntax first of all um we have this macro instead of uh protocol we don't need to do anything we can just do VAR model VAR year because everything is observed by default and then the state object is simplified to Simply state that causes new Behavior otherwise it will just be you know some sugar and not very interesting so the main Behavior change is that in this case when we change car. year the view is not refreshed because observation knows that we only care about card do model we do not care about the year so it's just not going to diff The View and it's not going to refresh the view so the big question is how does it no and it kind of looks like magic right it's like some magical thing and you might think oh maybe it's some private API stuff maybe it's some crazy reflection you know mirror stuffff maybe it's Pars in the body of the viewed compile time and creating something on the side it's actually none of those things um and we're going to like I said before now we're going to poke around into the into the code base of observation and see exactly how it works so how observation works so like I said we have new syntax that is creating this new Behavior so we're going to uh look under the hood of this new syntax and see how that new syntax helps us create this new Behavior so we have this um macro at observable and the nice thing with uh xcode 15 is that we can do right click on the macro and ask xcode to expand that macro and then we get that this is a lot of code um but trust me not not much is actually happening here and I'm going to walk you through it uh or at least the the part you should care about uh and it's not actually a lot so first of all we have another macro suddenly which is called Observation tract and then we have this underscore version of the value so all we did is we said we have this VAR value that is some string suddenly we have underscore value that was created for us that is also um initialized to the same value uh to string of one and it's observation ignored which is another macro and then we have this new observation registar whatever that means we're going to see in a second uh this object as you can see it's not static it's you know whenever we create this model then we have another value of this OB observation regist and then we have two functions one is access which all it does is it's saying observation register. access that's it and it provides self the model and the key path um what the property we accessed and then we have with mutation which is kind of like the setter version equivalent of access um this one provides self keyth and this mutation closure which we're going to see in a second so you can see we have these two new macros uh so observation ignored there's nothing to expand here it's an empty macro uh it's just meant to uh Mark um for observation to the compiler to not you know not observe this property observation truck however um has more stuff that we can expand even further and that goes outside of the slide so imagine you still have all of this but then you also have that um and so when we expand observation track we suddenly see this very interesting thing so suddenly this value that we created and we thought it's just data just string this now became a computed property it's not data anymore and so now we see oh okay so the underscore value that's now the data and this is just a computed property that access the underscore value but before it access that value it calls access which now we know all it does is just calling the regist and it calls in the setter it calls with mutation which also we know it's just call in the regist but the interesting thing that you can see here it doesn't change the value it actually creates a closure to change the value and it gives that to the registar so this is what we wrote we created some you know model with couple of properties one two three um very small and very simple and we added this this add observable macro and this is what we got from uh from the observable framework uh we got this getter and the setter and both of them are calling the regist and both of them um are calling these functions on the register access and with mutation so later when we run the app we have some View and that view has some body and the body of that view is accessing you know that value in the model um and that is going to call the getter that is going to call regist and that is going to call Access so whenever we call a body of the view uh it's essentially calling exess on the registra for any observable model that we have then later somewhere something happened we got some new data from the server whatever um and we called some fun function that is changing that value and so that is calling the setter which is calling with mutation on the regist so let's start with the access so access um what it does um there is a lot of pointer related uh code here to you know for memory safety and all that you can ignore that we're not going to deal with that for now uh it doesn't really matter but what actually matters here is that we have this underscore thread local. value and that thread local. value is a static value that is local to the thread and the type of that value is observation tracking exess list and to that access list we call add access and we give again the P the key path that we got and context context is essentially just everything the regist basically the entire state of the registar everything the registar has it's almost like passing the registar itself as a reference basically so that uh thread local. value it basic basically stores all the registrations so whenever some register uh calls add access it is basically getting into the same value the same static value and that is because thread local. is a TLS thread local storage which means it's essentially Global value for the thread so it's almost like a single tone but for for this one specific thread that we're running on currently which is probably the main thread in the case of Swift 2i and the type is like I said observation tracking doore access list so when we call add access in Access list so access list itself has this dictionary of entries which are basically identifier to an entry this entry you can see inside Ed access it's basically it's just a um a value that contains that context that holds on to that context we got from the registar which again is just the state of the registar and then we add the key path and So eventually we're going to have this dictionary that has all the key paths from all the registrars with the context about those registrars so we already learned about two two main players that we have in this framework the registar and and the underscore uh thread local data value the registar again is one per observable object instance and it it's kind of just like a proxy to register all the getting set calls and then the excess list is one per thread and and this is the list the main place that keeps all the registrations then we have observation tracking which is the third player that we're going to look at right now so observation tracking that's the the main entry point into the framework and it has mainly that's half true but let's imagine that it has mainly one public Global uh function which is called with with observation tracking on change and it takes two things it takes an apply closure and an onchange closure the apply closure this is where we register anything we care about right so in this case we are iterating some cars and for each car we want to print a name the fact that we call. name calls the getter registers that we care about the name and then when the name changes the on change is what's called and then we print you know do whatever and so you can kind of like imagine what maybe happens in swiftui this part is not open source so we cannot view but it's probably something similar to what you see here there's probably some kind of render function uh that calls with observation tracking accesses access the body inside the apply closure and then eventually when something changes all we need is we just need to do a recursion and render ourselves again right and get the body again do the diff in whatever we need to do and refers The View so for example we have some view we have this carard model and we use that model inside the body of that view and then later we change the model to p f f08 however you pronounce it the the interesting thing here is that if we pass the object itself the car itself to this to to the sub view now we did not access any property of the car in in this view this means that this body is not observing car even though we are using the car object we have it a state we pass it to sub view but we will not be refreshed when any of the properties in car change because we do not use any of the properties inside car so we can pass the view um kind of like a proxy to the sub view subview is probably using car do model somewhere and so subview is going to register to refresh whenever that model changes so with observation tracking uh that entry point I mentioned uh it's very simple function actually it doesn't have a lot it has two things that it does generate access list and install tracking generate access list mostly what it does it just calls apply and then it records in accessed uh any accessed and registered observed properties but if you think about it for a second those just happen for us right the we just call a getter that calls the registar that calls the access list and we're pretty much done and so this is everything that happens inside generate access list we literally just call apply and then we just grab thread local. value and we have everything we need because when we call the ply it called somebody it called the registrars um and start everything there's a lot of thread safety and po manipulation code here that I completely deleted um so if you actually open the the Repro and you look at it you'll be like oh what's going on it's a huge function it's all boiler plate uh this is the actual logic of what's actually happening there and then we return the uh the access list that we got what we do with that access list we call install tracking and install tracking is another function that takes that that list of things we accessed and things we care about and the onchange closure that's what we got from from the color this is what we need to do when something change so inside install tracking we are iterating that list and we connect each each access each uh you know a key path uh that was accessed in any of the observed objects to this unchange closure so again there's a lot of pipeline code here that I'm skipping um but this is kind of like a simply ification of what that function does and basically all it does is it just iterates the entries inside the excess list and for each entry it's calling register tracking and remember the entry has the context which is the registar basically passing the properties those are that's a list of each key path that was accessed for that entry for that um object and the onchange closure so just a reminder we have this access list it's holding this entri dictionary and those entries are all the properties um combin all the properties connected to the to the context of the regist inside register tracking itself this is reaching inside the regist itself through the entry we we getting the these properties and the onchange closure and then we create this observation object um I actually now don't remember if it's a valure class so I'm not sure it's an object but we're creating this observation type that holds on to the unchange closure this is what we need to call eventually and all the properties that we care about and then the regist itself keeps uh retains the memory of those um observation values and this is where we finally connect between you know we have the registered properties the things we care about the things that when they change we want to call something and we have we have this unchange closure on the other hand think I'm doing it upside yeah we have the unchange closure on this side that we want to call eventually and then finally the registar has the connection between them and that's basically it because now the registar can just take care of it now the register knows knows what we care about it knows what to do when something changed and it can just call the unchange closure and that's basically it that's pretty much how observation Works um that's there isn't much there is much more uh to it let's let's uh use an example to kind of summarize what we just saw so we call with with observation tracking we provide two closures apply and on change we call apply when we call apply things get called you know some bodies and some views so we have some user some car and they have some properties and we call the getter and the setter uh sorry only the getter at this point and those are calling the regist those registrars are all given what they have to the access list and then we just get the list and then we pass the on change to those regist chars that we have access to them through the access list and then at some point the user had a birthday so we need to change the age we're calling set on the age of the user that set again calls the regist now the regist has this observations array I'm sorry dictionary and it just looks for the key path of age on the user and it it checks oh am I observing age looks like I'm observing age calling on change and cancels the observation so now that we know at least in a high level how observation Works um honestly it's not that much high level it's basically this this is pretty much it um there are few things we need to keep in mind when we are using observations so if you watch uh the wwc tutorials on how to how to use observation this is kind of kind of how they present that uh you know this amazing beautiful world uh everything is perfect there's no problems in reality at least uh in the next I don't know couple years three years until we can actually use only observation and forget observable over object ever existed uh this is what we have for now uh this is kind of like our reality currently and even if we don't have you know some of us might have observable object and uh sorry observed object and observable in the same project and then it's probably going to be very confusing even if not at least mentally we will have to you know maybe you're jumping between projects that one migrated and one didn't you know one is IOS 17 plus one is IOS 16 plus um so either mentally actually in the codebase we will have to do this um understanding between how things work with and without so the first and maybe the most confusing thing is property rppers if you have a view bless you with the state um and if you're using observable like I said you can just put at state right but if you're using observable object you have to use State object you cannot just put at State because Ed state is only for Value types before observable The Annoying Thing here is that xcode is not even going to say anything xcode will be very happy build succeeded no warning given but the object will just not be retained because only if you have state object for observable object uh the object the object is retained and only then you will see any changes so you have to use State object so if you're using observable using state if you're using observable object using State object the other thing is the observed object uh property rer which usually we had to add that to some child view that is receiving um receiving a model for a parent view now I'm seeing the slide is actually wrong should not be equals model it should be you know receiving a model type from a parent view and so with observable I can just do let I get to I get the model from somewhere from a parent view um and it's observed everything is fine with observable object if you don't remember to use observed object then it will just not be refreshed and then you'll be very confused what's going on um so again if you using observable you can just use Simple let if you're using observable object you have to use observ object and here I can see that the slides are actually correct in the code um so this is kind of like a summary I don't know if you for those who like to take pictures of um useful slides uh feel free to take this is a good snap um slide basically showing what I just explained um Again State object versus State and observed object versus simplet the other thing is State versus identity so this is also true with observed object uh it's not a new thing but it can be maybe extra confusing with um with observable and the reason it can be extra confusing is because we think that you know observable is so great it solved all of our problems we don't need to worry if I didn't use the if I didn't use some property then I don't even care that I have that model in my view it's not going to refresh sort of so with uh with the observable object before uh observation we had this state object and let's say that we have this root view that creates a Branch View that creates a leaf View and we don't want the Branch View to care or know about the model we want to basically inject the this model that we have all the way to the leaf View and keep everything in the middle decoupled right so we used environment object that was great and that way Branch doesn't even need to know that model even exists now we might be able to to say oh I don't need that stuff I can just pass the model to the branch and then just pass the model to the leaf and branch is not using model so everything is fine branch is not going to be refreshed everything is like I expected but here's the catch if you change a property inside model yes that is true because the state changed and branch is not using any of those properties but if you change the model itself then you change the IDE identity of the view so if you look at the root here in the body we're not changing model do something we're not getting to any Getters inside models that's called in the registar instead we have an entirely new model that's a new class that's a new address in memory and that changes Branch's identity and so now it's not that the dependencies of Branch change it's that Branch itself as a view changed and in that case yes Branch will be refreshed in the case that you see here and then both refreshed in this case so you might you might say okay this is interesting but I don't see how this actually applies to anything in my life that I care about so here is a real world example uh that kind of like makes it very easy to see so imagine we have a list with some data and we access some value inside this data right and this is it's very simple um each row in the list is just a text with a label and some value at some point we want to update the the data and so let's say that we obviously we want animation because we want the app to look great and be animated and so the coder is just a weird weird way to basically replace between the first and the second value right we take um we grab the the value index zero we give one to zero and then we give the temporary to one one so we basically replaced the values inside the model between 0 and one so that means that if we had if the values were also these numbers like 0 1 2 three you would see that the top row suddenly the number changes and the second row the number also changes and that's exactly what you can see um the animation is a little bit fast so but I'm going to spoil it to you one and two are going to be replaced but because we didn't change the identity here we only change the dependencies each row in the list essentially says oh I can handle this update myself I don't need the list to handle this animation for me and so they don't know about each other so one just Fades into two and two just Fades into one so look at that for a second there we go hope you didn't miss it um and so maybe this is what we want maybe not but this is what happens when the dependencies change but not the identity of the view what if we change the model itself so we actually take the object that we have at data at index zero we give it to uh sorry we take yeah we take index one give it to index zero we Tak we take index zero we give it to index one now we change the objects themselves now the text view actually changes the identity because the the object changed and not just dependencies inside that object this actually changes the animation so now the object is saying I changed as a view I don't know how to animate myself I don't know how to animate my changes I need my parent which is the list to take care of that and then the list animates those changes and now you're going to see animation that maybe you were expecting which is the two rows replacing themselves there you go so this is kind of like a interesting example of where you can just visually very clearly see what happens when the dependencies of your view change but it's the same view view versus when the identity of the view change and it's just a different View and then Swift UI has to basically take away the old View and put a new view uh instead and it's it's very easy to get confused with observation so that's that the next thing is escaping content closure so there is this design pattern in Swift UI whenever you are creating container objects right you are taking this content closure some generic type and you are taking it in the initializer and usually the best practice is to open that closure inside in it and not hold the closure but instead to hold the content itself after you you already opened it but let's think about it for a second with observation this initializer is going to get called in some in a body of some view right some some parent view is going to create the sub View and create and call the initializer because they create that view inside their body so we are now inside the body of some other parent view so this means that whatever happens inside content in whatever property is getting called inside this closure even though only we the subview care about it the parent view is the one that's going to register it and observe it so if we have this content view we pass content and it's some text that access model. Val model. value is registered to content View not to sub view because this is when the closure is called an observation doesn't care you know where was it in the code all it cares is that it was called as part of the same apply closure as part of the same body and they um and then later we change model. value um from whatever it was first to some other string and you can see here content view is only using other value and if you remember the example I showed all the way at the beginning this looks like that example where I could say oh this is great value changes but we don't care about value cuz we're not using it so we're not going to be refreshed but yes we are going to be refreshed because that closure we passed to subview was getting C inside our body and the interesting thing is because we already opened the closure maybe we don't even show that content so here we pass show it some Boolean flag we pass false we're not even using that content in the sub view that doesn't matter the the the content view is still going to be refreshed and then also the sub view is going to be refreshed because of that even though we're just not presenting this this content anywhere so instead if you drop that customize uh initializer and you just keep the content as a closure in this case it's not going to be called and it's not going to be refreshed unnecessarily so first of all content view never refreshes in this case um because like I said we don't use model. value we only use model do other value and then even sub view will only observe and be refreshed when show it is actually true so as long as show it is false we don't even use that content we don't even open that closure and then it doesn't even matter what happens to model. Value it can change a thousand times it doesn't matter that view will never be refreshed only when show it is true now we care about the content now we are going to observe model do value um you might be asking okay so should I stop opening closures everywhere in my app um I'm not saying that um I think it's a case by casee situation and so I don't know if there is you know suddenly a new best practice um versus what we have until now but I think this is just something to keep in mind um and it's very interesting way to to see the difference between um what we had before and the behavior we have with observation and how the the fact that observation doesn't care which view using which which property all it cares is that when you call somebody of some view anything that got called inside that body for whatever reason is going to be observed for that body then lastly uh there are some things that are not supported with observation some of them are by Design some of them will maybe be supported later some of them are just bugs that we still waiting on uh the team at Apple to to improve but as of today um so first of all you cannot do lazy properties if you have lazy V you're just going to get an error cannot be used on a computed property because if you remember this value now is not not data anymore it's a computed property and the computed property cannot have lazy property rppers if you have some custom property rppers exactly the same scenario you will have exactly the same error uh cannot be applied to Compu computered property and finally no value types um observation as a decision as of the second review that passed in August um just simply does not support value types it's only classes only reference types so a few takeaways um I think it's really useful to learn how observation works because um it looks at the beginning like you know just a simplified version of what we had before and you know nicer syntax but actually there there is a lot of confusion and a lot of changes in the logic that I think uh is important to know and I think once you do know that and you have that in mind it really helps to build solid apps or if they're not solid then at least easier to debug issues when you do have them um and then like I said it's not just new syntax there's a lot of behavior changes and it's important to keep them in mind and then know your state or know the syntax you need to be using for each framework because xcode unfortunately is not going to help you um and finally it is very powerful for workk I I think it's a great a great framework and I think it has a lot of um potential and benefit but it's not a silver bullet that's it um I'd start with um a question on uh that applies to both things uh which is about uh debugging so how do you debug those uh those stuff uh both observables and they see streams is it yeah apparently I can't debug a microphone uh a async streams it's it's pretty standard you're you're following the flow through it's just the the the async side of it um and apple introduced some nice testing macros so that you can test some async things too so that's that's been helpful um well with observation um I think xod first of all did an amazing job with the fact that you can just expand the macro and something I didn't show is that once you expand the macro you can actually just copy the entire codebase the entire snippet of that expanded macro and paste it as is and then drop the at observable um declaration and you get exactly the same behavior you don't lose anything everything walks exactly the same and then it's your code and then you can just debug it and and you can see what's going on and so you can see uh um exactly how it's how it runs and you can add you know like print statements or whatever you need and add break points you can also set a breakpoint inside the expanded macro even better and then you can just print from there even better so what I'm getting from both subjects is like should we expect at some point uh combine to be dropped completely or is it some still something that we're going to have for a long time before it can be completely replaced from s dry and everything else so people get upset when I say this but it's it's the line from castleblanca that combines going away not today but soon and for the rest of your [Laughter] life and uh one of one other things that hit me when you talked about um about observation and I was like uh okay there's so many thing happening uh away from uh what I'm doing myself because there are some micros Etc should we be uh really worried about uh some memory issues uh should we think about oh there's going to be so much trouble with some be Etc I am I am not aware of any memory issues um with using observable uh you mean the access list right yeah I'm not aware of any any memory issues um I can only assume that Apple rent tests um but who knows um I do see that they they occasionally add more and more you know in the last three months I've been tracking that that that part of the Swift T Pro and every time I see a new PR with a new um a new test to to check a new Edge case they found like for example they just a month ago I think they they realized that nested if you call with observation track in and then inside you call with observation track in again it was broken for some in some way and so they fixed it and then added the test to that so so they keep they keep finding those edge cases and they keep um fixing them and and adding uh test to them but I'm not aware of I I haven't seen anything about you know anybody's complaining or seen anything one of the things I love about observable is if you have a a class that you make observable and you're observing it in a view way here you can put controllers in between even structs and computed properties since this is registered to say I want to know when when anything changes it like reaches through and says I'm I'm tracking number all the way here this is a computed property that uses number then it gets those updates too there's a lot of just really nice magic because the first thing people said who came from combin was well how do I republish something I I consume it and then republish it you don't have to computed properties makes that very easy exactly and that's actually why um nested nested observable object don't work because they are very explicit they call object wheel change right and then that doesn't call the outer uh that doesn't make the outer observable object to also call object whe change to notify the view oldi in this case like I say nobody cares it just doesn't matter a property gets accessed it gets registered you're done um a question that um it's more about architecture than about the implementation itself um what's your advice on the on the granularity of using observability oring streams in your swiftui views so should we um like have them observability basically in sub views even nested sub views or right or not um well um yeah I think it's a really good question because I already showed you know a couple of those caveats and I think there were some things that it took maybe a year or two but eventually we realized okay this is how we should do this in Swift UI this how we should do that swiftui for example like the Redux pattern um you know Apple doesn't say it explicitly but they sort of say it implicitly like you probably should not do Redux style uh which is like a huge context with everything because because of how observable I'm sorry because of how observ object is working and then observable came and kind of like threw all of that out the window but it has other caveats and I think we might see some changes in patterns or in best practices in terms of performance in terms of architecture in terms of how to how to organize your code and how to organize your views with for observable that might be actually very different from how how we thought it's the best thing to do for observable object observ the object I think if you take a parent view and break it into subview that use just a piece of the model that you want observable is very efficient it's it's the the state versus identity thing and so the more you can break these out I think the more power you're getting and then if you need to reach back and change something you need the at bindable to do that um no questions all right uh see uh more question from the audience uh there's all specific about um uh observables well I had a personal questions uh personal to you but to what what's going on in the uh in this field so basically uh we we've been leaving uh until very recently with no um acing uh in in Swift and then all of a sudden we go combine um acing streams observable uh so uh as a developer I would say what's going on so it's uh is Apple like getting a cing is road map or something like this I'd say more that we have been using async for a long time but now we're being more careful about it you can't say that kvo and notifications we've been using async since the beginning but now we're finding out that we made a lot of bad mistakes and bad decisions and we weren't careful async is forcing us to really be careful with things uh so I recently went through codebase for one of my apps and you notice I'm very careful instead of importing Foundation or Swift UI I import combin so I could find every use of it and replace it with observable and I'm delighted the code has gotten smaller easier and I really was a fan of combin so this is not a combined hater I remember one of you talks on Eric Swift back in the day so yeah guess so all right um so that that's all so thank you very much indeed