loading...
Cover image for [DROPS ] #1 - Swift - Memory leak

[DROPS ] #1 - Swift - Memory leak

jonatanlllima profile image Jonatan Lima ・2 min read

Memory Leak in Swift

Pelo fato de Swift trabalhar orientado a callbacks (completion handlers).

Quando fazemos referência de uma classe dentro de um completion, criamos uma dependência cíclica, com isso as duas classes dependem uma da outra.

Nesse exemplo, adicionamos uma referência de url na completion do método post da classe HttpPostClient. Dessa forma termos uma referência forte.

Dessa forma esses objetos não serão liberados da memória, criando um memory leak.

public final class RemoteAddAccount: AddAccount {
    private let url: URL
    private let httpClient: HttpPostClient

    public init(url: URL, httpClient: HttpPostClient) {
        self.url = url
        self.httpClient = httpClient
    }

    public func add(addAccountModel: AddAccountModel, completion: @escaping (Result<AccountModel, DomainError>) -> Void) {
        self.httpClient.post(to: self.url, with: addAccountModel.toData()) { result in
                // FIXME: - Memory leak
            let x = self.url

            switch result {
            case .success(let data):
                if let model: AccountModel = data.toModel() {
                    completion(.success(model))
                } else {
                    completion(.failure(.unexpected))
                }
            case .failure: completion(.failure(.unexpected))
            }
        }
    }

Para resolvermos esse problema esse problema, temos que capturara o self como weak, assim o self dentro da completion se torna nullable, tornando uma referência fraca, nesse caso temos que trata-lo com um guard let ou como optional.

public final class RemoteAddAccount: AddAccount {
    private let url: URL
    private let httpClient: HttpPostClient

    public init(url: URL, httpClient: HttpPostClient) {
        self.url = url
        self.httpClient = httpClient
    }

    public func add(addAccountModel: AddAccountModel, completion: @escaping (Result<AccountModel, DomainError>) -> Void) {
        self.httpClient.post(to: self.url, with: addAccountModel.toData()) { [weak self] result in
            guard let x = self?.url else { return }
            switch result {
            case .success(let data):
                if let model: AccountModel = data.toModel() {
                    completion(.success(model))
                } else {
                    completion(.failure(.unexpected))
                }
            case .failure: completion(.failure(.unexpected))
            }
        }
    }
}

Dessa forma, caso a classe morra, ele pode liberar a memória, pois a referência é fraca.

Posted on by:

jonatanlllima profile

Jonatan Lima

@jonatanlllima

Hi, I love development with the best practices with tests and clean architecture.

Discussion

markdown guide