Memory leak when use AFHTTPSessionManager - memory-leaks

Env:
iOS 13/iOS 10
Xcode 11.1
AFNetworking version: 3.2.1
Swift 4.2
Description:
When use AFNetworking to post or get Json data, it will cause memory leak, I create a AFHTTPSessionManager like this:
private static func ConfigureAFManager(requestSerialization: HttpSerializationType = HttpSerializationType.HTTP,
responseSerialization: HttpSerializationType = HttpSerializationType.JSON,
timeout: TimeInterval = 30,
headers: [String: String]? = nil
) -> AFHTTPSessionManager {
let AFManager = AFHTTPSessionManager()
if requestSerialization == .JSON {
AFManager.requestSerializer = AFHTTPRequestSerializer()
}
if responseSerialization == .HTTP {
AFManager.responseSerializer = AFHTTPResponseSerializer()
}
for (key, value) in headers ?? [:] {
AFManager.requestSerializer.setValue(value, forHTTPHeaderField: key)
}
AFManager.requestSerializer.timeoutInterval = timeout;
return AFManager
}
then I use it to perform post action:
static func POST(httpURL: String,
parameter: Any?,
timeout: TimeInterval = 30,
headers: [String: String]? = nil,
requestSerialization: HttpSerializationType = HttpSerializationType.HTTP,
responseSerialization: HttpSerializationType = HttpSerializationType.JSON,
success: ((Any?) -> Void)?,
fail: ((Error?) -> Void)?) -> Void
{
let AFManager = self.ConfigureAFManager(requestSerialization: requestSerialization, responseSerialization: responseSerialization, timeout: timeout, headers: headers)
AFManager.post(httpURL, parameters: parameter, progress: nil, success: { (task, response) in
success?(response)
}) { (task, error) in
fail?(error)
}
}
When I clicked the Debug Memory Graph on Xcode, I found that, there are some cycle reference between AFHTTPSessionManager and __NSURLSessionLocal.
Cycle reference
Is this happening only in the 3.2.1 version of AFNetworking?

I know why, because AFNetWorking use URLSession it's delegate will establish a strong reference, so after use the AFHttpSessionManager should call invalidateSessionCancelingTasks

Related

Publish background context Core Data changes in a SwiftUI view without blocking the UI

After running a background-context core data task, Xcode displays the following purple runtime warning when the updates are published in a SwiftUI view:
"[SwiftUI] Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."
Besides the ContentView.swift code below, I also added container.viewContext.automaticallyMergesChangesFromParent = true to init in the default Persistence.swift code.
How can I publish the background changes on the main thread to fix the warning? (iOS 14, Swift 5)
Edit: I've changed the code below, in response to the first answer, to clarify that I'm looking for a solution that doesn't block the UI when a lot of changes are saved.
struct PersistenceHelper {
private let context: NSManagedObjectContext
init(context: NSManagedObjectContext = PersistenceController.shared.container.viewContext) {
self.context = context
}
public func fetchItem() -> [Item] {
do {
let request: NSFetchRequest<Item> = Item.fetchRequest()
var items = try self.context.fetch(request)
if items.isEmpty { // Create items if none exist
for _ in 0 ..< 250_000 {
let item = Item(context: context)
item.timestamp = Date()
item.data = "a"
}
try! context.save()
items = try self.context.fetch(request)
}
return items
} catch { assert(false) }
}
public func updateItemTimestamp(completionHandler: #escaping () -> ()) {
PersistenceController.shared.container.performBackgroundTask({ backgroundContext in
let start = Date(), request: NSFetchRequest<Item> = Item.fetchRequest()
do {
let items = try backgroundContext.fetch(request)
for item in items {
item.timestamp = Date()
item.data = item.data == "a" ? "b" : "a"
}
try backgroundContext.save() // Purple warning appears here
let interval = Double(Date().timeIntervalSince(start) * 1000) // Artificial two-second delay so cover view has time to appear
if interval < 2000 { sleep(UInt32((2000 - interval) / 1000)) }
completionHandler()
} catch { assert(false) }
})
}
}
// A cover view with an animation that shouldn't be blocked when saving the background context changes
struct CoverView: View {
#State private var toggle = true
var body: some View {
Circle()
.offset(x: toggle ? -15 : 15, y: 0)
.frame(width: 10, height: 10)
.animation(Animation.easeInOut(duration: 0.25).repeatForever(autoreverses: true))
.onAppear { toggle.toggle() }
}
}
struct ContentView: View {
#State private var items: [Item] = []
#State private var showingCoverView = false
#State private var refresh = UUID()
let persistence = PersistenceHelper()
let formatter = DateFormatter()
var didSave = NotificationCenter.default
.publisher(for: .NSManagedObjectContextDidSave)
// .receive(on: DispatchQuene.main) // Doesn't help
var body: some View {
ScrollView {
LazyVStack {
Button("Update Timestamp") {
showingCoverView = true
persistence.updateItemTimestamp(completionHandler: { showingCoverView = false })
}
ForEach(items, id: \.self) { item in
Text(formatter.string(from: item.timestamp!) + " " + (item.data ?? ""))
}
}
}
.id(refresh)
.onAppear {
formatter.dateFormat = "HH:mm:ss"
items = persistence.fetchItem()
}
.onReceive(didSave) { _ in
items = persistence.fetchItem()
}
.fullScreenCover(isPresented: $showingCoverView) {
CoverView().onDisappear { refresh = UUID() }
}
}
}
Since you are performing a background task, you are on a background thread - rather than the main thread.
To switch to the main thread, change the line producing the runtime warning to the following:
DispatchQueue.main.async {
try backgroundContext.save()
}
You should use Combine and observe changes to your background context and update State values for your UI to react.
#State private var coreDataAttribute = ""
var body: some View {
Text(coreDataAttribute)
.onReceive(
CoreDataManager.shared.moc.publisher(for: \.hasChanges)
.subscribe(on: DispatchQueue.global())
.receive(on: DispatchQueue.global())
.map{_ in CoreDataManager.shared.fetchCoreDataValue()}
.filter{$0 != coreDataAttribute}
.receive(on: DispatchQueue.main))
{ value in
coreDataAttribute = value
}
}

