First of all, I am new to instrument and memory graph. Was trying to check whether my app is having memory leak then I bump into this issue. It is a collection of class having MVVM architecture that does the functions below:
View Controller trigger a function in the View Model
View Model ask the networking class to retrieve JSON from web api
Networking class successfully retrieve data from the web api, it use SwiftyJson to convert the dictionary into JSON object and memory leak occur
The leak happen in the init function of SwiftyJSON aka JSON()/JSON.init()
The code base is a mixture of objective-c and swift but the classes that has memory leak are written in swift
I have done a little bit of research, below are my finding:
Create a new project and copy the classes and attempt to recreate the issue but no memory leak occur
This issue only occur when closure is called and return the result back to the caller
This leak also happen on other places that having similar code structure; Calling closure -> closure make an api call -> receive result -> convert into JSON -> Memory leak. Another case is getting remote config using Firebase SDK. In more detail, the function is FIRRemoteConfig.fetch
Enough of introduction, code and screenshot of memory graph and instrument as below, (The code class is pretty big so I only attach the most important part):
View Controller Class
#objc final class HomeVC: UIViewController {
internal lazy var viewModel = { return HomeVM() }()
override func viewDidLoad() {
initVM()
}
private func initVM() {
viewModel.reloadPersonalization.addObserver(onChange: {[weak self] (reload) in
if reload {
self?.collectionView.reloadSections(IndexSet(integer: HomeSection.personalization.rawValue))
}
})
}
}
View Model Class
import SwiftyJSON
class HomeVM {
//MARK:- Observable
let reloadPersonalization = Observable<Bool>(value: false)
let isLoading = Observable<Bool>(value: false)
//MARK:- Properties
var personalizations = Personalization()
//MARK:- Initialization
init() {
}
//MARK:- Functions
public func fetchPersonalization() {
BaseApi.requestAPIService(apiService: MainMallService.showPersonalization, resultReceive: {
}, success: {[weak self] (json) in
self?.personalizations.updateProductList(json: json)
self?.reloadPersonalization.value = true
}, successWithError: {[weak self] (errorMsg) in
self?.reloadPersonalization.value = false
}, fail: {[weak self] (errorMsg) in
self?.reloadPersonalization.value = false
})
}
}
Networking Class
import Moya
import SwiftyJSON
import Alamofire
class BaseApi {
//MARK: Properties
//MARK: Functions
open class func requestAPIService(apiService: TargetType, resultReceive: #escaping () -> Void ,success: #escaping (_ jsonResponse: JSON) -> (), successWithError: #escaping (_ error: String) -> (), fail: #escaping (_ errorMsg: String) -> ()) {
let provider = MoyaProvider<MultiTarget>(manager:NetworkManager.shared ,plugins: [NetworkLoggerPlugin(verbose: true)])
let target = MultiTarget(apiService)
provider.request(target) { result in
resultReceive()
switch result {
case let .success(moyaResponse):
let data = moyaResponse.data
let json = JSON(data)
let apiStatus = ApiStatus.initWithJson(json: json["status"])
switch apiStatus.status {
case .Success:
if json["response"].exists() {
let jsonResponse = json["response"]
success(jsonResponse)
}
case .Failed:
successWithError(StringConst.Error.internalError)
case .Unkown:
fail(StringConst.Error.unknownError)
}
case let .failure(error):
var errorMsg = ""
if error._code == NSURLErrorTimedOut {
errorMsg = StringConst.Connection.timeout
} else if error._code == NSURLErrorNotConnectedToInternet {
errorMsg = StringConst.Connection.noConnection
} else {
errorMsg = StringConst.Error.unknownError
}
fail(errorMsg)
}
}
}
}
Memory leak occur on this line code, both memory graph and instrument is pointing to this line, again; Leak only occur when success block is called, other block like error and successWithError don't trigger the memory leak.
let json = JSON(data)
Appreciate your help !
Related
I have a simple app, that communicates with server via TCP Socket using custom protocol. I want to achieve HTTP-like response-request behaviour, abstracting from socket layer.
So I have simple protocol:
protocol ResponseType {
init(with frame: SocketMessage)
}
And some of examples:
struct MessageAck: ResponseType {
var messageId: String
init(with frame: SocketMessage) {
messageId = frame.messageId
}
}
I created simple protocol for sending requests:
protocol APIClient {
func send<T: ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
enum SocketAPIRequest {
case textMessage(messageId: String, ...)
...
}
And finally:
enum Result<T> {
case success(T)
case failure(Error)
}
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
....
callbacks[stompFrame.receiptId] = completion
....
}
}
So, when I want to store callback for each request, to call it after answer will be received, I got such error:
Cannot assign value of type '((Result<T>) -> Void)?' to type '((Result<ResponseType>) -> Void)?'
I guess the problem with mixing Type's and objects, or maybe something else.
Swift generics are not covariant (with special hard-coded exceptions for Array which involve copying the elements). That means that Result<Apple> is not a subtype of Result<Fruit>. See Swift Generics & Upcasting for examples of why.
In your case, what would prevent you from passing a Result<MessageBody> to a callback that expected a Result<MessageAck>? For example:
for callback in callbacks {
callback(result)
}
How could you know this was legal at compile time for any given type of result?
EDIT (BETTER ANSWER):
You can hide the type inside a closure to get what you want. Try this:
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<SocketMessage>) -> Void)]() // <--- Change
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
// Store the closure we don't understand inside a closure we do
callbacks[stompFrame.receiptId] = { result in
switch result {
case .success(let message):
completion?(.success(T.init(with: message)))
case .failure(let error):
completion?(.failure(error))
}
}
}
}
Now, instead of trying to hold T directly in callbacks, it's held in each individual closure, hidden from the rest of the class, and T never escapes this function. When you get to wherever you call callback in your code, just pass it the Result<SocketMessage> that I assume you already have somewhere.
OLD ANSWER:
The simplest solution to your problem is to have the callback always pass a Result<Data> and remove T entirely:
protocol APIClient {
func send(request: SocketAPIRequest, completion: ((Result<Data>) -> Void)?)
}
Then leave it to the MessageAck (in the completion handler) to deserialize itself from the raw data.
There are other ways to achieve all this with type erasers, but they're much more complex and sometimes very fiddly.
Have you tried the following signature
func send<T:ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?){ ... }
and still getting error?
Edit 1:
or probably you should try something like this
protocol APIClient {
associatedtype T
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
and,
class SocketAPIClient: APIClient {
typealias MessageId = String
typealias T = ResponseType
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) {
....
callbacks[stompFrame.receiptId] = completion
....
}
}
I am trying to call a function reloadTable in my HomeViewController from my Task class. But I keep being thrown an
Use of instance member 'reloadTable' on type 'HomeViewController'; did you mean to use a value of type 'HomeViewController' instead?
This is my HomeViewController code:
import UIKit
import Alamofire
import SwiftyJSON
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
func reloadTable() {
self.displayTask.reloadData()
}
}
This is my Tasks class:
import Foundation
import Alamofire
import SwiftyJSON
class Tasks {
static let sharedInstance = Tasks()
var datas: [JSON] = []
func getTaskDetails(){
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
HomeViewController.reloadTable()
for task in tasks {
if let taskName = task["task_name"].string {
print(taskName)
}
}
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
}
// for prevent from creating this class object
private init() { }
}
In this case reloadTable() is an instance method. You can't call it by class name, you have to create object for HomeViewController and then you have to call that method by using that object.
But in this situation no need to call the method directly by using HomeViewController object. You can do this in another way by using NSNotification
Using NSNotification :
Add a notification observer for your HomeViewController
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(HomeViewController.reloadTable), name:"ReloadHomeTable", object: nil)
Add the above lines in your viewDidLoad method of HomeViewController
Now replace this line HomeViewController.reloadTable() by NSNotificationCenter.defaultCenter().postNotificationName("ReloadHomeTable", object: nil) in your Task class
As many of the other answers states, you are trying to access a non-static function on a static reference.
I would suggest the use of closures, consider the following;
Create a closure for .Success and the .Failure, this enables you to act accordingly to the data request result. The onError closure is defined as an optional, this means you don't have to implement the onError in the ´getTaskDetails` function call, implement it where you feel you need the error information.
func getTaskDetails(onCompletion: ([Task]) -> (), onError: ((NSError) -> ())? = nil) {
Alamofire.request(.GET, Data.weeklyEndpoint).validate().responseJSON {
response in
switch response.result {
case .Success(let data):
let json = JSON(data)
if let buildings = json.array {
for building in buildings {
if let startDate = building["start_date"].string{
print(startDate)
}
if let tasks = building["tasks"].array{
Tasks.sharedInstance.datas = tasks
onCompletion(tasks)
}
}
}
case .Failure(let error):
print("Request failed with error: \(error)")
onError?(error)
}
}
}
From your HomeViewController:
// Call from wherever you want to reload data.
func loadTasks(){
// With error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
}, onError: {
error in
// Handle error, display a message or something.
print(error)
})
// Without error handling.
// Fetch the tasks and reload data.
Tasks.sharedInstance.getTaskDetails({
tasks in
self.displayTask.reloadData()
})
}
Basics
Closures
Make class Task as Struct, make function getTaskDetails as static and try to call function getTaskDetails() in HomeViewController. In result this of this function use realoadTable()
In HomeViewController you have defined reloadTable() as instance method not the class function.You should call reloadTable() by making instance of HomeViewController as HomeViewController(). Replace
HomeViewController.showLeaderboard()
HomeViewController().showLeaderboard()
Hope it helps. Happy Coding.
I am experimenting with generics in Swift and I am attempting to push it to its limits.
In my application I have a super simple API wrapper around Alamofire. The structure is like so:
API -> Request -> Alamofire request
Here is some generic code that I threw into a playground to test some concepts. Here is what I have so far:
protocol SomeProtocol {
var cheese: String { get }
init()
}
class Something: SomeProtocol {
required init() { }
var cheese: String {
return "wiz"
}
}
class API {
class func performRequest<T: SomeProtocol>(completion: (T?, NSError) -> Void) {
// This code is irrelevant, just satisfying the completion param
let test = T()
let error = NSError(domain: "Pizza", code: 1, userInfo: nil)
completion(test, error)
}
}
func test() {
API.performRequest<Something> { item, error in
}
}
Calling the function gives the error:
"Cannot explicitly specialize a generic function"
****** UPDATE ******
As per the answer below, removing the typical <> generic type specifier and instead adding the expected type to the completion params solves the issue. Just a quick example:
func test() {
API.performRequest { (item: Something?, error) in
}
}
Additionally, I have discovered that making the API wrapper class a generic class solves the issue like so:
protocol SomeProtocol {
var pizza: String { get }
}
class SomeObject: SomeProtocol {
var pizza: String { return "pie" }
}
class API<T: SomeProtocol> {
class func performRequest(completion: (T?, NSError?) -> Void) {
}
}
func test() {
API<SomeObject>.performRequest { item, error in
// Do something with item, which has a type of SomeObject
}
}
Either way, the end goal is accomplished. We have a single generic method that will perform a set of tasks and return, via completion closure, the object based on the type passed in with each use.
The way generics work is they allow a function to use unspecialized variables inside of its implementation. One can add functionality to these variables by specifying that the variables must conform to a given protocol (this is done within the declaration). The result is a function that can be used as a template for many types. However, when the function is called in the code itself, the compiler must be able to specialize and apply types to the generics.
In your code above, try replacing
func test() {
API.performRequest<Something> { item, error in
}
}
with
func test() {
API.performRequest { (item: Something?, error) in
}
}
this lets the compiler know which type it must apply to the function without explicitly specifying. The error message you received should now make more sense.
Here is what i did using alamofire and alamofire object mapper:
Step 1: Create modal classes that conforms to Mappable protocols.
class StoreListingModal: Mappable {
var store: [StoreModal]?
var status: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
store <- map["result"]
status <- map["status"]
}
}
Step 2: Create a fetch request using the generic types:
func getDataFromNetwork<T:Mappable>(urlString: String, completion: (T?, NSError?) -> Void) {
Alamofire.request(.GET, urlString).responseObject { (response: Response<T, NSError>) in
guard response.result.isSuccess else{
print("Error while fetching: \(response.result.error)")
completion(nil, response.result.error)
return
}
if let responseObject = response.result.value{
print(responseObject)
completion(responseObject, nil)
}
}
}
Step 3: Now all you need is to call this fetch function. This can be done like this:
self.getDataFromNetwork("your url string") { (userResponse:StoreListingModal?, error) in
}
You will not only get your response object but it will also be mapped to your modal class.
I have this custom implementation of Alamofire:
protocol HTTPProtocol: class {
typealias RequestType
typealias RespondType
func doRequest(requestData: RequestType) -> Self
func completionHandler(block:(Result<RespondType, NSError>) -> Void) -> Self
}
//example of a request:
locationInfo
//Make a request
.doRequest(HTTPLocationInfo.RequestType(coordinate: $0))
//Call back when request finished
.completionHandler { result in
switch result {
case .Success(let info): self.locationInfoRequestSuccess(info)
case .Failure(let error): self.locationInfoRequestFailed(error)
}
}
I want to apply MVVM and RxSwift into my project. However, I can't find a proper way to do this.
What I want to achieve is a ViewModel and a ViewController that can do these things:
class ViewController {
func googleMapDelegate(mapMoveToCoordinate: CLLocationCoordinate2D) {
// Step 1: set new value on `viewModel.newCoordinate` and make a request
}
func handleViewModelCallBack(resultParam: ...*something*) {
// Step 3: subscribeOn `viewModel.locationInfoResult` and do things.
}
}
class ViewModel {
//Result if a wrapper object of Alamofire.
typealias LocationInfoResult = (Result<LocationInfo.Respond, NSError>) -> Void
let newCoordinate = Variable<CLLocationCoordinate2D>(kInvalidCoordinate)
let locationInfoResult: Observable<LocationInfoResult>
init() {
// Step 2: on newCoordinate change, from step 1, request Location Info
// I could not find a solution at this step
// how to make a `completionHandler` set its result on `locationInfoResult`
}
}
Any help is deeply appreciated. Thank you.
You can use RxAlamofire as #Gus said in the comment. But if you are using any library that doesn't support Rx extensions by default you may need to do the conversion by hand.
So for the above code snippet, you can create an observable from the callback handler you had implemented
func getResultsObservable() -> Observable<Result> {
return Observable.create{ (observer) -> Disposable in
locationInfo
//Make a request
.doRequest( .... )
//Call back when request finished
.completionHandler { result in
switch result {
case .Success(let info): observer.on(Event.Next(info))
case .Failure(let error): observer.on(Event.Error(NetworkError()))
}
}
return Disposables.create {
// You can do some cleaning here
}
}
}
Callback handlers are implementation to observer pattern, so mapping it to a custom Observable is a straight forward operation.
A good practice is to cancel the network request in case of disposing, for example this is a complete disposable Post request:
return Observable<Result>.create { (observer) -> Disposable in
let requestReference = Alamofire.request("request url",
method: .post,
parameters: ["par1" : val1, "par2" : val2])
.validate()
.responseJSON { (response) in
switch response.result{
case .success:
observer.onNext(response.map{...})
observer.onCompleted()
case .failure:
observer.onError(NetworkError(message: response.error!.localizedDescription))
}
}
return Disposables.create(with: {
requestReference.cancel()
})
Note: before swift 3 Disposables.create() is replaced with NopDisposable.instance
It doesn't seem like you need to subscribe to newCoordinate so I would just make that a request func.
Then, using the info you get back from Alamofire, just set the value on the locationInfoResult and you will get the new result in the ViewController
class ViewController: UIViewController {
func viewDidLoad() {
super.viewDidLoad()
//subscribe to info changes
viewModel.locationInfoResult
.subscribeNext { info in
//do something with info...
}
}
func googleMapDelegate(mapMoveToCoordinate: CLLocationCoordinate2D) {
viewModel.requestLocationInfo(mapMoveToCoordinate)
}
}
class ViewModel {
let locationInfoResult: Variable<LocationInfoResult?>(nil)
init() {
}
func requestLocationInfo(location: CLLocationCoordinate2D) {
//do Alamofire stuff to get info
//update with the result
locationInfoResult.value = //value from Alamofire
}
}
I try to use Swift's closures like the completion blocks in ObjC when calling async requests.
This seems to works. I'm using a protocol for my model classes and in conjunction with an Array I get problems. The relevant code:
//ModelProtocol.swift
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) )
}
//Person.swift
// calls the HTTP request and should return all Person-Objects in `completion`
class func all(completion: ((models: Array<ModelProtocol>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}
//HTTPRequest.swift
func getAll(completion: ((data: NSArray) -> Void) ) {
//... some setup would be here
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
var jsonResponse: NSArray = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSArray
completion(data: jsonResponse)
}
}
//ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// use this whole code here - receive all Persons and show in a tableView or something like this
Person.all( { (models: Array<ModelProtocol>) in
println(models) //CRASH here
})
}
When I change the protocol definition of function all (and so the function all in Person.swift) to class func all(completion: ((models: Person[]) -> Void) ) it is working.
But I want to use Array<ModelProtocol> to use polymorphismus and only use classes that conforms to ModelProtocol, that can be Person or House or whatever.
I think I'm missing something important or basic here. I hope my problem is clear enough.
Edit:
In ViewController.swift the execution of the App stops at the println(models) statement with the message EXC_BAD_ACCESS.
Maybe this will do what you want:
protocol ModelProtocol {
// all my models should implement `all`
class func all(completion: ((models: Array<Self>) -> Void) )
}
class func all(completion: ((models: Array<Person>) -> Void) ) {
let request = HTTPRequest()
request.getAll() { (data:NSArray) in
var persons:Person[] = //... `data` is the result from the HTTP GET request and will be parsed here - this is ok
completion(models: persons)
}
}