Summary
On iOS in Xcode OperationQueue Thread is not released by IMAP Session (each session allocates 560KB = standard thread size)
These threads stay in memory, and finally app goes out of memory. Might be because [dispatch_release] is not supported anymore or maybe something holds in [DispatchQueue] or the tasks count is not released to zero (so it will not get cleared), or something is not [nil]'ed to release by iOS.
Platform(s)
iOS iPhone 13
XCode
**Happens on Mail Server**
outlook
Hotmail
And few others - hosted mail servers
**Piece of code**
To reproduce run it on timer (all class below):
public class MapViewModel {
public var TAG:String = "MapViewModel : "
var timer: Timer? = nil
public func StopTaskScheduler(){
if (self.timer != nil) {
self.timer!.invalidate()
self.timer = nil
}
}
func StartTaskScheduler(){
if timer != nil {StopTaskScheduler()}
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: 50.0, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
timer?.fire()
}
}
@objc func timerAction() {
print("timer started - begin")
// you can run it as closure
imapConnect()
// you can run it async - same result as closure
Task { [weak self] in
guard let strongSelf = self else {
return
}
await strongSelf.imapConnectAsync()
}
}
private func imapConnect() {
let session = MCOIMAPSession()
Log.d("TAG", "IMAP isOperationQueueRunning: \(session.isOperationQueueRunning)")
session.hostname = "imap.*********.com"
session.username = "*****@********.com"
session.password = "*******"
session.port = 993
session.allowsFolderConcurrentAccessEnabled = true
session.connectionType = MCOConnectionType.TLS
session.authType = MCOAuthType.saslLogin
session.isCheckCertificateEnabled = false
session.isVoIPEnabled = false
session.maximumConnections = 2
session.timeout = 10
if let loginOperation = session.checkAccountOperation() {
loginOperation.start { (error) -> Void in
if (error != nil) {
print("Error login:\n\(String(describing: error))")
} else {
print("imapConnect - Successful IMAP connection")
}
}
}
// list folders
var folderList:[Any]? = nil
if let folderListOperation = session.fetchAllFoldersOperation() {
folderListOperation.start { error, folders in
if let error = error {
print("Error downloading folder list: \(error.localizedDescription)")
} else {
folderList = folders
print("All IMAP Folders loaded")
for value in folderList! { // MARK: folderlist was null
switch value {
case is MCOIMAPFolder:
let folder:MCOIMAPFolder = value as! MCOIMAPFolder
//folder.path
print("\(folder.path.description) is a folder")
//folder.
default:
print("Skip ... possibly null value!")
}
}
}
}
}
// logout
if let logoutOperation = session.disconnectOperation() {
logoutOperation.start { error in
if (error != nil) {
print("IMAP logoutOperation Error")
} else {
print("imapConnect - Successful IMAP logoutOperation")
}
}
}
session.cancelAllOperations()
Log.d("TAG", "IMAP isOperationQueueRunning: \(session.isOperationQueueRunning)")
}
private func imapConnectAsync() async {
let session = MCOIMAPSession()
Log.d("TAG", "IMAP isOperationQueueRunning: \(session.isOperationQueueRunning)")
session.hostname = "imap.*******.com"
session.username = "******@*****.com"
session.password = "*******"
session.port = 993
session.allowsFolderConcurrentAccessEnabled = true
session.connectionType = MCOConnectionType.TLS
session.authType = MCOAuthType.saslLogin
session.isCheckCertificateEnabled = false
session.isVoIPEnabled = false
session.maximumConnections = 2
session.timeout = 10
if let loginOperation = session.checkAccountOperation() {
do { try await loginOperation.start(); print("imapConnect - Successful IMAP connection") }
catch { print("imapConnect - IMAP Connect Error: \(error)"); return }
}
// list folders
var folderList:[Any]? = nil
if let folderListOperation = session.fetchAllFoldersOperation() {
do { folderList = try await folderListOperation.start(); print("All IMAP Folders loaded") }
catch { print("Error listing folders: \(error)") }
}
for value in folderList! { // MARK: folderlist was null
switch value {
case is MCOIMAPFolder:
let folder:MCOIMAPFolder = value as! MCOIMAPFolder
print("\(folder.path.description) is a folder")
default:
print("Skip ... possibly null value!")
}
}
// logout
if let logoutOperation = session.disconnectOperation() {
do { try await logoutOperation.start(); print("Successful IMAP logoutOperation") }
catch {
print("IMAP logoutOperation Error: \(error)")
return
}
}
session.cancelAllOperations()
Log.d("TAG", "IMAP isOperationQueueRunning: \(session.isOperationQueueRunning)")
}
}
**Actual outcome**
MailCore does not release IMAP Session thread
** Logs**
Please see the screenshot
**Expected outcome**
MailCore should release IMAP Session thread
Summary On iOS in Xcode OperationQueue Thread is not released by IMAP Session (each session allocates 560KB = standard thread size) These threads stay in memory, and finally app goes out of memory. Might be because [dispatch_release] is not supported anymore or maybe something holds in [DispatchQueue] or the tasks count is not released to zero (so it will not get cleared), or something is not [nil]'ed to release by iOS. Platform(s) iOS iPhone 13