lucas34 / swiftqueue Goto Github PK
View Code? Open in Web Editor NEWJob Scheduler for IOS with Concurrent run, failure/retry, persistence, repeat, delay and more
License: MIT License
Job Scheduler for IOS with Concurrent run, failure/retry, persistence, repeat, delay and more
License: MIT License
Hello Lucas,
I would request your support for the below scenario.
I have set up the download queue as below
Initiated one queue with queueName and tag (q1) which in turn create multiple jobs with the job name and tag name. Tag name(tag_q1) is same for all the jobs under the first queue (q1)
Initiated 2nd queue with queueName and tag (q2) which in turn create multiple jobs with the job name and tag name. Tag name(tag_q2) is same for all the jobs under the 2nd queue (q2)
Both queues are running in parallel, now I have to delete q1 and its jobs with tag name tag_q1.
I tried to cancel the jobs using the "cancelOperations(tag:"tag_q1"), but its not cancelling the job even one single job.
Kindly share your comments on how to this situation with SwiftQueue.
I'm using Swift 3.2 with your lib (1.6.0)
retryInBackgroundAfter
have to be fileprivate
for me. Hope that I'm not the only one.
I want to run the job every day between 4 and 5 am. What should I do?
return RetryConstraint.canceled() will hide the origin error from the completion block
All is in my question :)
In android-priority-jobqueue for example, they give the opportunity to define a callback.
Would it be possible to have something equivalent?
Thanks.
We will refractor error to follow enum Pattern
ContraintError will be removed
All errors will be thrown under SwiftQueueError
Loading serialised task may be a time consuming operation. Should found a way to allow this to run asynchronously.
But newly added task should still be scheduled AFTER.
Hi,
I am using JobCreator with custom params as below
let swiftQueueManager = SwiftQueueManagerBuilder(creator: JobCreator(otherParams : otherParams, completion:completionForJob)).build()
First thing is I run this line of code every time i.e. this instance is created every time as the params values is different always and there is no way to set JobCreator in ManagerBuilder other than initialisation.
After 3-4 such runs, the jobs get executed but not in synchronised manner.
Do I need to keep the instance of manager on class level? If yes, then how can I set job creator in the manager as it has different params every time.
And how is job removed after successful run? Do I manually need to call remove function after execution in onRun() method?
Please respond.
Thanks
if the application enters :
applicationDidEnterBackground(_:)
would a job persist once it comes back to the foreground?
How can I subscribe to a Job's callback?
i.e. I execute my Job in a View Model and I want to know the result of the job inside the View Model
I want to use this SwiftQueue for multiple files downloading. Can you provide me a sample code it.
First, thank you for the wonderful library!
Let's say I have a job group like this:
JobBuilder(type: CatJob.type)
.with(params: [CatJob.PARAM_CAT_ID: catId])
.singleInstance(forId: catId)
// Requires internet to run
.internet(atLeast: .cellular)
// will run in parallel to other group names, in sequence with other device info
.group(name: "cats")
.retry(limit: .limited(10))
// Add to queue manager
.schedule(manager: queueManager)
and inside the job, we have something like this:
func onRetry(error: Error) -> RetryConstraint {
if let err = error as? SyncError {
switch (err) {
case .apiError(let httpStatus):
if httpStatus == 400 {
return RetryConstraint.cancel
}
case .localError:
// do nothing, fall through to below
break
}
}
return RetryConstraint.exponential(initial: 10)
}
And now I add 3 cat jobs. The first one succeeds, but the second one fails.
Will the library continue to retry the second cat job before proceeding to cat job #3? From what I can tell, this seems to be the case. Is it possible to configure the library to continue to process all of the cat jobs and simply push the failing ones to the bottom of the queue?
Thanks!
In order to make the completion callback more extensible, I would like to implement as follow
// Sucess
onDone(result: JobCompleted.success)
// fail
onDone(result: JobCompleted.withError(error))
#unittest
I see that I can pause jobs with the pause()
method but it looks like there is no way to know if the manager is in the paused
state. Any hint?
I found func singleInstance(forId: String, override: Bool = false) -> Self
to allow only 1 job of a type but my use case is a bit different.
I do a sync with the backend and during the app lifetime there are requests for that sync coming in. The idea is that when a sync is already running, it should be not canceled and another sync should be scheduled. But when one sync is already running and another one is already scheduled, there is no point in scheduling a third sync.
Basically I need the queue to always have at most 1 running and 1 scheduled operation. Is this possible?
'Invalid type in JSON write (SwiftQueue.SwiftQueueManager)'
Hi,
I just wanted to ask how to go about executing a job only once.
I tried implementing the onRetry(error: Error) method with a return of RetryConstraint.cancel
Also, I built the job with the retry option set at .limited(0.0) and .limited(-1.0).
By looking at the console when executing queued jobs, it appears that the job's onRun method gets executed twice.
Hope you can help.
Regards,
Dennis
The param will be included with other meta-info and automatically serialised.
It will be clearer to have it as Dictionary.
Make it compatible with Carthage.
I'm trying to create a periodic job that runs once every x time. But when onDone(error: Swift.Error?)
is called with an error it never reschedules.
Hi @lucas34, my name is Zuur W. I'm a graphic designer and I'd like to collaborate on your open source project and propose free logo/icon design for your project. If it's something you're interested in, please, let me know!
Best Regards
Zuur
For example I want schedule a job for a specific time to run a code that fetches some data from the server.
is it possible?
I want to know if it will still run the scheduled job even when the app is terminated or not even running?
Also tell me if it will still able to run the scheduled job even after device restart?
How can i pass information from one job to another when the first one completes. For example, i'm requesting some information from the web. Once i get that information I use it for make another request from the web.
I stumbled upon a problem I didn't think about before. All my jobs are being run after a delay. I thought I'd be able to suspend them whenever I need to, but it looks like the current implementation is actually creating the Operation
and in its run
method it's just waiting the time set by the delay. That way, even if the OperationQueue
is being suspended, all of the tasks that I queued are already being run and can't be suspended.
Have you got any idea how I could change this to make operations with delays suspendable?
Cheers!
Specify a timeout for each run. This will prevent the job to get stuck.
Add a callback completion in SwiftQueue manager.
Sometimes we need to know exactly when the job is done.
For example when using background fetch in iOS. The job will still go trough the Queue.
When i try to import SwiftQueue using Carthage its asking minimum deployment target should be iOS 10.3. My target was iOS 9.0.
Can some one give some idea what changes i need to do, to support minimum iOS 9.0.
hi thank for this library
this is my class. i am using Alamofire for http request.
`class SendJobLocation: Job {
// Type to know which Job to return in job creator
static let type = "SendJobLocation"
// Param
private let loc: [String: Any]
required init(Location: [String: Any]) {
// Receive params from JobBuilder.with()
self.loc = Location
}
func onRun(callback: JobResult) {
let url = myurl
let token = UserDefaults.standard.string(forKey: "token") as! String//?.data(using: .utf8)?.base64EncodedString()
let auth = "PT-\(UserDefaults.standard.string(forKey: "myuuid")!):\(token)".data(using: .utf8)?.base64EncodedString()
let headerInsert : HTTPHeaders = ["Authorization" : "Basic \(auth!)"]
print("Parameter is : \(loc) \n and header is : \(headerInsert)")
Alamofire.request(url, method: .post, parameters: loc, encoding: JSONEncoding.default, headers: headerInsert).validate(statusCode: 200..<300).responseJSON { response in
let json = JSON(response.data!)
callback.done(JobCompletion.success)
print( "JSON AFTER SEND TO SERVER IS : \(json)")
}
}
func onRetry(error: Error) -> RetryConstraint {
return RetryConstraint.retry(delay: 0)
}
func onRemove(result: JobCompletion) {
switch result {
case .success:
print("job success")
break
case .fail(let erorr):
print(erorr)
break
}
}
}
class LocationCreator: JobCreator {
func create(type: String, params: [String : Any]?) -> Job {
if type == SendJobLocation.type{
print("location creat")
return SendJobLocation(Location: params!)
}else{
fatalError("No Job !")
}
}
}`
and in ViewController use this line of code
`if internetReachability.isReachable {
print("Internet Ok")
JobBuilder(type: SendJobLocation.type).internet(atLeast: .any).with(params: self.locationParameter).schedule(manager: managerQueue)
// self.SendLocation(parameter: self.locationParameter)
}else{
print("Internet Failed")
JobBuilder(type: SendJobLocation.type).internet(atLeast: .any).retry(limit: .limited(3)).with(params: self.locationParameter).schedule(manager: managerQueue)
}
when internet is running erveything is ok but when internet not reachable message not in queue and when internet comes back not dequeue ! how can this ?
thank for all
Create a function to start or pause execution. Will makes testing easier.
I install pod but not building.please help me
.periodic(count: -1, interval: 5*60)
won't run the job indefinitely
I am having to do:
.periodic(count: 9999, interval: 5*60)
I would like to create a job that based on a "retry-after" header from an HTTP request would periodically
I'm attempting to build a production version of an app which has SwiftQueue as a dependency. When I do so and attempt to validate it with iTunes Connect I receive an error that...
SwiftQueue framework is invalid. The Info.plist file is missing the required key: CFBundleVersion
In the SwiftQueue.xcodeproj/SwiftQueue_Info.plist
file CFBundleVersion
is mapped to a build variable called CURRENT_PROJECT_VERSION
. In the build settings it looks like CURRENT_PROJECT_VERSION
isn't actually set to anything which I assume isn't deliberate.
Am I misunderstanding or does that need to be added to the source?
Should be UserDefaultsPersister.swift
In the code and documentation both words are using and make the library inconsistent.
Hi, is it possible to expose the number of jobs in SwiftQueueManager?
public func count() -> Int {
return manage.values.count
}
I'm thinking about multiple Images uploading service with SwiftQueue.
For example, If I take three photos, three jobs would insert into queue like 0/3
.
As soon as I insert into queue, the first job will execute like 1/3
.
Then, in order to show the progress of jobs to User, I'd like to know jobs that keep and finished currently in SqOperationQueue.
If I get a job counts that keep and finished currently in SqOperationQueue, I can show the progress of jobs to User.
Please tell meπ
Thank you ahead of time.
DeadlineError and TaskAlreadyExist should be exposed
I wrote an SQLite3 based JobPersister class and it is available on Github at https://github.com/gumbright/SQLitePersisterForSwiftQueue if anyone is interested
Im getting a crash on the assert
in this function in the JobBuilder
class.
/// Custom parameters will be forwarded to create method
public func with(params: [String: Any]) -> Self {
assert(JSONSerialization.isValidJSONObject(params))
info.params = params
return self
}
Before moving to the new code I was passing things like delegates and callbacks to my job but since those are not valid JSON but conform to Any
it crashes on this assertion.
I'm not sure if this is intended or not but it does mention "Validate JSON argument" int the 1.4.0 change log so maybe. If it was intended, is there a better way to get callback blocks and updates from a job?
I wanted to cancel all the operations which is scheduled or in execution without the SwiftQueueManager reference.
is there any way?
Scenario: Image is being upload mean while i am logging out from my app, so i wanna cancel all the uploading operations.
Hey @lucas34 , your lib is awesome but your README lacks an installation section
I created this iOS README template to help you out π
Please fix this issue before we can approve the AwesomeiOS PR
Hello, i'm using SwiftQueue to createa upload/download queue in my app.
When i try to run the queue return allways the same image for all objects in que
Can you help me?
I have arround 500 images to upload but queue only run the same job for te 500 images.
What i'm doing wrong, im using the last version of pod.
`import Foundation
import SwiftQueue
import Crashlytics
import Alamofire
class MIImagesInQueueDownload: NSObject, NSCoding {
var url: String?
init(url: String?) {
self.url = url
}
func encode(with aCoder: NSCoder) {
aCoder.encode(url, forKey: "url")
}
required init(coder aDecoder: NSCoder) {
if let url = aDecoder.decodeObject(forKey: "url") as? String {
self.url = url
}
}
static func ==(lhs: MIImagesInQueueDownload, rhs: MIImagesInQueueDownload) -> Bool {
return lhs.url ?? "" == rhs.url ?? ""
}
}
class MIImagesInQueueUpload: NSObject, NSCoding {
var filePath: String = ""
var fileName: String = ""
var assignmentUUID: String = ""
var url:String = ""
init(filePath: String, fileName: String, assignmentUUID: String, url:String) {
self.fileName = fileName
self.filePath = filePath
self.assignmentUUID = assignmentUUID
self.url = url
}
func encode(with aCoder: NSCoder) {
aCoder.encode(filePath, forKey: "filePath")
aCoder.encode(fileName, forKey: "fileName")
aCoder.encode(assignmentUUID, forKey: "assignmentUUID")
aCoder.encode(url, forKey: "url")
}
required init(coder aDecoder: NSCoder) {
if let filename = aDecoder.decodeObject(forKey: "fileName") as? String {
self.fileName = filename
}
if let filePath = aDecoder.decodeObject(forKey: "filePath") as? String {
self.filePath = filePath
}
if let uuid = aDecoder.decodeObject(forKey: "assignmentUUID") as? String {
self.assignmentUUID = uuid
}
if let url = aDecoder.decodeObject(forKey: "url") as? String {
self.url = url
}
}
static func ==(lhs: MIImagesInQueueUpload, rhs: MIImagesInQueueUpload) -> Bool {
return lhs.fileName == rhs.fileName && lhs.assignmentUUID == rhs.assignmentUUID && lhs.filePath == rhs.filePath && lhs.url == rhs.url
}
}
public struct ImageError: Error {
let msg: String
}
public struct UploadError: Error {
let msg: String
}
extension ImageError: LocalizedError {
public var errorDescription: String? {
return NSLocalizedString(msg, comment: "")
}
}
extension UploadError: LocalizedError {
public var errorDescription: String? {
return NSLocalizedString(msg, comment: "")
}
}
class downloadOperation: Job {
let image: MIImagesInQueueDownload
static let type = "downloadImage"
required init(image: MIImagesInQueueDownload) {
self.image = image
}
func onRun(callback: JobResult) {
self.downloadImage(with: self.image) { (error) in
if let error = error {
callback.done(.fail(error))
} else {
callback.done(.success)
}
}
}
func onRetry(error: Error) -> RetryConstraint {
if error is UploadError {
return RetryConstraint.retry(delay: 3600)
} else {
return RetryConstraint.cancel
}
}
func onRemove(result: JobCompletion) {
// This job will never run anymore
switch result {
case .success:
// Job success
break
case .fail(let error):
Logger.shared.log(message: error.localizedDescription, vc: "ImageDownload JOB")
break
}
}
private func downloadImage(with image:MIImagesInQueueDownload, onComplete:@escaping onCompleteupload) {
guard let urlImage = image.url?.encodeURL() else { return }
MIImageQueue.shared.imageDownloader.downloadImage(with: urlImage, options: [], progressBlock: nil) { (image, error, url, data) in
Logger.shared.log(message: error?.localizedDescription ?? "", vc: "imageDowload")
guard let image = image else {
onComplete(ImageError(msg: "missing file at server"))
return
}
guard let url = url else {
onComplete(ImageError(msg: "missing file at server"))
return
}
MIHelper.shared.storeImage(image: image, fileURL: url.absoluteString, fileName: url.lastPathComponent, execute: { (imageDevice) in
onComplete(nil)
})
}
}
}
class uploadOperation: Job {
let image: MIImagesInQueueUpload
lazy var uploadQueue: DispatchQueue = {
var queue = DispatchQueue(label: "com.uscope.photoId.ImagesUpload",
qos: DispatchQoS.background,
attributes: .concurrent,
autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit,
target: DispatchQueue.global(qos: .background))
return queue
}()
static let type = "uploadImage"
required init(image: MIImagesInQueueUpload) {
self.image = image
print(image.filePath)
}
func onRun(callback: JobResult) {
print(image.fileName)
self.uploadImage(with: self.image) { (error) in
if let error = error {
callback.done(.fail(error))
} else {
callback.done(.success)
}
}
}
func onRetry(error: Error) -> RetryConstraint {
if error is UploadError || error is ImageError {
return RetryConstraint.retry(delay: 3600)
} else {
return RetryConstraint.cancel
}
}
func onRemove(result: JobCompletion) {
// This job will never run anymore
switch result {
case .success:
// Job success
break
case .fail(let error):
Logger.shared.log(message: error.localizedDescription, vc: "ImageUpload JOB")
break
}
}
fileprivate func uploadImage(with imageQueue:MIImagesInQueueUpload, onComplete:@escaping onCompleteupload){
let object = imageQueue
if let filePathURL = URL(string: imageQueue.filePath), let profileIdToSet = imageQueue.url.getProfileID() {
let filePath = filePathURL.imagesFolderPath()
if filePath.extists() {
let parameters:Parameters = ["filename": object.fileName, "sourceFileUri": filePath.absoluteString, "assignmentId": object.assignmentUUID, "profileId": profileIdToSet]
let parametersLog:[String:String] = ["filename": object.fileName, "assignmentId": object.assignmentUUID, "profileId": profileIdToSet]
print(parameters)
Answers.logCustomEvent(withName: "image upload", customAttributes: parametersLog)
MIConnnection.shared.alamofireManager.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(filePath, withName: "bill", fileName: object.fileName.isEmpty ? filePath.getImageName() : object.fileName, mimeType: "image/jpeg")
}, with: APINetworkRouter.upload(parameters: parameters), encodingCompletion: { [weak self] (result) in
guard let strongSelf = self else { return }
switch result {
case .success(let upload, _, _):
upload.uploadProgress(closure: { (progress) in
Logger.shared.log(message: "Upload progress \(progress.fractionCompleted)", vc: String(describing: self))
})
upload.customValidation().responseJSON(queue: strongSelf.uploadQueue, completionHandler: { (response) in
switch response.result {
case .success(let responseJSON):
guard let responseObject = responseJSON as? DefaultDict else { return }
guard let success = responseObject["success"]?.boolValue else { return }
guard let message = responseObject["message"] as? String else { return }
if success {
Answers.logCustomEvent(withName: "image upload success", customAttributes: ["success" : success, "message": message])
let objectNotification = ["url": imageQueue.url.getImageNameWhitoutURL(), "assignmnentId" : object.assignmentUUID]
NotificationCenter.default.post(name: Notifications.didChangeUpload, object: nil, userInfo:objectNotification)
DispatchQueue.main.async {
onComplete(nil)
}
} else {
DispatchQueue.main.async {
onComplete(UploadError(msg: message))
}
Answers.logCustomEvent(withName: "image upload fail", customAttributes: ["success" : success, "message": message])
return
}
case .failure(let error):
if isDebug {
error.manageAlamofireError()
}
DispatchQueue.main.async {
onComplete(UploadError(msg: error.localizedDescription))
}
return
}
})
case .failure(let error):
if isDebug {
error.manageAlamofireError()
}
onComplete(error)
return
}
})
} else {
DispatchQueue.main.async {
onComplete(ImageError(msg: "missing file at disk"))
}
Answers.logCustomEvent(withName: "image upload fail", customAttributes: ["error" : "missing file at disk"])
}
}
}
}
class UploaderDownloaderManager {
static let shared:UploaderDownloaderManager = UploaderDownloaderManager()
let queueManager = SwiftQueueManagerBuilder(creator: mainJobCreator()).set(logger: ConsoleLogger()).build()
func uploadImage(with imageQueue:MIImagesInQueueUpload) {
if let user = MIFirebaseDatabaseManager.shared.userApp {
JobBuilder(type: uploadOperation.type)
// Prevent adding the same job multiple times
.singleInstance(forId: imageQueue.url)
// Different group name will run in parallel
// But one by one for a group
.group(name: "uploadPicture")
// Sending tweet will require internet. At least cellular.
// Will also be executed if connected to wifi.
.internet(atLeast: user.cellarSync ? .any : .wifi)
// Content of your tweet. Can be a class, struct or anything
.with(params: ["fileURI" : imageQueue.url, "filePath" : imageQueue.filePath, "fileName" : imageQueue.fileName, "assignmentId" : imageQueue.assignmentUUID, "profileId": user.userID])
// unlimited retry by default
.retry(limit: .limited(10))
// set persist by default
.persist(required: true)
// set periodic by default
// .periodic()
// Cancel the job if it's not completed after one week
.deadline(date: Date(timeIntervalSinceNow: 604_800))
// Insert to queue
.schedule(manager: self.queueManager)
}
}
func downloadImage(with imageQueue:MIImagesInQueueDownload) {
if let user = MIFirebaseDatabaseManager.shared.userApp {
JobBuilder(type: downloadOperation.type)
// Prevent adding the same job multiple times
.singleInstance(forId: imageQueue.url ?? "")
// Different group name will run in parallel
// But one by one for a group
.group(name: "downloadPicture")
// Sending tweet will require internet. At least cellular.
// Will also be executed if connected to wifi.
.internet(atLeast: user.cellarSync ? .any : .wifi)
// Content of your tweet. Can be a class, struct or anything
.with(params: ["fileURI" : imageQueue.url ?? ""])
// unlimited retry by default
.retry(limit: .limited(5))
// set persist by default
.persist(required: true)
// set periodic by default
.periodic()
// Cancel the job if it's not completed after 24 hours
.deadline(date: Date(timeIntervalSinceNow: 86_400))
// Insert to queue
.schedule(manager: self.queueManager)
}
}
func downloadFromURL(with url:URL) {
if let user = MIFirebaseDatabaseManager.shared.userApp {
JobBuilder(type: downloadOperation.type)
// Prevent adding the same job multiple times
.singleInstance(forId: url.absoluteString)
// Different group name will run in parallel
// But one by one for a group
.group(name: "downloadPicture")
// Sending tweet will require internet. At least cellular.
// Will also be executed if connected to wifi.
.internet(atLeast: user.cellarSync ? .any : .wifi)
// Content of your tweet. Can be a class, struct or anything
.with(params: ["fileURI" : url.absoluteString])
// unlimited retry by default
.retry(limit: .limited(5))
// set persist by default
.persist(required: true)
// set periodic by default
.periodic()
// Cancel the job if it's not completed after 24 hours
.deadline(date: Date(timeIntervalSinceNow: 86_400))
// Insert to queue
.schedule(manager: self.queueManager)
}
}
}
class mainJobCreator: JobCreator {
func create(type: String, params: [String : Any]?) -> Job {
// check for job and params type
if type == downloadOperation.type {
return downloadOperation(image: MIImagesInQueueDownload(url: params?["fileURI"] as? String ?? ""))
} else if type == uploadOperation.type {
return uploadOperation(image: MIImagesInQueueUpload(filePath: params?["filePath"] as? String ?? "", fileName: params?["fileName"] as? String ?? "", assignmentUUID: params?["assignmentId"] as? String ?? "", url: params?["fileURI"] as? String ?? ""))
} else {
fatalError("No Job !")
}
}
}
`
I would like to create a new release that includes commit:
7758c84
Update project structure to follow swift package manager rule.
Release lib on this platform
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.