How Can I Match Swift 4 KVO on Non-Objective-C Type? - swift4

I have a Result type that I use in asynchronous processes:
internal enum Result<T> {
case success(T)
case failure(Error)
}
I also have a APIDataResultContext that I use to pass data between Operation subclasses:
internal final class APIDataResultContext: NSObject {
// MARK: Properties
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
}
}
}
In my unit tests, I need to determine when result has been set in an APIDataResultContext instance. I can't use KVO because my Result<T> type cannot be marked as dynamic since it can't be represented in Objective-C.
I don't know of another way that will allow me to monitor when result is changed other than using a closure property or a Notification, which I would prefer not to do. I will resort to one of the two if necessary, though.
What other way(s) can I monitor for a change of result?

I ended up adding a closure property to APIDataResultContext:
internal final class APIDataResultContext {
// MARK: Properties
internal var resultChanged: (()->())?
private let lock = NSLock()
private var _result: Result<Data>!
internal var result: Result<Data>! {
get {
lock.lock()
let temp = _result
lock.unlock()
return temp
}
set {
lock.lock()
_result = newValue
lock.unlock()
resultChanged?()
}
}
}
I use the closure in my tests to determine when result has been changed:
internal func testNeoWsFeedOperationWithDatesPassesDataToResultContext() {
let operationExpectation = expectation(description: #function)
let testData = DataUtility().data(from: "Hello, world!")
let mockSession = MockURLSession()
let testContext = APIDataResultContext()
testContext.resultChanged = {
operationExpectation.fulfill()
guard let result = testContext.result else {
XCTFail("Expected result")
return
}
switch result {
case .failure(_):
XCTFail("Expected data")
case .success(let data):
XCTAssertEqual(data, testData, "Expected '\(testData)'")
}
}
NeoWsFeedOperation(context: testContext, sessionType: mockSession, apiKey: testAPIKey, startDate: testDate, endDate: testDate).start()
mockSession.completionHandler?(testData, nil, nil)
wait(for: [operationExpectation], timeout: 2)
}

You've already solved this issue (and what you did is probably what I'd do), but there's probably still value in providing a literal answer for the title question: How can you use KVO on a non-Objective-C type?
As it turns out, it's not too difficult to do, although it is somewhat ugly. Basically, you need to create an Objective-C property that is typed Any with the same Objective-C name as the Swift name of the real property. Then, you put willSet and didSet handlers on the real property that call the appropriate KVO methods for the Objective-C property. So, something like:
#objc(result) private var _resultKVO: Any { return self.result }
internal var result: Result<Data>! {
willSet { self.willChangeValue(for: \._resultKVO) }
didSet { self.didChangeValue(for: \._resultKVO) }
}
(For the sake of simplicity, I'm assuming that result is your stored property, and removing the lock and the private property from the equation)
The caveat is that you will have to use _resultKVO instead of result when constructing key paths to observe, which means that if this needs to be observable from outside the object, you can't make _resultKVO private, and you'll have to clutter up your class's interface with it. But so it goes.
Again, I probably wouldn't do this for your particular use case (and if you did, you could obviously fire the notifications in result's set rather than having to bother with willSet and didSet), but in some cases this can be useful, and it's good to have an answer describing how to do it as a reference.

Related

Can I set computed property in get block in Swift?

var myProperty: PropertyType {
get {
if let alreadyComupted = savedValue {
return alreadyComputed
}
return computeAndSave(someParam: "Hello")
}
set {
// is it possible to move *computeAndSave* body here somehow so I can call *set* method in above get definition?
}
}
private var savedValue: PropertyType?
private func computeAndSave(someParam: SomeType) -> PropertyType {
// perform computations and assign value to *savedValue*
}
I am fairly new to swift language, not sure if this is even standard by coding practice or not.
Basically you are describing a lazy variable. It calculates its initializer once, when the value is first fetched, and from then on uses the stored value (unless it is replaced). You can combine this with a define-and-call initializer:
lazy var myProperty: PropertyType = {
let p = // perform some expensive one-time calculation here
return p
}()
The outcome is that the first time you ask for the value of myProperty, the initializer method runs; but after that the previous result is used, and the initializer method never runs again.