Nesting URLSession.shared.dataTask in Swift 4

I am trying to fetch data from an api where the JSON returned has URLs to other pieces of information that I need, such as
"value1" : "data",
"value2": {
"url": "https://example.com/stuff",
}
My logic is as follows:
func(completion: #escaping ([Data]) -> ()) {
var classArray = [myClass]()
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
guard let resultArray = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
let myObject = myClass(value1: resultArray["value1"]! as! String)
guard let valueUrl = URL(string: resultArray["value2"]! as! String) else { return }
URLSession.shared.dataTask(with: valueUrl) { (data, _, _) in
myObject.value2 = data
classArray.append(myObject)
}.resume()
} catch let error {
print("Failed to create json with error: ", error.localizedDescription)
}
completion(classArray)
}.resume()
}
}
Is this a valid approach or are there better implementations? Trying to avoid a future Pyramid of Doom situation. I have tried putting the inner URLSession call in a separate private function but still receive an empty classArray in the end.

FBSDKLoginKit is crashing for unknown reason on iOS11.3 version

#IBAction func didTapLogainAction(_ sender: Any) {
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["email"], from: self) { (result, error) in
if (error == nil){
let fbloginresult : FBSDKLoginManagerLoginResult = result!
if fbloginresult.grantedPermissions != nil {
if(fbloginresult.grantedPermissions.contains("email")) {
if((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
let dict = result as! [String : AnyObject]
FBSDKLoginManager().logOut()
}
})
}
}
}
}
}
}
Facebook login, when i'm press continue from facebook interface, it crashing on my iOS11.3 version. it quitely working good in ios11.4 and iOS11.03 lowers versions.
It got resolved by the Facebook developer team. Meanwhile, we have to use the following snippet for iOS11.3. If older Methods also will work i think.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(app, open: url, options: options)
}

replacing %3F instead of `?` in url

