Reputation: 4840
For an example, there is simple task which makes network call and then maps the result.
class Task {
func execute() -> Single<String> {
return doSomeRequest().flatMap { [weak self] data in
if let string = self?.map(data: data) {
return .just(string)
}
return .error(Error())
}
}
func doSomeRequest() -> Single<Data> {
// makes network call and returns Data
}
func map(data: Data) -> String? {
// maps Data to String
}
}
And there is function that uses this Task
:
func fetchSomeString() {
Task().execute().subscribe(onSuccess: { string in
print(string)
}).disposed(by: disposeBag)
}
The problem is that Task
is deinitialized before even get response from doSomeRequest()
, due to weak self
in flatMap
. One of the solution would be to store Task instance as a property, but let's say there is no place in my case for that.
Is there any solution that doesn't lead to memory leaks in flatMap
, but executes Task
properly?
Upvotes: 2
Views: 196
Reputation: 5679
One of the solution would be to store Task instance as a property, but let's say there is no place in my case for that.
There is one great operator which can store Task
instance as a property and it doesn't need a place to store it. The operator is called Using.
Here is a little example:
flatMapLatest({ (room: String?) -> Observable<WebSocketServiceValue> in
switch room {
case .some(let room):
return Observable
.using({ WebSocketServiceInstance(room: room) }, observableFactory: { $0.observable })
case .none:
return Observable<WebSocketServiceValue>
.just(WebSocketServiceValue.connected(false))
}
})
I observer a room which is an optional String
. If there is a valid room - I create a WebSocketServiceInstance
and observe its signals. As soon as the room is changed - WebSocketServiceInstance
is deinited because of flatMapLatest
operator. So, the instance lives as long as you have a connection.
In your example your Task
class might live as long as the operation executes if you use Using
operator. The only thing is required - Task
class should conform to Disposable
. But it is very easy:
extension WebSocketServiceInstance: Disposable {
func dispose() {
socket?.delegate = nil
socket?.close()
socket = nil
}
}
Upvotes: 1
Reputation: 138051
It seems to me that you simply have to use a strong reference to self
instead of a weak reference.
I assume that your closure is evaluated once and then discarded. If that is the case, then you will temporarily have a strong reference cycle that will self-break at the end of the operation.
Upvotes: 1
Reputation: 32806
You could make map(data:)
be a static method. This way you don't need the task
instance, and it might make more sense than an instance method if the map
one doesn't make use of any instance members.
class Task {
func execute() -> Single<String> {
return doSomeRequest().map { [weak self] data in
guard let string = Task.map(data: data) else {
throw Error()
}
return string
}
}
func doSomeRequest() -> Single<Data> {
// makes network call and returns Data
}
static func map(data: Data) -> String? {
// maps Data to String
}
}
I also made some changes to you flow by calling Single.map
and throwing the error, this is a little more readable.
Upvotes: 1
Reputation: 3433
Yes 'one solution is to store Task
instance as a property' or you can pass/essentially store the instance in your execute
function.
static func execute(_ task: Task) -> Single<String> {
return task.doSomeRequest().flatMap { data in
if let string = task.map(data: data) {
return .just(string)
}
return .error(Error())
}
}
Then call it like:
Task.execute(Task()).subscri...
Upvotes: 1