Converting Older KVO to Swift 4

I'm trying to convert some old WWDC swift code to Swift 4. I think that I have everything done, except for this last bit that does some KVO. This has been pretty difficult to narrow it down to this last bit because everything appears to function like the example code - but these KVO methods do not get called in Swift 4. I found that out here: Open Radar Bug
What would be the Swift 4 way to represent the following?
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
return ["state" as NSObject]
}
And here are the variable definitions from the example:
override var isReady: Bool {
switch state {
case .initialized:
// If the operation has been cancelled, "isReady" should return true
return isCancelled
case .pending:
// If the operation has been cancelled, "isReady" should return true
guard !isCancelled else {
return true
}
// If super isReady, conditions can be evaluated
if super.isReady {
evaluateConditions()
}
// Until conditions have been evaluated, "isReady" returns false
return false
case .ready:
return super.isReady || isCancelled
default:
return false
}
}
override var isExecuting: Bool {
return state == .executing
}
override var isFinished: Bool {
return state == .finished
}
If more code is needed, please let me know.
If this is a duplicate question, please link to the duplicate here. I've been unable to find a solution.
The keyPathsForValuesAffecting… members can be properties instead of methods.
They must be declared #objc because the KVO system accesses the properties using the Objective-C runtime.
The properties should have type Set<String>.
If you use the #keyPath directive, the compiler can tell you when you've used an invalid key path (for example because of a spelling error or a change to the property name).
Thus:
#objc class var keyPathsForValuesAffectingIsReady: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsExecuting: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsFinished: Set<String> {
return [#keyPath(state)]
}
You also need to make sure your state property is declared #objc dynamic.
The main problem is that KVO is built using Objective-C, and it uses the Objective-C runtime to detect the existence of the keyPathsForValuesAffecting methods. In Swift 4, methods are no longer exposed to Objective-C by default if you don't include an #objc annotation on them. So, in a nutshell, adding the #objc annotation will probably fix your problem.
Another thing that I do—not strictly necessary, but it makes the code look a bit nicer—is to declare these as static constants. The #objc will cause these to get exposed to Objective-C as class methods, so it all works, and it's slightly cleaner. I like to put private on them, too, since these will never get called by Swift code, and there's no point cluttering your class's internal and/or public interface.
You also need to make sure that your state property is KVO-compliant, and sending the notifications when it is changed. You can either do this by making the property dynamic, which will cause the KVO system to automatically generate the notification calls for you, or you can manually call willChangeValue(for:) and didChangeValue(for:) (or the string-based versions, willChangeValue(forKey:) and didChangeValue(forKey:)) in your willSet and didSet handlers for the property.
Finally, don't use raw string key paths in Swift if you can avoid it. The #keyPath() mechanism is the preferred way to get string-based key paths (and for uses other than these legacy Objective-C methods that need to take strings, you should use the new KeyPath type which is better still). If your state property is not an Objective-C-compatible type, though, you're stuck with the old string key paths (in which case you'll fire the notifications in your willSet and didSet as described in the previous paragraph). Alternatively, you can create a dummy Any-typed object that mirrors your state property, purely for KVO purposes.
So, something like this:
#objc private static let keyPathsForValuesAffectingIsReady: Set<String> = [
#keyPath(state)
]
Now, the state property. If it's an Objective-C-compatible type, it's easy:
#objc dynamic var state: ...
Or, if it's not:
#objc var state: SomeNonObjCThing {
willSet { self.willChangeValue(forKey: "state") }
didSet { self.didChangeValue(forKey: "state") }
}
OR:
#objc private var _stateKVO: Any { return self.state }
var state: SomeNonObjCThing {
willSet { self.willChangeValue(for: \.stateKVO) }
didSet { self.didChangeValue(for: \.stateKVO) }
}
// you can now use #keyPath(_stateKVO) in keyPathsForValuesAffecting...
(NS)Operation relies heavily on NSObject-KVO
The closest Swift syntax is
#objc private class func keyPathsForValuesAffectingIsReady() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return [#keyPath(state)]
}
Side note: You might need to make state thread-safe.

ReSwiftRecorder Add Action with property