i have this problem when i want call this method in request. It seems to be replacing %3f instead of ? in swift4
its my ApiRouter
struct ApiRouter {
enum Router: URLRequestConvertible {
case getAllPlcae(id: Int, paseSize: Int, pageNumber: Int, countryID: Int, cityID: Int)
var method: Alamofire.HTTPMethod {
switch self {
case .getAllPlcae:
return .get
}
}
func asURLRequest() throws -> URLRequest {
let result: (path: String, parameters: [String: AnyObject]?) = {
switch self {
case .getAllPlcae(let id, let pageSize, let pageNumber, let countryID, let cityID):
return("Location?textToSearch=&tagIds=&id=\(id)&pageSize=\(pageSize)&pageNumber=\(pageNumber)&countryID=\(countryID)&cityID=\(cityID)",nil)
}
}()
// MARK: - Set HTTP Header Field
let url = URL(string: Constants.ApiConstants.baseURLString)!
var urlRequest = URLRequest(url: url.appendingPathComponent(result.path))
urlRequest.httpMethod = method.rawValue
if let token = User.getToken() {
urlRequest.setValue(token, forHTTPHeaderField: "Authorization")
}
let encoding = try Alamofire.URLEncoding.default.encode(urlRequest, with: result.parameters)
return encoding
}
}}
when i call this request , just like down
Alamofire.request(ApiRouter.Router.getAllPlcae(id: 0, paseSize: 10, pageNumber: 1, countryID: 0, cityID: 0)).responseArray { (response: DataResponse<[Place]>) in }
its my url request
Location%3FtextToSearch=&tagIds=&id=0&pageSize=10&pageNumber=1&countryID=0&cityID=0
It seems to be replacing %3f instead of ?
how can fix it ?
I found the solution for this question. We should remove Percent Encoding.
// MARK: - Set HTTP Header Field
let base = URL(string: Constants.ApiConstants.baseURLString)!
let baseAppend = base.appendingPathComponent(result.path).absoluteString.removingPercentEncoding
let url = URL(string: baseAppend!)
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = method.rawValue
if let token = User.getToken() {
urlRequest.setValue(token, forHTTPHeaderField: "Authorization")
}
let encoding = try Alamofire.URLEncoding.default.encode(urlRequest, with: result.parameters)
return encoding
Post with url parameters
https://github.com/Moya/Moya/issues/1030#issuecomment-646910369
public var task: Task {
...
return .uploadCompositeMultipart(multipartData, urlParameters: ["key":"value"])
}

Convert Data to String in Swift 3

I am very new to Swift.
I want to create something like API on Swift for my educational app.
I have this code:
static func getFilm(filmID: Int) -> String {
print("getFilm")
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
var request = URLRequest(url: url)
var returnData: String = ""
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if var responseVar = response, var dataVar = data {
print(responseVar)
returnData = String(data: dataVar, encoding: .utf8)
} else {
print(error)
}
}
task.resume()
return returnData
}
And I try to convert Data to String in this line: returnData = String(data: dataVar, encoding: .utf8)
Swift compiler gives me an error, and change this line to
returnData = String(data: dataVar, encoding: .utf8)!
, when I execute this line I get empty returnData variable.
If I use basic example line
print(String(data: data, encoding: .utf8))
everything will be OK and I can see data in XCode console.
So, how I can convert Data to String?
This is an example using a completion handler:
class func getFilm(filmID: Int, completion: #escaping (String) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
print(error!)
completion("")
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(returnData)
} else {
completion("")
}
}
}.resume()
}
And you call it
MyClass.getFilm(filmID:12345) { result in
print(result)
}
In case of an error the completion handler returns an empty string.
MyClass is the enclosing class of getFilm method. Most likely the web service will return JSON, so you might need to deserialize the JSON to an array or dictionary.
In a more sophisticated version create an enum with two cases and associated values
enum ConnectionResult {
case success(String), failure(Error)
}
With a little more effort demonstrating the subtle power of Swift you can return either the converted string on success of the error on failure in a single object.
class func getFilm(filmID: Int, completion: #escaping (ConnectionResult) -> ()) {
let url = URL(string: "https://api.kinopoisk.cf/getFilm?filmID=\(filmID)")!
URLSession.shared.dataTask(with:url) { (data, response, error) in
if error != nil {
completion(.failure(error!))
} else {
if let returnData = String(data: data!, encoding: .utf8) {
completion(.success(returnData))
} else {
completion(.failure(NSError(domain: "myDomain", code: 9999, userInfo: [NSLocalizedDescriptionKey : "The data is not converible to 'String'"])))
}
}
}.resume()
}
On the caller side a switch statement separates the cases.
MyClass.getFilm(filmID:12345) { result in
switch result {
case .success(let string) : print(string)
case .failure(let error) : print(error)
}
}
I had this problem, you can't use encoding: .utf8 for unpredictable data. It will return nil every time.
Use this instead:
String(decoding: data, as: UTF8.self)
For anyone coming in future (which are probably not interested in OP's film code?!);
Simply, try something like:
extension Data {
public func toString() -> String {
return String(data: self, encoding: .utf8) ?? "";
}
}
See also my toHex related answer

Resources