Recently I have used ReSwift API, And I want to add ReSwiftRecorder too!
The sample of ReSwiftRecorder in Github is very simple app
I need to to something more complicated. I have an object which get data from server and I need to It reloads its data when app is not connected to net. Here is my code:
AppState:
struct AppState: StateType {
var menus: Result<[Menu]>?
}
MenuReducer:
func menusReducer(state: Result<[Menu]>?, action: Action) -> Result<[Menu]>? {
switch action {
case let action as SetMenusAction:
return action.menus
default:
return state
}
}
AppReducer:
struct AppReducer: Reducer {
func handleAction(action: Action, state: AppState?) -> AppState {
return AppState(
menus: menusReducer(state: state?.menus, action: action),
)
}
}
MenuActions:
struct SetMenus: Action {
let menus: Result<[Menu]>
}
I know I need to change MenuAction to Something like this:
let SetMenusActionTypeMap: TypeMap = [SetMenusAction.type: SetMenusAction.self]
struct SetMenusAction: StandardActionConvertible {
static let type = "SET_MENU_ACTION"
let menus: Result<[Menu]>
init() {}
init(_ standardAction: StandardAction) {}
func toStandardAction() -> StandardAction {
return StandardAction(type: SetMenusAction.type, payload: [:], isTypedAction: true)
}
}
but I got error on init functions
Return from initializer without initializing all stored properties
when I set a initializer code the error disappear but app does not restore saved data! How can I fix it?
You will want to add serialization/deserialization code. The menus property needs to be set. Also, you will want to serialize that property as payload:
let SetMenusActionTypeMap: TypeMap = [SetMenusAction.type: SetMenusAction.self]
struct SetMenusAction: StandardActionConvertible {
static let type = "SET_MENU_ACTION"
let menus: Result<[Menu]>
init() {
self.menus = // however you initialize that
}
init(_ standardAction: StandardAction) {
let maybeMenus = standardAction.payload["menus"] as? [Menu]?
self.menus = // create Result from Optional<[Menu]>
}
func toStandardAction() -> StandardAction {
let maybeMenus = self.menus.asOptional // Cannot serialize Result itself
return StandardAction(type: SetMenusAction.type, payload: ["menus" : maybeMenus], isTypedAction: true)
}
}
So problems I see here: JSON serialization depends on Dictionary representation of your payload data, i.e. the properties of your object. Can Result be serialized directly? I guess not, so you need to convert it, probably easiest to nil.
All in all, the payload is the key you missed and now you have to figure out how to use it with the data you have at hand. Also, it makes me a bit suspicious that the Result type itself is part of the AppState. I expected it to be reduced away or handled before dispatching an action, like SettingMenusFailedAction instead of ChangeMenusAction(result:) or similar. Just as a sidenote: actions should be more than typed property setters.

Simplifying nested if in swift

My app has a class called MyDevice that I use to communicate with hardware. This hardware is optionnal, so is the instance variable:
var theDevice:MyDevice = nil
Then, in the app, I have to initialize the device (for communication) then perform a self test to check its availability and readiness to perform. If this fails, the device is either not available / reachable / is malfunctionning.
Here is my overly complicated code. I am looking how to simplify it.
if let device = self.theDevice
{
device.initDevice()
if (!device.selfTest())
{
self.theDevice = nil;
}
}
I tried to combine all tests with && in the if statement, but it fails.
The issue is that I have 12 of them for various devices at the beginning of a function. It takes a lot of space and is dirty. How can I combine these statements in Swift ?
You can use ? optional chaining, and combine if case let pattern matching and where statement
theDevice?.initDevice()
if case let selfTest? = theDevice?.selfTest() where !selfTest{
theDevice = nil
}
But I would prefer if let optional unwrapping for readability.
You could improve this by making initDevice return self, then you could do the following:
if case let selfTest? = theDevice?.initDevice().selfTest() where !selfTest {
theDevice = nil
}
Another option may be to make initDevice perform the selfTest and return it.
All of this assumes that the initDevice is your code, or you can edit in one way or another.
If it is not editable, you can always make an extension and make a new method in that:
extension TheDeviceClass {
func initAndTest() -> Bool {
initDevice()
return selfTest()
}
}
and then call it using something like this:
if case let selfTest? = theDevice?.initAndTest() where !selfTest {
theDevice = nil
}
But this might make it more complicated than you would want. If you use this code a lot, consider putting it in another method.
I think you should change your MyDevice implementation to make your code less redundant, by defining a failable initializer and do all the checks inside it. Thus if the initialization fails you will get a nil and can just assign it to the relevant property of the other class, and don't even need the method that checks the 12 devices.
class MyDevice {
func selfTest() -> Bool {
// test
return testresult
}
func initDevice() {
// initialization
}
init?() {
self.initDevice()
if !self.selfTest() {
return nil
}
}
}
If you don't like to change your code too much, you may just define a closure to reduce these redundant code, like this (it still looks ugly so I DO NOT recommend you to do this.):
class SomeClassThatHasTwelveDevices {
var theDeviceOne: MyDevice?
var theDeviceTwo: MyDevice?
var theDeviceThree: MyDevice?
var theDeviceFour: MyDevice?
var theDeviceFive: MyDevice?
var theDeviceSix: MyDevice?
var theDeviceSeven: MyDevice?
var theDeviceEight: MyDevice?
var theDeviceNine: MyDevice?
var theDeviceTen: MyDevice?
var theDeviceEleven: MyDevice?
var theDeviceTwelve: MyDevice?
func someMethodThatChecksAllDevices() {
let check: (inout MyDevice?) -> Void = { deviceRef in
if let device = deviceRef {
device.initDevice()
if !device.selfTest() {
deviceRef = nil
}
}
}
check(&theDeviceOne)
check(&theDeviceTwo)
check(&theDeviceThree)
check(&theDeviceFour)
check(&theDeviceFive)
check(&theDeviceSix)
check(&theDeviceSeven)
check(&theDeviceEight)
check(&theDeviceNine)
check(&theDeviceTen)
check(&theDeviceEleven)
check(&theDeviceTwelve)
}
}

Re-initialize a lazy initialized variable in Swift

I have a variable that initialized as:
lazy var aClient:Clinet = {
var _aClient = Clinet(ClinetSession.shared())
_aClient.delegate = self
return _aClient
}()
The problem is, at some point, I need to reset this aClient variable so it can initialize again when the ClinetSession.shared() changed. But if I set the class to optional Clinet?, LLVM will give me an error when I try to set it to nil. If I just reset it somewhere in the code using aClient = Clinet(ClinetSession.shared()), it will end up with EXEC_BAD_ACCESS.
Is there a way that can use lazy and being allowed to reset itself?
lazy is explicitly for one-time only initialization. The model you want to adopt is probably just an initialize-on-demand model:
var aClient:Client {
if(_aClient == nil) {
_aClient = Client(ClientSession.shared())
}
return _aClient!
}
var _aClient:Client?
Now whenever _aClient is nil, it will be initialized and returned. It can be reinitialized by setting _aClient = nil
Because the behavior of lazy changed in Swift 4, I wrote a few structs that give very specific behavior, which should never change between language versions. I put these on GitHub, under the BH-1-PD license: https://github.com/RougeWare/Swift-Lazy-Patterns
ResettableLazy
Here is the one relevant to this question, which gives you a way to lazily-initialize a value, cache that value, and destroy it so it can be lazily-reinitialized later.
Note that this requires Swift 5.1! For the Swift 4 version, see version 1.1.1 of that repo.
The simple usage of this is very straightforward:
#ResettableLazy
var myLazyString = "Hello, lazy!"
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"
_myLazyString.clear()
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"
myLazyString = "Overwritten"
print(myLazyString) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
This will print:
Hello, lazy!
Hello, lazy!
Hello, lazy!
Hello, lazy!
Overwritten
Hello, lazy!
If you have complex initializer logic, you can pass that to the property wrapper:
func makeLazyString() -> String {
print("Initializer side-effect")
return "Hello, lazy!"
}
#ResettableLazy(initializer: makeLazyString)
var myLazyString: String
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"
_myLazyString.clear()
print(myLazyString) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString) // Just returns the value "Hello, lazy!"
myLazyString = "Overwritten"
print(myLazyString) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
You can also use it directly (instaed of as a property wrapper):
var myLazyString = ResettableLazy<String>() {
print("Initializer side-effect")
return "Hello, lazy!"
}
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"
myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
print(myLazyString.wrappedValue) // Just returns the value "Hello, lazy!"
myLazyString.wrappedValue = "Overwritten"
print(myLazyString.wrappedValue) // Just returns the value "Overwritten"
_myLazyString.clear()
print(myLazyString.wrappedValue) // Initializes, caches, and returns the value "Hello, lazy!"
These will both print:
Initializer side-effect
Hello, lazy!
Hello, lazy!
Initializer side-effect
Hello, lazy!
Hello, lazy!
Overwritten
Initializer side-effect
Hello, lazy!
This answer has been updated; its original solution no longer works in Swift 4 and newer.
Instead, I recommend you use one of the solutions listed above, or #PBosman's solution
Previously, this answer hinged on behavior which was a bug. Both that old version of this answer, its behavior, and why it's a bug are described in the text and comments of Swift bug SR-5172 (which has been resolved as of 2017-07-14 with PR #10,911), and it's clear that this behavior was never intentional.
That solution is in that Swift bug's text, and also in the history of this answer, but because it's a bug exploit that doesn't work in Swift 3.2+ I recommend you do not do that.
EDIT: As per Ben Leggiero's answer, lazy vars can be nilable in Swift 3.
EDIT 2: Seems like nilable lazy vars are no more.
Very late to the party, and not even sure if this will be relevant in Swift 3, but here goes. David's answer is good, but if you want to create many lazy nil-able vars, you will have to write a pretty hefty block of code. I'm trying to create an ADT that encapsulates this behaviour. Here's what I've got so far:
struct ClearableLazy<T> {
private var t: T!
private var constructor: () -> T
init(_ constructor: #escaping () -> T) {
self.constructor = constructor
}
mutating func get() -> T {
if t == nil {
t = constructor()
}
return t
}
mutating func clear() { t = nil }
}
You would then declare and use properties like this:
var aClient = ClearableLazy(Client.init)
aClient.get().delegate = self
aClient.clear()
There are things I don't like about this yet, but don't know how to improve:
You have to pass a constructor to the initializer, which looks ugly. It has the advantage, though, that you can specify exactly how new objects are to be created.
Calling get() on a property every time you want to use it is terrible. It would be slightly better if this was a computed property, not a function, but computed properties cannot be mutating.
To eliminate the need to call get(), you have to extend every type you want to use this for with initializers for ClearableLazy.
If someone feels like picking it up from here, that would be awesome.
This allows setting the property to nil to force reinitialization:
private var _recordedFileURL: NSURL!
/// Location of the recorded file
private var recordedFileURL: NSURL! {
if _recordedFileURL == nil {
let file = "recording\(arc4random()).caf"
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(file)
NSLog("FDSoundActivatedRecorder opened recording file: %#", url)
_recordedFileURL = url
}
return _recordedFileURL
}
Swift 5.1:
class Game {
private var _scores: [Double]? = nil
var scores: [Double] {
if _scores == nil {
print("Computing scores...")
_scores = [Double](repeating: 0, count: 3)
}
return _scores!
}
func resetScores() {
_scores = nil
}
}
Here is how to use:
var game = Game()
print(game.scores)
print(game.scores)
game.resetScores()
print(game.scores)
print(game.scores)
This produces the following output:
Computing scores...
[0.0, 0.0, 0.0]
[0.0, 0.0, 0.0]
Computing scores...
[0.0, 0.0, 0.0]
[0.0, 0.0, 0.0]
Swift 5.1 and Property Wrapper
#propertyWrapper
class Cached<Value: Codable> : Codable {
var cachedValue: Value?
var setter: (() -> Value)?
// Remove if you don't need your Value to be Codable
enum CodingKeys: String, CodingKey {
case cachedValue
}
init(setter: #escaping () -> Value) {
self.setter = setter
}
var wrappedValue: Value {
get {
if cachedValue == nil {
cachedValue = setter!()
}
return cachedValue!
}
set { cachedValue = nil }
}
}
class Game {
#Cached(setter: {
print("Computing scores...")
return [Double](repeating: 0, count: 3)
})
var scores: [Double]
}
We reset the cache by setting it to any value:
var game = Game()
print(game.scores)
print(game.scores)
game.scores = []
print(game.scores)
print(game.scores)
There are some good answers here. Resetting a lazy var is indeed, desirable in a lot of cases.
I think, you can also define a closure to create client and reset lazy var with this closure. Something like this:
class ClientSession {
class func shared() -> ClientSession {
return ClientSession()
}
}
class Client {
let session:ClientSession
init(_ session:ClientSession) {
self.session = session
}
}
class Test {
private let createClient = {()->(Client) in
var _aClient = Client(ClientSession.shared())
print("creating client")
return _aClient
}
lazy var aClient:Client = createClient()
func resetClient() {
self.aClient = createClient()
}
}
let test = Test()
test.aClient // creating client
test.aClient
// reset client
test.resetClient() // creating client
test.aClient
If the objective is to re-initialize a lazy property but not necessarily set it to nil, Building from Phlippie Bosman and Ben Leggiero, here is something that avoids conditional checks every time the value is read:
public struct RLazy<T> {
public var value: T
private var block: () -> T
public init(_ block: #escaping () -> T) {
self.block = block
self.value = block()
}
public mutating func reset() {
value = block()
}
}
To test:
var prefix = "a"
var test = RLazy { () -> String in
return "\(prefix)b"
}
test.value // "ab"
test.value = "c" // Changing value
test.value // "c"
prefix = "d"
test.reset() // Resetting value by executing block again
test.value // "db"
I made #David Berry's answer into a property wrapper. Works great with UI-components that you want to reload if you need to apply size changes but otherwise want to hold in their configured state.
#propertyWrapper class Reloadable<T: AnyObject> {
private let initializer: (() -> T)
private var _wrappedValue: T?
var wrappedValue: T {
if _wrappedValue == nil {
_wrappedValue = initializer()
}
return _wrappedValue!
}
init(initializer: #escaping (() -> T)) {
self.initializer = initializer
}
func nuke() {
_wrappedValue = nil
}
}
Here's an example with a CAShapeLayer. Set you variable like so:
#Reloadable<CAShapeLayer>(initializer: {
Factory.ShapeLayer.make(fromType: .circle(radius: Definitions.radius, borderWidth: Definitions.borderWidth)) // this factory call is just what I use personally to build my components
}) private var circleLayer
and when you want to reload your view just call:
_circleLayer.nuke()
Then you can use the var circleLayer as you normally would in your re-layout routine, upon which it will get re-initialized.
PS: I made a gist for the file I use in my own project: https://gist.github.com/erikmartens/b34a130d11b62400ab13a59a6c3dbd91
This seems like a pretty bad code smell. Something strange is going on with:
var _aClient = Clinet(ClinetSession.shared())
What is ClinetSession.shared()?
This looks like a static function that returns a new instance of ClinetSession on every call.
So no wonder you are not seeing changes to this object. It seems like a broken singleton to me.
class ClinetSession {
static func shared() -> Self {
ClinetSession()
}
}
Try doing this instead:
class ClinetSession {
static let shared = ClinetSession()
var value: Int = 0
}
Now if you change ClinetSession value you will see it.
There's no need for a lazy property that can be reset here as far as I can tell.
Here's a fully working example. BTW if you don't own ClinetSession then write a wrapper to control this.
class Clinet {
let clinetSession: ClinetSession
init(_ clinetSession: ClinetSession) {
self.clinetSession = clinetSession
}
var delegate: P?
}
class ClinetSession {
static let shared = ClinetSession()
var value = 0
}
protocol P { }
struct Test: P {
lazy var aClient:Clinet = {
var _aClient = Clinet(ClinetSession.shared)
_aClient.delegate = self
return _aClient
}()
mutating func updateSession() {
aClient.clinetSession.value = 10
}
}
var test = Test()
test.updateSession()
print(test.aClient.clinetSession.value)
// prints 10
Note: If you don't want to use a singleton then don't use shared() as the constructor as this is a convention. But then it's up to you to make sure you pass in the same reference as the one you want to mutate. That's your job to manage. The singleton just makes sure there is only 1 instance so this becomes simpler but has its trade offs.