├── image ├── logo.png └── logo.sketch ├── SYNQueue ├── SYNQueue.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── xcbaselines │ │ │ └── 9F6202351B333AAF0026CE2C.xcbaseline │ │ │ │ ├── 24523CEE-5CA3-4AAB-A970-50D68B159D1F.plist │ │ │ │ └── Info.plist │ │ └── xcschemes │ │ │ ├── SYNQueueTests.xcscheme │ │ │ └── SYNQueue.xcscheme │ └── project.pbxproj ├── SYNQueue │ ├── SYNQueue.h │ ├── Info.plist │ ├── Utils.swift │ ├── NSDate+Utils.swift │ ├── SYNQueue.swift │ └── SYNQueueTask.swift └── SYNQueueTests │ ├── ConsoleLogger.swift │ ├── Info.plist │ ├── NSUserDefaultsSerializer.swift │ └── SYNQueueTests.swift ├── .travis.yml ├── SYNQueue.xcworkspace └── contents.xcworkspacedata ├── SYNQueueDemo ├── SYNQueueDemo │ ├── ConsoleLogger.swift │ ├── TaskCell.swift │ ├── Utils.swift │ ├── SettingsViewController.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── NSUserDefaultsSerializer.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── ViewController.swift │ └── Main.storyboard ├── SYNQueueDemoTests │ ├── Info.plist │ └── SYNQueueDemoTests.swift └── SYNQueueDemo.xcodeproj │ └── project.pbxproj ├── SYNQueue.podspec ├── .gitignore ├── LICENSE └── README.md /image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THREDOpenSource/SYNQueue/HEAD/image/logo.png -------------------------------------------------------------------------------- /image/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/THREDOpenSource/SYNQueue/HEAD/image/logo.sketch -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7 3 | xcode_workspace: SYNQueue.xcworkspace 4 | xcode_scheme: SYNQueueTests 5 | 6 | script: 7 | - xctool test -workspace SYNQueue.xcworkspace -scheme SYNQueueTests -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -------------------------------------------------------------------------------- /SYNQueue.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/SYNQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYNQueue.h 3 | // SYNQueue 4 | // 5 | 6 | #import 7 | 8 | //! Project version number for SYNQueue. 9 | FOUNDATION_EXPORT double SYNQueueVersionNumber; 10 | 11 | //! Project version string for SYNQueue. 12 | FOUNDATION_EXPORT const unsigned char SYNQueueVersionString[]; 13 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueueTests/ConsoleLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConsoleLogger.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import Foundation 7 | import SYNQueue 8 | 9 | func log(level: LogLevel, _ msg: String) { 10 | return ConsoleLogger.log(level, msg) 11 | } 12 | 13 | 14 | class ConsoleLogger : SYNQueueLogProvider { 15 | // MARK: - SYNQueueLogProvider Delegates 16 | 17 | @objc func log(level: LogLevel, _ msg: String) { 18 | return ConsoleLogger.log(level, msg) 19 | } 20 | 21 | class func log(level: LogLevel, _ msg: String) { 22 | runOnMainThread { print("[\(level.toString().uppercaseString)] \(msg)") } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/ConsoleLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConsoleLogger.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import Foundation 7 | import SYNQueue 8 | 9 | func log(level: LogLevel, _ msg: String) { 10 | return ConsoleLogger.log(level, msg) 11 | } 12 | 13 | 14 | class ConsoleLogger : SYNQueueLogProvider { 15 | // MARK: - SYNQueueLogProvider Delegates 16 | 17 | @objc func log(level: LogLevel, _ msg: String) { 18 | return ConsoleLogger.log(level, msg) 19 | } 20 | 21 | class func log(level: LogLevel, _ msg: String) { 22 | runOnMainThread { print("[\(level.toString().uppercaseString)] \(msg)") } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SYNQueue.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = "SYNQueue" 4 | s.version = "0.2.2" 5 | s.summary = "SYNQueue" 6 | s.description = "A simple yet powerful queueing system for iOS (with persistence)" 7 | s.homepage = "https://github.com/THREDOpenSource/SYNQueue" 8 | s.license = 'MIT' 9 | s.author = { "John Hurliman" => "johnh@thredhq.com", "Sidhant Gandhi" => "sidhant.gandhi@gmail.com" } 10 | s.source = { :git => "https://github.com/THREDOpenSource/SYNQueue.git", :tag => s.version.to_s } 11 | 12 | s.platform = :ios, '8.0' 13 | s.requires_arc = true 14 | 15 | s.source_files = 'SYNQueue/SYNQueue/**.swift' 16 | end 17 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/xcshareddata/xcbaselines/9F6202351B333AAF0026CE2C.xcbaseline/24523CEE-5CA3-4AAB-A970-50D68B159D1F.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | SYNQueueTests 8 | 9 | testEnqueuingPerformance() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.00045437 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/TaskCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskCell.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import UIKit 7 | import SYNQueue 8 | 9 | class TaskCell : UICollectionViewCell { 10 | @IBOutlet weak var succeedButton: UIButton! 11 | @IBOutlet weak var failButton: UIButton! 12 | @IBOutlet weak var nameLabel: UILabel! 13 | 14 | weak var task: SYNQueueTask? = nil 15 | 16 | @IBAction func succeedTapped(sender: UIButton) { 17 | if let task = task { 18 | task.completed(nil) 19 | } 20 | } 21 | 22 | @IBAction func failTapped(sender: UIButton) { 23 | if let task = task { 24 | let err = error("User tapped Fail on task \(task.taskID)") 25 | task.completed(err) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | 35 | .DS_Store 36 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueueTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.SYNQueue.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.SYNQueue.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.SYNQueue.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.2.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemoTests/SYNQueueDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYNQueueDemoTests.swift 3 | // SYNQueueDemoTests 4 | // 5 | 6 | import UIKit 7 | import XCTest 8 | 9 | class SYNQueueDemoTests: XCTestCase { 10 | 11 | override func setUp() { 12 | super.setUp() 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | } 15 | 16 | override func tearDown() { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | super.tearDown() 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | XCTAssert(true, "Pass") 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | self.measureBlock() { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import Foundation 7 | import SYNQueue 8 | 9 | func arrayMax(array: [T]) -> T? { 10 | return array.reduce(array.first) { return $0 > $1 ? $0 : $1 } 11 | } 12 | 13 | func findIndex(array: [T], _ valueToFind: T) -> Int? { 14 | for (index, value) in array.enumerate() { 15 | if value == valueToFind { 16 | return index 17 | } 18 | } 19 | return nil 20 | } 21 | 22 | func runOnMainThread(callback:dispatch_block_t) { 23 | dispatch_async(dispatch_get_main_queue(), callback) 24 | } 25 | 26 | func runOnMainThreadAfterDelay(delay:Double, _ callback:()->()) { 27 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in 28 | callback() 29 | }) 30 | } 31 | 32 | func error(msg: String) -> NSError { 33 | return NSError(domain: "Error", code: -1, userInfo: [NSLocalizedDescriptionKey: msg]) 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import UIKit 7 | 8 | let kAddDependencySettingKey = "settings.addDependency" 9 | let kAutocompleteTaskSettingKey = "settings.autocompleteTask" 10 | 11 | class SettingsViewController: UITableViewController { 12 | 13 | @IBOutlet weak var autocompleteTaskSwitch: UISwitch! 14 | @IBOutlet weak var dependencySwitch: UISwitch! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | self.dependencySwitch.on = NSUserDefaults.standardUserDefaults().boolForKey(kAddDependencySettingKey) 20 | self.autocompleteTaskSwitch.on = NSUserDefaults.standardUserDefaults().boolForKey(kAutocompleteTaskSettingKey) 21 | } 22 | 23 | @IBAction func addDependencySwitchToggled(sender: UISwitch) { 24 | NSUserDefaults.standardUserDefaults().setBool(sender.on, forKey: kAddDependencySettingKey) 25 | } 26 | 27 | @IBAction func autocompleteTaskSwitchToggled(sender: UISwitch) { 28 | NSUserDefaults.standardUserDefaults().setBool(sender.on, forKey: kAutocompleteTaskSettingKey) 29 | } 30 | } -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // SYNQueue 4 | // 5 | 6 | import Foundation 7 | 8 | func runInBackgroundAfter(seconds: NSTimeInterval, callback:dispatch_block_t) { 9 | let delta = dispatch_time(DISPATCH_TIME_NOW, Int64(seconds) * Int64(NSEC_PER_SEC)) 10 | dispatch_after(delta, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), callback) 11 | } 12 | 13 | func synced(lock: AnyObject, closure: () -> ()) { 14 | objc_sync_enter(lock) 15 | closure() 16 | objc_sync_exit(lock) 17 | } 18 | 19 | func runOnMainThread(callback:dispatch_block_t) { 20 | dispatch_async(dispatch_get_main_queue(), callback) 21 | } 22 | 23 | func toJSON(obj: AnyObject) throws -> String? { 24 | let json = try NSJSONSerialization.dataWithJSONObject(obj, options: []) 25 | return NSString(data: json, encoding: NSUTF8StringEncoding) as String? 26 | } 27 | 28 | func fromJSON(str: String) throws -> AnyObject? { 29 | if let json = str.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { 30 | let obj: AnyObject = try NSJSONSerialization.JSONObjectWithData(json, options: .AllowFragments) 31 | return obj 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/xcshareddata/xcbaselines/9F6202351B333AAF0026CE2C.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 24523CEE-5CA3-4AAB-A970-50D68B159D1F 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 1700 19 | logicalCPUCoresPerPackage 20 | 4 21 | modelCode 22 | MacBookAir6,2 23 | physicalCPUCoresPerPackage 24 | 2 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | i386 30 | targetDevice 31 | 32 | modelCode 33 | iPhone5,1 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/NSDate+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Utils.swift 3 | // SYNQueue 4 | // 5 | 6 | import Foundation 7 | 8 | class ISOFormatter : NSDateFormatter { 9 | override init() { 10 | super.init() 11 | self.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z" 12 | self.timeZone = NSTimeZone(forSecondsFromGMT: 0) 13 | self.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)! 14 | self.locale = NSLocale(localeIdentifier: "en_US_POSIX") 15 | } 16 | 17 | required init?(coder aDecoder: NSCoder) { 18 | super.init(coder: aDecoder) 19 | } 20 | } 21 | 22 | extension NSDate { 23 | convenience init?(dateString:String) { 24 | let formatter = NSDateFormatter() 25 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z" 26 | formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") 27 | if let d = formatter.dateFromString(dateString) { 28 | self.init(timeInterval:0, sinceDate:d) 29 | } else { 30 | self.init(timeInterval:0, sinceDate:NSDate()) 31 | return nil 32 | } 33 | } 34 | 35 | var isoFormatter: ISOFormatter { 36 | if let formatter = objc_getAssociatedObject(self, "formatter") as? ISOFormatter { 37 | return formatter 38 | } else { 39 | let formatter = ISOFormatter() 40 | objc_setAssociatedObject(self, "formatter", formatter, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 41 | return formatter 42 | } 43 | } 44 | 45 | func toISOString() -> String { 46 | return self.isoFormatter.stringFromDate(self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.SYNQueue.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import UIKit 7 | 8 | @UIApplicationMain 9 | class AppDelegate: UIResponder, UIApplicationDelegate { 10 | 11 | var window: UIWindow? 12 | 13 | 14 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | func applicationWillResignActive(application: UIApplication) { 20 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 21 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 22 | } 23 | 24 | func applicationDidEnterBackground(application: UIApplication) { 25 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 26 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 27 | } 28 | 29 | func applicationWillEnterForeground(application: UIApplication) { 30 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 31 | } 32 | 33 | func applicationDidBecomeActive(application: UIApplication) { 34 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 35 | } 36 | 37 | func applicationWillTerminate(application: UIApplication) { 38 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/xcshareddata/xcschemes/SYNQueueTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueueTests/NSUserDefaultsSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaultsSerializer.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import Foundation 7 | import SYNQueue 8 | 9 | 10 | class NSUserDefaultsSerializer : SYNQueueSerializationProvider { 11 | // MARK: - SYNQueueSerializationProvider Methods 12 | 13 | @objc func serializeTask(task: SYNQueueTask, queueName: String) { 14 | if let serialized = task.toJSONString() { 15 | let defaults = NSUserDefaults.standardUserDefaults() 16 | var stringArray: [String] 17 | 18 | if let curStringArray = defaults.stringArrayForKey(queueName) { 19 | stringArray = curStringArray 20 | stringArray.append(serialized) 21 | } else { 22 | stringArray = [serialized] 23 | } 24 | 25 | defaults.setValue(stringArray, forKey: queueName) 26 | } else { 27 | log(.Error, "Failed to serialize task \(task.taskID) in queue \(queueName)") 28 | } 29 | } 30 | 31 | @objc func deserializeTasksInQueue(queue: SYNQueue) -> [SYNQueueTask] { 32 | let defaults = NSUserDefaults.standardUserDefaults() 33 | if let queueName = queue.name, 34 | let stringArray = defaults.stringArrayForKey(queueName) 35 | { 36 | return stringArray 37 | .map { return SYNQueueTask(json: $0, queue: queue) } 38 | .filter { return $0 != nil } 39 | .map { return $0! } 40 | } 41 | 42 | return [] 43 | } 44 | 45 | @objc func removeTask(taskID: String, queue: SYNQueue) { 46 | if let queueName = queue.name { 47 | var curArray: [SYNQueueTask] = deserializeTasksInQueue(queue) 48 | curArray = curArray.filter { return $0.taskID != taskID } 49 | 50 | let stringArray = curArray 51 | .map { return $0.toJSONString() } 52 | .filter { return $0 != nil } 53 | .map { return $0! } 54 | 55 | let defaults = NSUserDefaults.standardUserDefaults() 56 | defaults.setValue(stringArray, forKey: queueName) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/NSUserDefaultsSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaultsSerializer.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import Foundation 7 | import SYNQueue 8 | 9 | 10 | class NSUserDefaultsSerializer : SYNQueueSerializationProvider { 11 | // MARK: - SYNQueueSerializationProvider Methods 12 | 13 | @objc func serializeTask(task: SYNQueueTask, queueName: String) { 14 | if let serialized = task.toJSONString() { 15 | let defaults = NSUserDefaults.standardUserDefaults() 16 | var stringArray: [String] 17 | 18 | if let curStringArray = defaults.stringArrayForKey(queueName) { 19 | stringArray = curStringArray 20 | stringArray.append(serialized) 21 | } else { 22 | stringArray = [serialized] 23 | } 24 | 25 | defaults.setValue(stringArray, forKey: queueName) 26 | } else { 27 | log(.Error, "Failed to serialize task \(task.taskID) in queue \(queueName)") 28 | } 29 | } 30 | 31 | @objc func deserializeTasksInQueue(queue: SYNQueue) -> [SYNQueueTask] { 32 | let defaults = NSUserDefaults.standardUserDefaults() 33 | if let queueName = queue.name, 34 | let stringArray = defaults.stringArrayForKey(queueName) 35 | { 36 | return stringArray 37 | .map { return SYNQueueTask(json: $0, queue: queue) } 38 | .filter { return $0 != nil } 39 | .map { return $0! } 40 | } 41 | 42 | return [] 43 | } 44 | 45 | @objc func removeTask(taskID: String, queue: SYNQueue) { 46 | if let queueName = queue.name { 47 | var curArray: [SYNQueueTask] = deserializeTasksInQueue(queue) 48 | curArray = curArray.filter { return $0.taskID != taskID } 49 | 50 | let stringArray = curArray 51 | .map { return $0.toJSONString() } 52 | .filter { return $0 != nil } 53 | .map { return $0! } 54 | 55 | let defaults = NSUserDefaults.standardUserDefaults() 56 | defaults.setValue(stringArray, forKey: queueName) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | A simple yet powerful queueing system for iOS (with persistence). 6 |
7 |
8 | 9 | 10 |
11 | 12 | ## Overview 13 | 14 | `SYNQueue` is a subclass of `NSOperationQueue` so you get: 15 | 16 | - Serial or concurrent queues 17 | - Task priority 18 | - Multiple queues 19 | - Dependencies 20 | - KVC/KVO 21 | - Thread safety 22 | 23 | **But it goes beyond `NSOperationQueue` and `NSOperation` to offer:** 24 | 25 | - Task persistence (via protocol) 26 | - Queue specific logging (via protocol) 27 | - Retries (exponential back-off) 28 | 29 | ## Motivation 30 | With a good queuing solution you can provide a much better user experience in areas such as: 31 | 32 | - Web requests 33 | - Saving/creating content (images, video, audio) 34 | - Uploading data 35 | 36 | ## Architecture 37 | When we started building `SYNQueue`, persistence was the most important feature for us since we hadn't seen a good generic implementation of it anywhere. With that in mind, we designed each `SYNQueueTask` (`:NSOperation`) to hold just metadata about the task rather than code itself. 38 | 39 | The actual code to perform the task gets passed to the queue in the form of a `taskHandler` closure. Each `SYNQueueTask` must have a `taskType` key which corresponds to a specific `taskHandler`. 40 | 41 | ## Example Code 42 | For a thorough example see the demo project in the top level of the repository. 43 | 44 | ### Create a queue 45 | ```swift 46 | let queue = SYNQueue(queueName: "myQueue", maxConcurrency: 2, maxRetries: 3, 47 | logProvider: ConsoleLogger(), serializationProvider: NSUserDefaultsSerializer(), 48 | completionBlock: { [weak self] in self?.taskComplete($0, $1) }) 49 | ``` 50 | 51 | >The `logProvider` and `serializationProvider` must conform to the `SYNQueueLogProvider` and `SYNQueueSerializationProvider` protocols respectively. 52 | > 53 | >See [NSUserDefaultsSerializer.swift](SYNQueueDemo/SYNQueueDemo/NSUserDefaultsSerializer.swift) and [ConsoleLogger.swift](SYNQueueDemo/SYNQueueDemo/ConsoleLogger.swift) for example implementations. 54 | > 55 | >The `completionBlock` is the block to run when a task in the queue completes (success or failure). 56 | 57 | ### Create a task 58 | ```swift 59 | let t1 = SYNQueueTask(queue: queue, taskID: "1234", taskType: "uploadPhoto", dependencyStrs: [], data: [:]) 60 | ``` 61 | 62 | >The `queue` is the queue you will add the task to. `taskID` is a unique ID for the task. `taskType` is the generic type of task to perform. Each `taskType` will have its own `taskHandler`. `data` is any data your task will need to perform its job. 63 | 64 | ### Add dependencies 65 | ```swift 66 | let t2 = SYNQueueTask(queue: queue, taskID: "5678", taskType: "submitForm", dependencyStrs: [], data: [:]) 67 | t2.addDependency(t1) 68 | ``` 69 | 70 | ### Add it to the queue 71 | ```swift 72 | queue.addOperation(t2) 73 | queue.addOperation(t1) 74 | ``` 75 | 76 | >Notice that even though we add task `t2` to the queue first, it will not execute until its dependency, `t1` has finished executing.` 77 | 78 | ## An important note on persistence 79 | You may have realized that you are free to serialize tasks however you like through the `SYNQueueSerializationProvider` protocol. **The one caveat is that all tasks must be idempotent.** That is, even if called multiple times, the outcome of the task should be the same. For example: `x = 1` is idempotent, `x++` is not. 80 | 81 | Hopefully this makes sense given that a serialized task may get interrupted before it finishes, and when we deserialize this task we will run it again. We only remove the serialized task after it has completed (success or failure). 82 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueueTests/SYNQueueTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYNQueueTests.swift 3 | // SYNQueueTests 4 | // 5 | 6 | import UIKit 7 | import XCTest 8 | @testable import SYNQueue 9 | 10 | class SYNQueueTests: XCTestCase { 11 | 12 | var logger = ConsoleLogger() 13 | var serializer = NSUserDefaultsSerializer() 14 | let testTaskType = "testTaskType" 15 | 16 | override func setUp() { 17 | super.setUp() 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | 20 | logger = ConsoleLogger() 21 | serializer = NSUserDefaultsSerializer() 22 | 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | super.tearDown() 28 | } 29 | 30 | func testInitialization() { 31 | let name = randomQueueName() 32 | let queue = SYNQueue(queueName: name, maxConcurrency: 3, maxRetries: 2, logProvider: logger, serializationProvider: serializer) { (error: NSError?, task: SYNQueueTask) -> Void in 33 | // 34 | } 35 | XCTAssertNotNil(queue) 36 | XCTAssert(queue.name == name) 37 | XCTAssert(queue.maxConcurrentOperationCount == 3) 38 | XCTAssert(queue.maxRetries == 2) 39 | XCTAssertNotNil(queue.logProvider) 40 | XCTAssertNotNil(queue.serializationProvider) 41 | XCTAssertNotNil(queue.completionBlock) 42 | } 43 | 44 | func testTaskCompletion() { 45 | 46 | let taskCompletionExpectation = expectationWithDescription("taskCompletion") 47 | 48 | let queue = SYNQueue(queueName: randomQueueName(), maxConcurrency: 3, maxRetries: 2, logProvider: logger, serializationProvider: serializer) { (error: NSError?, task: SYNQueueTask) -> Void in 49 | taskCompletionExpectation.fulfill() 50 | } 51 | 52 | queue.addTaskHandler(testTaskType) { $0.completed(nil) } 53 | let task = SYNQueueTask(queue: queue, taskType: testTaskType) 54 | queue.addOperation(task) 55 | 56 | XCTAssert(queue.operationCount == 1) 57 | 58 | waitForExpectationsWithTimeout(5, handler: { error in 59 | XCTAssertNil(error, "Error") 60 | }) 61 | } 62 | 63 | func testSerialization() { 64 | let name = randomQueueName() 65 | 66 | // Creating a queue 67 | var queue: SYNQueue? = SYNQueue(queueName: name, maxConcurrency: 3, maxRetries: 2, logProvider: logger, serializationProvider: serializer) { (error: NSError?, task: SYNQueueTask) -> Void in 68 | // 69 | } 70 | 71 | // Add a task to the queue 72 | queue!.addTaskHandler(testTaskType) { 73 | NSThread.sleepForTimeInterval(2) 74 | $0.completed(nil) 75 | } 76 | let task = SYNQueueTask(queue: queue!, taskType: testTaskType) 77 | queue!.addOperation(task) 78 | 79 | // Nil out the queue to simulate app backgrounded or quit 80 | queue = nil 81 | 82 | // Now create a new queue (with the same name) and load serialized tasks 83 | let queue2 = SYNQueue(queueName: name, maxConcurrency: 3, maxRetries: 2, logProvider: logger, serializationProvider: serializer) { (error: NSError?, task: SYNQueueTask) -> Void in 84 | // 85 | } 86 | queue2.loadSerializedTasks() 87 | XCTAssert(queue2.operationCount == 1) 88 | 89 | let serializedTasks = queue2.serializationProvider?.deserializeTasksInQueue(queue2) 90 | serializedTasks?.forEach({ (task: SYNQueueTask) -> () in 91 | queue2.serializationProvider?.removeTask(task.taskID, queue: queue2) 92 | }) 93 | } 94 | 95 | func testEnqueuingPerformance() { 96 | let queue = SYNQueue(queueName: "testQueue", maxConcurrency: 3, maxRetries: 2, logProvider: logger, serializationProvider: serializer) { (error: NSError?, task: SYNQueueTask) -> Void in 97 | // 98 | } 99 | 100 | queue.addTaskHandler(testTaskType) { 101 | NSThread.sleepForTimeInterval(1) 102 | $0.completed(nil) 103 | } 104 | let task = SYNQueueTask(queue: queue, taskType: testTaskType) 105 | 106 | self.measureBlock() { 107 | queue.addOperation(task) 108 | } 109 | } 110 | } 111 | 112 | // MARK: Helper methods 113 | func randomQueueName() -> String { 114 | return NSUUID().UUIDString 115 | } 116 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/xcshareddata/xcschemes/SYNQueue.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SYNQueueDemo 4 | // 5 | 6 | import UIKit 7 | import SYNQueue 8 | 9 | class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 10 | @IBOutlet weak var progressView: UIProgressView! 11 | @IBOutlet weak var collectionView: UICollectionView! 12 | 13 | var totalTasksSeen = 0 14 | var nextTaskID = 1 15 | lazy var queue: SYNQueue = { 16 | return SYNQueue(queueName: "myQueue", maxConcurrency: 2, maxRetries: 3, 17 | logProvider: ConsoleLogger(), serializationProvider: NSUserDefaultsSerializer(), 18 | completionBlock: { [weak self] in self?.taskComplete($0, $1) }) 19 | }() 20 | 21 | // MARK: - UIViewController Overrides 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | queue.addTaskHandler("cellTask", taskHandler: taskHandler) 27 | queue.loadSerializedTasks() 28 | 29 | let taskIDs = queue.operations 30 | .map { return $0 as! SYNQueueTask } 31 | .map { return Int($0.taskID) ?? 0 } 32 | nextTaskID = (arrayMax(taskIDs) ?? 0) + 1 33 | } 34 | 35 | override func viewDidLayoutSubviews() { 36 | if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { 37 | flowLayout.itemSize = CGSizeMake(collectionView.bounds.size.width, 50) 38 | } 39 | } 40 | 41 | override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) { 42 | collectionView.performBatchUpdates(nil, completion: nil) 43 | } 44 | 45 | // MARK: - SYNQueueTask Handling 46 | 47 | func taskHandler(task: SYNQueueTask) { 48 | // NOTE: Tasks are not actually handled here like usual since task 49 | // completion in this example is based on user interaction, unless 50 | // we enable the setting for task autocompletion 51 | 52 | log(.Info, "Running task \(task.taskID)") 53 | 54 | // Do something with data and call task.completed() when done 55 | // let data = task.data 56 | 57 | // Here, for example, we just auto complete the task 58 | let taskShouldAutocomplete = NSUserDefaults.standardUserDefaults().boolForKey(kAutocompleteTaskSettingKey) 59 | if taskShouldAutocomplete { 60 | // Set task completion after 3 seconds 61 | runOnMainThreadAfterDelay(3, { () -> () in 62 | task.completed(nil) 63 | }) 64 | } 65 | 66 | runOnMainThread { self.collectionView.reloadData() } 67 | 68 | } 69 | 70 | func taskComplete(error: NSError?, _ task: SYNQueueTask) { 71 | if let error = error { 72 | log(.Error, "Task \(task.taskID) failed with error: \(error)") 73 | } else { 74 | log(.Info, "Task \(task.taskID) completed successfully") 75 | } 76 | 77 | if queue.operationCount == 0 { 78 | nextTaskID = 1 79 | totalTasksSeen = 0 80 | } 81 | 82 | updateProgress() 83 | 84 | runOnMainThread { self.collectionView.reloadData() } 85 | } 86 | 87 | // MARK: - UICollectionView Delegates 88 | 89 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection 90 | section: Int) -> Int 91 | { 92 | return queue.operationCount 93 | } 94 | 95 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath 96 | indexPath: NSIndexPath) -> UICollectionViewCell 97 | { 98 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier( 99 | "TaskCell", forIndexPath: indexPath) as! TaskCell 100 | cell.backgroundColor = UIColor.redColor() 101 | 102 | if let task = queue.operations[indexPath.item] as? SYNQueueTask { 103 | cell.task = task 104 | cell.nameLabel.text = "task \(task.taskID)" 105 | let taskShouldAutocomplete = NSUserDefaults.standardUserDefaults().boolForKey(kAutocompleteTaskSettingKey) 106 | if task.executing && !taskShouldAutocomplete { 107 | cell.backgroundColor = UIColor.blueColor() 108 | cell.failButton.enabled = true 109 | cell.succeedButton.enabled = true 110 | } else { 111 | cell.backgroundColor = UIColor.grayColor() 112 | cell.succeedButton.enabled = false 113 | cell.failButton.enabled = false 114 | } 115 | } 116 | 117 | return cell 118 | } 119 | 120 | // MARK: - IBActions 121 | 122 | @IBAction func addTapped(sender: UIButton) { 123 | let taskID1 = nextTaskID++ 124 | let task1 = SYNQueueTask(queue: queue, taskID: String(taskID1), 125 | taskType: "cellTask", dependencyStrs: [], data: [:]) 126 | 127 | let shouldAddDependency = NSUserDefaults.standardUserDefaults().boolForKey(kAddDependencySettingKey) 128 | if shouldAddDependency { 129 | let taskID2 = nextTaskID++ 130 | let task2 = SYNQueueTask(queue: queue, taskID: String(taskID2), 131 | taskType: "cellTask", dependencyStrs: [], data: [:]) 132 | 133 | // Make the first task dependent on the second 134 | task1.addDependency(task2) 135 | queue.addOperation(task2) 136 | } 137 | 138 | queue.addOperation(task1) 139 | totalTasksSeen = max(totalTasksSeen, queue.operationCount) 140 | updateProgress() 141 | 142 | collectionView.reloadData() 143 | } 144 | 145 | @IBAction func removeTapped(sender: UIButton) { 146 | // Find the first task in the list 147 | if let task = queue.operations.first as? SYNQueueTask { 148 | log(.Info, "Removing task \(task.taskID)") 149 | 150 | task.cancel() 151 | 152 | collectionView.reloadData() 153 | } 154 | } 155 | 156 | // MARK: - Helpers 157 | 158 | func updateProgress() { 159 | let tasks = queue.tasks 160 | let progress = Double(totalTasksSeen - tasks.count) / Double(totalTasksSeen) 161 | 162 | runOnMainThread { self.progressView.progress = Float(progress) } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/SYNQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYNQueue.swift 3 | // SYNQueue 4 | // 5 | 6 | import Foundation 7 | 8 | /** 9 | Log level for use in the SYNQueueLogProvider `log()` call 10 | 11 | - Trace: "Trace" 12 | - Debug: "Debug" 13 | - Info: "Info" 14 | - Warning: "Warning" 15 | - Error: "Error" 16 | */ 17 | @objc 18 | public enum LogLevel: Int { 19 | case Trace = 0 20 | case Debug = 1 21 | case Info = 2 22 | case Warning = 3 23 | case Error = 4 24 | 25 | public func toString() -> String { 26 | switch (self) { 27 | case .Trace: return "Trace" 28 | case .Debug: return "Debug" 29 | case .Info: return "Info" 30 | case .Warning: return "Warning" 31 | case .Error: return "Error" 32 | } 33 | } 34 | } 35 | 36 | /** 37 | * Conform to this protocol to provide logging to SYNQueue 38 | */ 39 | @objc 40 | public protocol SYNQueueLogProvider { 41 | func log(level: LogLevel, _ msg: String) 42 | } 43 | 44 | /** 45 | * Conform to this protocol to provide serialization (persistence) to SYNQueue 46 | */ 47 | @objc 48 | public protocol SYNQueueSerializationProvider { 49 | func serializeTask(task: SYNQueueTask, queueName: String) 50 | func deserializeTasksInQueue(queue: SYNQueue) -> [SYNQueueTask] 51 | func removeTask(taskID: String, queue: SYNQueue) 52 | } 53 | 54 | /** 55 | * SYNQueue is a generic queue with customizable serialization, logging, task handling, retries, and concurrency behavior 56 | */ 57 | @objc 58 | public class SYNQueue : NSOperationQueue { 59 | 60 | /// The maximum number of times a task will be retried if it fails 61 | public let maxRetries: Int 62 | 63 | let serializationProvider: SYNQueueSerializationProvider? 64 | let logProvider: SYNQueueLogProvider? 65 | var tasksMap = [String: SYNQueueTask]() 66 | var taskHandlers = [String: SYNTaskCallback]() 67 | let completionBlock: SYNTaskCompleteCallback? 68 | 69 | public var tasks: [SYNQueueTask] { 70 | let array = operations 71 | 72 | var output = [SYNQueueTask]() 73 | output.reserveCapacity(array.count) 74 | 75 | for obj in array { 76 | if let cast = obj as? SYNQueueTask { output.append(cast) } 77 | } 78 | 79 | return output 80 | } 81 | 82 | /** 83 | Initializes a SYNQueue with the provided options 84 | 85 | - parameter queueName: The name of the queue 86 | - parameter maxConcurrency: The maximum number of tasks to run in parallel 87 | - parameter maxRetries: The maximum times a task will be retried if it fails 88 | - parameter logProvider: An optional logger, nothing will be logged if this is nil 89 | - parameter serializationProvider: An optional serializer, there will be no serialzation (persistence) if nil 90 | - parameter completionBlock: The closure to call when a task finishes 91 | 92 | - returns: A new SYNQueue 93 | */ 94 | public required init(queueName: String, maxConcurrency: Int = 1, maxRetries: Int = 5, 95 | logProvider: SYNQueueLogProvider? = nil, 96 | serializationProvider: SYNQueueSerializationProvider? = nil, 97 | completionBlock: SYNTaskCompleteCallback? = nil) 98 | { 99 | self.maxRetries = maxRetries 100 | self.logProvider = logProvider 101 | self.serializationProvider = serializationProvider 102 | self.completionBlock = completionBlock 103 | 104 | super.init() 105 | 106 | self.name = queueName 107 | self.maxConcurrentOperationCount = maxConcurrency 108 | } 109 | 110 | /** 111 | Add a handler for a task type 112 | 113 | - parameter taskType: The task type for the handler 114 | - parameter taskHandler: The handler for this particular task type, must be generic for the task type 115 | */ 116 | public func addTaskHandler(taskType: String, taskHandler:SYNTaskCallback) { 117 | taskHandlers[taskType] = taskHandler 118 | } 119 | 120 | /** 121 | Deserializes tasks that were serialized (persisted) 122 | */ 123 | public func loadSerializedTasks() { 124 | if let sp = serializationProvider { 125 | let tasks = sp.deserializeTasksInQueue(self) 126 | 127 | for task in tasks { 128 | task.setupDependencies(tasks) 129 | addDeserializedTask(task) 130 | } 131 | } 132 | } 133 | 134 | public func getTask(taskID: String) -> SYNQueueTask? { 135 | return tasksMap[taskID] 136 | } 137 | 138 | /** 139 | Adds a SYNQueueTask to the queue and serializes it 140 | 141 | - parameter op: A SYNQueueTask to execute on the queue 142 | */ 143 | override public func addOperation(op: NSOperation) { 144 | if let task = op as? SYNQueueTask { 145 | if tasksMap[task.taskID] != nil { 146 | log(.Warning, "Attempted to add duplicate task \(task.taskID)") 147 | return 148 | } 149 | tasksMap[task.taskID] = task 150 | 151 | // Serialize this operation 152 | if let sp = serializationProvider, let queueName = task.queue.name { 153 | sp.serializeTask(task, queueName: queueName) 154 | } 155 | } 156 | 157 | op.completionBlock = { self.taskComplete(op) } 158 | super.addOperation(op) 159 | } 160 | 161 | func addDeserializedTask(task: SYNQueueTask) { 162 | if tasksMap[task.taskID] != nil { 163 | log(.Warning, "Attempted to add duplicate deserialized task \(task.taskID)") 164 | return 165 | } 166 | 167 | task.completionBlock = { self.taskComplete(task) } 168 | super.addOperation(task) 169 | } 170 | 171 | func runTask(task: SYNQueueTask) { 172 | if let handler = taskHandlers[task.taskType] { 173 | handler(task) 174 | } else { 175 | log(.Warning, "No handler registered for task \(task.taskID)") 176 | task.cancel() 177 | } 178 | } 179 | 180 | func taskComplete(op: NSOperation) { 181 | if let task = op as? SYNQueueTask { 182 | tasksMap.removeValueForKey(task.taskID) 183 | 184 | if let handler = completionBlock { 185 | handler(task.lastError, task) 186 | } 187 | 188 | // Remove this operation from serialization 189 | if let sp = serializationProvider { 190 | sp.removeTask(task.taskID, queue: task.queue) 191 | } 192 | } 193 | } 194 | 195 | func log(level: LogLevel, _ msg: String) { 196 | logProvider?.log(level, msg) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue/SYNQueueTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYNQueueTask.swift 3 | // SYNQueue 4 | // 5 | 6 | import Foundation 7 | 8 | public typealias SYNTaskCallback = (SYNQueueTask) -> Void 9 | public typealias SYNTaskCompleteCallback = (NSError?, SYNQueueTask) -> Void 10 | public typealias JSONDictionary = [String: AnyObject?] 11 | 12 | /** 13 | * Represents a task to be executed on a SYNQueue 14 | */ 15 | @objc 16 | public class SYNQueueTask : NSOperation { 17 | static let MIN_RETRY_DELAY = 0.2 18 | static let MAX_RETRY_DELAY = 60.0 19 | 20 | public let queue: SYNQueue 21 | public let taskID: String 22 | public let taskType: String 23 | public let data: AnyObject? 24 | public let created: NSDate 25 | public var started: NSDate? 26 | public var retries: Int 27 | 28 | let dependencyStrs: [String] 29 | var lastError: NSError? 30 | var _executing: Bool = false 31 | var _finished: Bool = false 32 | 33 | public override var name: String? { get { return taskID } set { } } 34 | public override var asynchronous: Bool { return true } 35 | 36 | public override var executing: Bool { 37 | get { return _executing } 38 | set { 39 | willChangeValueForKey("isExecuting") 40 | _executing = newValue 41 | didChangeValueForKey("isExecuting") 42 | } 43 | } 44 | public override var finished: Bool { 45 | get { return _finished } 46 | set { 47 | willChangeValueForKey("isFinished") 48 | _finished = newValue 49 | didChangeValueForKey("isFinished") 50 | } 51 | } 52 | 53 | /** 54 | Initializes a new SYNQueueTask with the following options 55 | 56 | - parameter queue: The queue that will execute the task 57 | - parameter taskID: A unique identifier for the task, must be unique across app terminations, 58 | otherwise dependencies will not work correctly 59 | - parameter taskType: A type that will be used to group tasks together, tasks have to be generic with respect to their type 60 | - parameter dependencyStrs: Identifiers for tasks that are dependencies of this task 61 | - parameter data: The data that the task needs to operate on 62 | - parameter created: When the task was created 63 | - parameter started: When the task started executing 64 | - parameter retries: Number of times this task has been retried after failing 65 | - parameter queuePriority: The priority 66 | - parameter qualityOfService: The quality of service 67 | 68 | - returns: A new SYNQueueTask 69 | */ 70 | private init(queue: SYNQueue, taskID: String? = nil, taskType: String, 71 | dependencyStrs: [String] = [], data: AnyObject? = nil, 72 | created: NSDate = NSDate(), started: NSDate? = nil, retries: Int = 0, 73 | queuePriority: NSOperationQueuePriority = .Normal, 74 | qualityOfService: NSQualityOfService = .Utility) 75 | { 76 | self.queue = queue 77 | self.taskID = taskID ?? NSUUID().UUIDString 78 | self.taskType = taskType 79 | self.dependencyStrs = dependencyStrs 80 | self.data = data 81 | self.created = created 82 | self.started = started 83 | self.retries = retries 84 | 85 | super.init() 86 | 87 | self.queuePriority = queuePriority 88 | self.qualityOfService = qualityOfService 89 | } 90 | 91 | /** 92 | Initializes a new SYNQueueTask with the following options 93 | 94 | - parameter queue: The queue that will execute the task 95 | - parameter taskType: A type that will be used to group tasks together, tasks have to be generic with respect to their type 96 | - parameter data: The data that the task needs to operate on 97 | - parameter retries: Number of times this task has been retried after failing 98 | - parameter queuePriority: The priority 99 | - parameter qualityOfService: The quality of service 100 | 101 | - returns: A new SYNQueueTask 102 | */ 103 | public convenience init(queue: SYNQueue, type: String, data: AnyObject? = nil, retries: Int = 0, priority: NSOperationQueuePriority = .Normal, quality: NSQualityOfService = .Utility) { 104 | self.init(queue: queue, taskType: type, data: data, retries: retries, queuePriority: priority, qualityOfService: quality) 105 | } 106 | 107 | // For objective-c compatibility of convenience initializer 108 | // See: http://sidhantgandhi.com/swift-default-parameter-values-in-convenience-initializers/ 109 | public convenience init(queue: SYNQueue, taskType: String) { 110 | self.init(queue: queue, type: taskType) 111 | } 112 | 113 | /** 114 | Initializes a SYNQueueTask from a dictionary 115 | 116 | - parameter dictionary: A dictionary that contains the data to reconstruct a task 117 | - parameter queue: The queue that the task will execute on 118 | 119 | - returns: A new SYNQueueTask 120 | */ 121 | public convenience init?(dictionary: JSONDictionary, queue: SYNQueue) { 122 | if let taskID = dictionary["taskID"] as? String, 123 | let taskType = dictionary["taskType"] as? String, 124 | let dependencyStrs = dictionary["dependencies"] as? [String]? ?? [], 125 | let queuePriority = dictionary["queuePriority"] as? Int, 126 | let qualityOfService = dictionary["qualityOfService"] as? Int, 127 | let data: AnyObject? = dictionary["data"] as AnyObject??, 128 | let createdStr = dictionary["created"] as? String, 129 | let startedStr: String? = dictionary["started"] as? String ?? nil, 130 | let retries = dictionary["retries"] as? Int? ?? 0 131 | { 132 | let created = NSDate(dateString: createdStr) ?? NSDate() 133 | let started = (startedStr != nil) ? NSDate(dateString: startedStr!) : nil 134 | let priority = NSOperationQueuePriority(rawValue: queuePriority) ?? .Normal 135 | let qos = NSQualityOfService(rawValue: qualityOfService) ?? .Utility 136 | 137 | self.init(queue: queue, taskID: taskID, taskType: taskType, 138 | dependencyStrs: dependencyStrs, data: data, created: created, 139 | started: started, retries: retries, queuePriority: priority, 140 | qualityOfService: qos) 141 | } else { 142 | self.init(queue: queue, taskID: "", taskType: "") 143 | return nil 144 | } 145 | } 146 | 147 | /** 148 | Initializes a SYNQueueTask from JSON 149 | 150 | - parameter json: JSON from which the reconstruct the task 151 | - parameter queue: The queue that the task will execute on 152 | 153 | - returns: A new SYNQueueTask 154 | */ 155 | public convenience init?(json: String, queue: SYNQueue) { 156 | do { 157 | if let dict = try fromJSON(json) as? [String: AnyObject] { 158 | self.init(dictionary: dict, queue: queue) 159 | } else { 160 | return nil 161 | } 162 | } catch { 163 | return nil 164 | } 165 | } 166 | 167 | /** 168 | Setup the dependencies for the task 169 | 170 | - parameter allTasks: Array of SYNQueueTasks that are dependencies of this task 171 | */ 172 | public func setupDependencies(allTasks: [SYNQueueTask]) { 173 | dependencyStrs.forEach { 174 | (taskID: String) -> Void in 175 | 176 | let found = allTasks.filter({ taskID == $0.name }) 177 | if let task = found.first { 178 | self.addDependency(task) 179 | } else { 180 | let name = self.name ?? "(unknown)" 181 | self.queue.log(.Warning, "Discarding missing dependency \(taskID) from \(name)") 182 | } 183 | } 184 | } 185 | 186 | /** 187 | Deconstruct the task to a dictionary, used to serialize the task 188 | 189 | - returns: A Dictionary representation of the task 190 | */ 191 | public func toDictionary() -> [String: AnyObject?] { 192 | var dict = [String: AnyObject?]() 193 | dict["taskID"] = self.taskID 194 | dict["taskType"] = self.taskType 195 | dict["dependencies"] = self.dependencyStrs 196 | dict["queuePriority"] = self.queuePriority.rawValue 197 | dict["qualityOfService"] = self.qualityOfService.rawValue 198 | dict["data"] = self.data 199 | dict["created"] = self.created.toISOString() 200 | dict["started"] = (self.started != nil) ? self.started!.toISOString() : nil 201 | dict["retries"] = self.retries 202 | 203 | return dict 204 | } 205 | 206 | /** 207 | Deconstruct the task to a JSON string, used to serialize the task 208 | 209 | - returns: A JSON string representation of the task 210 | */ 211 | public func toJSONString() -> String? { 212 | // Serialize this task to a dictionary 213 | let dict = toDictionary() 214 | 215 | // Convert the dictionary to an NSDictionary by replacing nil values 216 | // with NSNull 217 | let nsdict = NSMutableDictionary(capacity: dict.count) 218 | for (key, value) in dict { 219 | nsdict[key] = value ?? NSNull() 220 | } 221 | 222 | do { 223 | let json = try toJSON(nsdict) 224 | return json 225 | } catch { 226 | return nil 227 | } 228 | } 229 | 230 | /** 231 | Starts executing the task 232 | */ 233 | public override func start() { 234 | super.start() 235 | 236 | executing = true 237 | run() 238 | } 239 | 240 | /** 241 | Cancels the task 242 | */ 243 | public override func cancel() { 244 | lastError = NSError(domain: "SYNQueue", code: -1, userInfo: [NSLocalizedDescriptionKey: "Task \(taskID) was cancelled"]) 245 | 246 | super.cancel() 247 | 248 | queue.log(.Debug, "Canceled task \(taskID)") 249 | finished = true 250 | } 251 | 252 | func run() { 253 | if cancelled && !finished { finished = true } 254 | if finished { return } 255 | 256 | queue.runTask(self) 257 | } 258 | 259 | /** 260 | Call this to mark the task as completed, even if it failed. If it failed, we will use exponential backoff to keep retrying 261 | the task until max number of retries is reached. Once this happens, we cancel the task. 262 | 263 | - parameter error: If the task failed, pass an error to indicate why 264 | */ 265 | public func completed(error: NSError?) { 266 | // Check to make sure we're even executing, if not 267 | // just ignore the completed call 268 | if (!executing) { 269 | queue.log(.Debug, "Completion called on already completed task \(taskID)") 270 | return 271 | } 272 | 273 | if let error = error { 274 | lastError = error 275 | queue.log(.Warning, "Task \(taskID) failed with error: \(error)") 276 | 277 | // Check if we've exceeded the max allowed retries 278 | if ++retries >= queue.maxRetries { 279 | queue.log(.Error, "Max retries exceeded for task \(taskID)") 280 | cancel() 281 | return 282 | } 283 | 284 | // Wait a bit (exponential backoff) and retry this task 285 | let exp = Double(min(queue.maxRetries ?? 0, retries)) 286 | let seconds:NSTimeInterval = min(SYNQueueTask.MAX_RETRY_DELAY, SYNQueueTask.MIN_RETRY_DELAY * pow(2.0, exp - 1)) 287 | 288 | queue.log(.Debug, "Waiting \(seconds) seconds to retry task \(taskID)") 289 | runInBackgroundAfter(seconds) { self.run() } 290 | } else { 291 | lastError = nil 292 | queue.log(.Debug, "Task \(taskID) completed") 293 | finished = true 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /SYNQueue/SYNQueue.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9F6202311B333AAF0026CE2C /* SYNQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F6202301B333AAF0026CE2C /* SYNQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 9F62023E1B333AAF0026CE2C /* SYNQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F62023D1B333AAF0026CE2C /* SYNQueueTests.swift */; }; 12 | 9FD63F871B333B81001BD09A /* SYNQueueTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63F861B333B81001BD09A /* SYNQueueTask.swift */; }; 13 | 9FD63FBF1B334316001BD09A /* NSDate+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FBE1B334316001BD09A /* NSDate+Utils.swift */; }; 14 | 9FD63FC11B335579001BD09A /* SYNQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FC01B335579001BD09A /* SYNQueue.swift */; }; 15 | DCD110DA1BBA166B003AF0F0 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD110D81BBA166B003AF0F0 /* ConsoleLogger.swift */; settings = {ASSET_TAGS = (); }; }; 16 | DCD110DB1BBA166B003AF0F0 /* NSUserDefaultsSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD110D91BBA166B003AF0F0 /* NSUserDefaultsSerializer.swift */; settings = {ASSET_TAGS = (); }; }; 17 | DCD110E01BBA2F1E003AF0F0 /* SYNQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F62022B1B333AAF0026CE2C /* SYNQueue.framework */; }; 18 | DCD110E21BBA2FB0003AF0F0 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD110E11BBA2FB0003AF0F0 /* Utils.swift */; settings = {ASSET_TAGS = (); }; }; 19 | DCD110E31BBA2FBC003AF0F0 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD110E11BBA2FB0003AF0F0 /* Utils.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 9F6202381B333AAF0026CE2C /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 9F6202221B333AAF0026CE2C /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 9F62022A1B333AAF0026CE2C; 28 | remoteInfo = SYNQueue; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 9F62022B1B333AAF0026CE2C /* SYNQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SYNQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 9F62022F1B333AAF0026CE2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 9F6202301B333AAF0026CE2C /* SYNQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SYNQueue.h; sourceTree = ""; }; 36 | 9F6202361B333AAF0026CE2C /* SYNQueueTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SYNQueueTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 9F62023C1B333AAF0026CE2C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 9F62023D1B333AAF0026CE2C /* SYNQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYNQueueTests.swift; sourceTree = ""; }; 39 | 9FD63F861B333B81001BD09A /* SYNQueueTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SYNQueueTask.swift; sourceTree = ""; }; 40 | 9FD63FBE1B334316001BD09A /* NSDate+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSDate+Utils.swift"; sourceTree = ""; }; 41 | 9FD63FC01B335579001BD09A /* SYNQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SYNQueue.swift; sourceTree = ""; }; 42 | DCD110D81BBA166B003AF0F0 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; }; 43 | DCD110D91BBA166B003AF0F0 /* NSUserDefaultsSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaultsSerializer.swift; sourceTree = ""; }; 44 | DCD110E11BBA2FB0003AF0F0 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 9F6202271B333AAF0026CE2C /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | 9F6202331B333AAF0026CE2C /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | DCD110E01BBA2F1E003AF0F0 /* SYNQueue.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 9F6202211B333AAF0026CE2C = { 67 | isa = PBXGroup; 68 | children = ( 69 | 9F62022D1B333AAF0026CE2C /* SYNQueue */, 70 | 9F62023A1B333AAF0026CE2C /* SYNQueueTests */, 71 | 9F62022C1B333AAF0026CE2C /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 9F62022C1B333AAF0026CE2C /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 9F62022B1B333AAF0026CE2C /* SYNQueue.framework */, 79 | 9F6202361B333AAF0026CE2C /* SYNQueueTests.xctest */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 9F62022D1B333AAF0026CE2C /* SYNQueue */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 9F62022E1B333AAF0026CE2C /* Supporting Files */, 88 | 9FD63FBE1B334316001BD09A /* NSDate+Utils.swift */, 89 | 9F6202301B333AAF0026CE2C /* SYNQueue.h */, 90 | 9FD63FC01B335579001BD09A /* SYNQueue.swift */, 91 | 9FD63F861B333B81001BD09A /* SYNQueueTask.swift */, 92 | DCD110E11BBA2FB0003AF0F0 /* Utils.swift */, 93 | ); 94 | path = SYNQueue; 95 | sourceTree = ""; 96 | }; 97 | 9F62022E1B333AAF0026CE2C /* Supporting Files */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9F62022F1B333AAF0026CE2C /* Info.plist */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | 9F62023A1B333AAF0026CE2C /* SYNQueueTests */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 9F62023D1B333AAF0026CE2C /* SYNQueueTests.swift */, 109 | 9F62023B1B333AAF0026CE2C /* Supporting Files */, 110 | ); 111 | path = SYNQueueTests; 112 | sourceTree = ""; 113 | }; 114 | 9F62023B1B333AAF0026CE2C /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | DCD110D81BBA166B003AF0F0 /* ConsoleLogger.swift */, 118 | DCD110D91BBA166B003AF0F0 /* NSUserDefaultsSerializer.swift */, 119 | 9F62023C1B333AAF0026CE2C /* Info.plist */, 120 | ); 121 | name = "Supporting Files"; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXHeadersBuildPhase section */ 127 | 9F6202281B333AAF0026CE2C /* Headers */ = { 128 | isa = PBXHeadersBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | 9F6202311B333AAF0026CE2C /* SYNQueue.h in Headers */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | /* End PBXHeadersBuildPhase section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 9F62022A1B333AAF0026CE2C /* SYNQueue */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 9F6202411B333AAF0026CE2C /* Build configuration list for PBXNativeTarget "SYNQueue" */; 141 | buildPhases = ( 142 | 9F6202261B333AAF0026CE2C /* Sources */, 143 | 9F6202271B333AAF0026CE2C /* Frameworks */, 144 | 9F6202281B333AAF0026CE2C /* Headers */, 145 | 9F6202291B333AAF0026CE2C /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = SYNQueue; 152 | productName = SYNQueue; 153 | productReference = 9F62022B1B333AAF0026CE2C /* SYNQueue.framework */; 154 | productType = "com.apple.product-type.framework"; 155 | }; 156 | 9F6202351B333AAF0026CE2C /* SYNQueueTests */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 9F6202441B333AAF0026CE2C /* Build configuration list for PBXNativeTarget "SYNQueueTests" */; 159 | buildPhases = ( 160 | 9F6202321B333AAF0026CE2C /* Sources */, 161 | 9F6202331B333AAF0026CE2C /* Frameworks */, 162 | 9F6202341B333AAF0026CE2C /* Resources */, 163 | ); 164 | buildRules = ( 165 | ); 166 | dependencies = ( 167 | 9F6202391B333AAF0026CE2C /* PBXTargetDependency */, 168 | ); 169 | name = SYNQueueTests; 170 | productName = SYNQueueTests; 171 | productReference = 9F6202361B333AAF0026CE2C /* SYNQueueTests.xctest */; 172 | productType = "com.apple.product-type.bundle.unit-test"; 173 | }; 174 | /* End PBXNativeTarget section */ 175 | 176 | /* Begin PBXProject section */ 177 | 9F6202221B333AAF0026CE2C /* Project object */ = { 178 | isa = PBXProject; 179 | attributes = { 180 | LastSwiftMigration = 0700; 181 | LastSwiftUpdateCheck = 0700; 182 | LastUpgradeCheck = 0630; 183 | ORGANIZATIONNAME = Syntertainment; 184 | TargetAttributes = { 185 | 9F62022A1B333AAF0026CE2C = { 186 | CreatedOnToolsVersion = 6.3.2; 187 | }; 188 | 9F6202351B333AAF0026CE2C = { 189 | CreatedOnToolsVersion = 6.3.2; 190 | }; 191 | }; 192 | }; 193 | buildConfigurationList = 9F6202251B333AAF0026CE2C /* Build configuration list for PBXProject "SYNQueue" */; 194 | compatibilityVersion = "Xcode 3.2"; 195 | developmentRegion = English; 196 | hasScannedForEncodings = 0; 197 | knownRegions = ( 198 | en, 199 | ); 200 | mainGroup = 9F6202211B333AAF0026CE2C; 201 | productRefGroup = 9F62022C1B333AAF0026CE2C /* Products */; 202 | projectDirPath = ""; 203 | projectRoot = ""; 204 | targets = ( 205 | 9F62022A1B333AAF0026CE2C /* SYNQueue */, 206 | 9F6202351B333AAF0026CE2C /* SYNQueueTests */, 207 | ); 208 | }; 209 | /* End PBXProject section */ 210 | 211 | /* Begin PBXResourcesBuildPhase section */ 212 | 9F6202291B333AAF0026CE2C /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | 9F6202341B333AAF0026CE2C /* Resources */ = { 220 | isa = PBXResourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | /* End PBXResourcesBuildPhase section */ 227 | 228 | /* Begin PBXSourcesBuildPhase section */ 229 | 9F6202261B333AAF0026CE2C /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 9FD63FC11B335579001BD09A /* SYNQueue.swift in Sources */, 234 | 9FD63F871B333B81001BD09A /* SYNQueueTask.swift in Sources */, 235 | 9FD63FBF1B334316001BD09A /* NSDate+Utils.swift in Sources */, 236 | DCD110E21BBA2FB0003AF0F0 /* Utils.swift in Sources */, 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | 9F6202321B333AAF0026CE2C /* Sources */ = { 241 | isa = PBXSourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | DCD110E31BBA2FBC003AF0F0 /* Utils.swift in Sources */, 245 | DCD110DB1BBA166B003AF0F0 /* NSUserDefaultsSerializer.swift in Sources */, 246 | DCD110DA1BBA166B003AF0F0 /* ConsoleLogger.swift in Sources */, 247 | 9F62023E1B333AAF0026CE2C /* SYNQueueTests.swift in Sources */, 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | }; 251 | /* End PBXSourcesBuildPhase section */ 252 | 253 | /* Begin PBXTargetDependency section */ 254 | 9F6202391B333AAF0026CE2C /* PBXTargetDependency */ = { 255 | isa = PBXTargetDependency; 256 | target = 9F62022A1B333AAF0026CE2C /* SYNQueue */; 257 | targetProxy = 9F6202381B333AAF0026CE2C /* PBXContainerItemProxy */; 258 | }; 259 | /* End PBXTargetDependency section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | 9F62023F1B333AAF0026CE2C /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 280 | COPY_PHASE_STRIP = NO; 281 | CURRENT_PROJECT_VERSION = 1; 282 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 283 | ENABLE_STRICT_OBJC_MSGSEND = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu99; 285 | GCC_DYNAMIC_NO_PIC = NO; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_OPTIMIZATION_LEVEL = 0; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "DEBUG=1", 290 | "$(inherited)", 291 | ); 292 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 300 | MTL_ENABLE_DEBUG_INFO = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos; 303 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 304 | TARGETED_DEVICE_FAMILY = "1,2"; 305 | VERSIONING_SYSTEM = "apple-generic"; 306 | VERSION_INFO_PREFIX = ""; 307 | }; 308 | name = Debug; 309 | }; 310 | 9F6202401B333AAF0026CE2C /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_UNREACHABLE_CODE = YES; 326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 327 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | CURRENT_PROJECT_VERSION = 1; 330 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 331 | ENABLE_NS_ASSERTIONS = NO; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | GCC_C_LANGUAGE_STANDARD = gnu99; 334 | GCC_NO_COMMON_BLOCKS = YES; 335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 337 | GCC_WARN_UNDECLARED_SELECTOR = YES; 338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 339 | GCC_WARN_UNUSED_FUNCTION = YES; 340 | GCC_WARN_UNUSED_VARIABLE = YES; 341 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 342 | MTL_ENABLE_DEBUG_INFO = NO; 343 | SDKROOT = iphoneos; 344 | TARGETED_DEVICE_FAMILY = "1,2"; 345 | VALIDATE_PRODUCT = YES; 346 | VERSIONING_SYSTEM = "apple-generic"; 347 | VERSION_INFO_PREFIX = ""; 348 | }; 349 | name = Release; 350 | }; 351 | 9F6202421B333AAF0026CE2C /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | CLANG_ENABLE_MODULES = YES; 355 | CODE_SIGN_IDENTITY = "iPhone Developer"; 356 | DEFINES_MODULE = YES; 357 | DYLIB_COMPATIBILITY_VERSION = 1; 358 | DYLIB_CURRENT_VERSION = 1; 359 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 360 | ENABLE_TESTABILITY = YES; 361 | INFOPLIST_FILE = SYNQueue/Info.plist; 362 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 363 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 364 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | SKIP_INSTALL = YES; 367 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 368 | }; 369 | name = Debug; 370 | }; 371 | 9F6202431B333AAF0026CE2C /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | CLANG_ENABLE_MODULES = YES; 375 | CODE_SIGN_IDENTITY = "iPhone Developer"; 376 | DEFINES_MODULE = YES; 377 | DYLIB_COMPATIBILITY_VERSION = 1; 378 | DYLIB_CURRENT_VERSION = 1; 379 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 380 | ENABLE_TESTABILITY = YES; 381 | INFOPLIST_FILE = SYNQueue/Info.plist; 382 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 383 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 384 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | SKIP_INSTALL = YES; 387 | }; 388 | name = Release; 389 | }; 390 | 9F6202451B333AAF0026CE2C /* Debug */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ENABLE_TESTABILITY = NO; 394 | GCC_PREPROCESSOR_DEFINITIONS = ( 395 | "DEBUG=1", 396 | "$(inherited)", 397 | ); 398 | INFOPLIST_FILE = SYNQueueTests/Info.plist; 399 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | }; 402 | name = Debug; 403 | }; 404 | 9F6202461B333AAF0026CE2C /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ENABLE_TESTABILITY = NO; 408 | INFOPLIST_FILE = SYNQueueTests/Info.plist; 409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | }; 412 | name = Release; 413 | }; 414 | /* End XCBuildConfiguration section */ 415 | 416 | /* Begin XCConfigurationList section */ 417 | 9F6202251B333AAF0026CE2C /* Build configuration list for PBXProject "SYNQueue" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | 9F62023F1B333AAF0026CE2C /* Debug */, 421 | 9F6202401B333AAF0026CE2C /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | 9F6202411B333AAF0026CE2C /* Build configuration list for PBXNativeTarget "SYNQueue" */ = { 427 | isa = XCConfigurationList; 428 | buildConfigurations = ( 429 | 9F6202421B333AAF0026CE2C /* Debug */, 430 | 9F6202431B333AAF0026CE2C /* Release */, 431 | ); 432 | defaultConfigurationIsVisible = 0; 433 | defaultConfigurationName = Release; 434 | }; 435 | 9F6202441B333AAF0026CE2C /* Build configuration list for PBXNativeTarget "SYNQueueTests" */ = { 436 | isa = XCConfigurationList; 437 | buildConfigurations = ( 438 | 9F6202451B333AAF0026CE2C /* Debug */, 439 | 9F6202461B333AAF0026CE2C /* Release */, 440 | ); 441 | defaultConfigurationIsVisible = 0; 442 | defaultConfigurationName = Release; 443 | }; 444 | /* End XCConfigurationList section */ 445 | }; 446 | rootObject = 9F6202221B333AAF0026CE2C /* Project object */; 447 | } 448 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9F70B5031B368AB500BDB945 /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F70B5021B368AB500BDB945 /* ConsoleLogger.swift */; }; 11 | 9FD63F971B333BDC001BD09A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63F961B333BDC001BD09A /* AppDelegate.swift */; }; 12 | 9FD63F991B333BDC001BD09A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63F981B333BDC001BD09A /* ViewController.swift */; }; 13 | 9FD63F9E1B333BDC001BD09A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9FD63F9D1B333BDC001BD09A /* Images.xcassets */; }; 14 | 9FD63FA11B333BDC001BD09A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9FD63F9F1B333BDC001BD09A /* LaunchScreen.xib */; }; 15 | 9FD63FAD1B333BDC001BD09A /* SYNQueueDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FAC1B333BDC001BD09A /* SYNQueueDemoTests.swift */; }; 16 | 9FD63FB91B333E15001BD09A /* SYNQueue.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9FD63FB81B333E15001BD09A /* SYNQueue.framework */; }; 17 | 9FD63FBA1B333E15001BD09A /* SYNQueue.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9FD63FB81B333E15001BD09A /* SYNQueue.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9FD63FC51B338BA5001BD09A /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FC41B338BA5001BD09A /* TaskCell.swift */; }; 19 | 9FD63FC71B33EA2F001BD09A /* NSUserDefaultsSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FC61B33EA2F001BD09A /* NSUserDefaultsSerializer.swift */; }; 20 | 9FD63FCD1B3417DF001BD09A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD63FCC1B3417DF001BD09A /* Utils.swift */; }; 21 | DC67E47E1BB0F01200F3D9E2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DC67E47D1BB0F01200F3D9E2 /* Main.storyboard */; settings = {ASSET_TAGS = (); }; }; 22 | DC67E4801BB0F1BD00F3D9E2 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC67E47F1BB0F1BD00F3D9E2 /* SettingsViewController.swift */; settings = {ASSET_TAGS = (); }; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 9FD63FA71B333BDC001BD09A /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 9FD63F891B333BDC001BD09A /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 9FD63F901B333BDC001BD09A; 31 | remoteInfo = SYNQueueDemo; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXCopyFilesBuildPhase section */ 36 | 9FD63FBB1B333E16001BD09A /* Embed Frameworks */ = { 37 | isa = PBXCopyFilesBuildPhase; 38 | buildActionMask = 2147483647; 39 | dstPath = ""; 40 | dstSubfolderSpec = 10; 41 | files = ( 42 | 9FD63FBA1B333E15001BD09A /* SYNQueue.framework in Embed Frameworks */, 43 | ); 44 | name = "Embed Frameworks"; 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXCopyFilesBuildPhase section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 9F70B5021B368AB500BDB945 /* ConsoleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = ""; }; 51 | 9FD63F911B333BDC001BD09A /* SYNQueueDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SYNQueueDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 9FD63F951B333BDC001BD09A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 9FD63F961B333BDC001BD09A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 54 | 9FD63F981B333BDC001BD09A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 55 | 9FD63F9D1B333BDC001BD09A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 56 | 9FD63FA01B333BDC001BD09A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 57 | 9FD63FA61B333BDC001BD09A /* SYNQueueDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SYNQueueDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 9FD63FAB1B333BDC001BD09A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 9FD63FAC1B333BDC001BD09A /* SYNQueueDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYNQueueDemoTests.swift; sourceTree = ""; }; 60 | 9FD63FB81B333E15001BD09A /* SYNQueue.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SYNQueue.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 9FD63FC41B338BA5001BD09A /* TaskCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; 62 | 9FD63FC61B33EA2F001BD09A /* NSUserDefaultsSerializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSUserDefaultsSerializer.swift; sourceTree = ""; }; 63 | 9FD63FCC1B3417DF001BD09A /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 64 | DC67E47D1BB0F01200F3D9E2 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 65 | DC67E47F1BB0F1BD00F3D9E2 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 9FD63F8E1B333BDC001BD09A /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | 9FD63FB91B333E15001BD09A /* SYNQueue.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 9FD63FA31B333BDC001BD09A /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | 9FD63F881B333BDC001BD09A = { 88 | isa = PBXGroup; 89 | children = ( 90 | 9FD63FB81B333E15001BD09A /* SYNQueue.framework */, 91 | 9FD63F931B333BDC001BD09A /* SYNQueueDemo */, 92 | 9FD63FA91B333BDC001BD09A /* SYNQueueDemoTests */, 93 | 9FD63F921B333BDC001BD09A /* Products */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 9FD63F921B333BDC001BD09A /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9FD63F911B333BDC001BD09A /* SYNQueueDemo.app */, 101 | 9FD63FA61B333BDC001BD09A /* SYNQueueDemoTests.xctest */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | 9FD63F931B333BDC001BD09A /* SYNQueueDemo */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 9FD63F9D1B333BDC001BD09A /* Images.xcassets */, 110 | 9FD63F941B333BDC001BD09A /* Supporting Files */, 111 | 9FD63F961B333BDC001BD09A /* AppDelegate.swift */, 112 | 9F70B5021B368AB500BDB945 /* ConsoleLogger.swift */, 113 | 9FD63F981B333BDC001BD09A /* ViewController.swift */, 114 | DC67E47F1BB0F1BD00F3D9E2 /* SettingsViewController.swift */, 115 | DC67E47D1BB0F01200F3D9E2 /* Main.storyboard */, 116 | 9FD63F9F1B333BDC001BD09A /* LaunchScreen.xib */, 117 | 9FD63FC41B338BA5001BD09A /* TaskCell.swift */, 118 | 9FD63FC61B33EA2F001BD09A /* NSUserDefaultsSerializer.swift */, 119 | 9FD63FCC1B3417DF001BD09A /* Utils.swift */, 120 | ); 121 | path = SYNQueueDemo; 122 | sourceTree = ""; 123 | }; 124 | 9FD63F941B333BDC001BD09A /* Supporting Files */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 9FD63F951B333BDC001BD09A /* Info.plist */, 128 | ); 129 | name = "Supporting Files"; 130 | sourceTree = ""; 131 | }; 132 | 9FD63FA91B333BDC001BD09A /* SYNQueueDemoTests */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 9FD63FAC1B333BDC001BD09A /* SYNQueueDemoTests.swift */, 136 | 9FD63FAA1B333BDC001BD09A /* Supporting Files */, 137 | ); 138 | path = SYNQueueDemoTests; 139 | sourceTree = ""; 140 | }; 141 | 9FD63FAA1B333BDC001BD09A /* Supporting Files */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 9FD63FAB1B333BDC001BD09A /* Info.plist */, 145 | ); 146 | name = "Supporting Files"; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 9FD63F901B333BDC001BD09A /* SYNQueueDemo */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 9FD63FB01B333BDC001BD09A /* Build configuration list for PBXNativeTarget "SYNQueueDemo" */; 155 | buildPhases = ( 156 | 9FD63F8D1B333BDC001BD09A /* Sources */, 157 | 9FD63F8E1B333BDC001BD09A /* Frameworks */, 158 | 9FD63F8F1B333BDC001BD09A /* Resources */, 159 | 9FD63FBB1B333E16001BD09A /* Embed Frameworks */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = SYNQueueDemo; 166 | productName = SYNQueueDemo; 167 | productReference = 9FD63F911B333BDC001BD09A /* SYNQueueDemo.app */; 168 | productType = "com.apple.product-type.application"; 169 | }; 170 | 9FD63FA51B333BDC001BD09A /* SYNQueueDemoTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 9FD63FB31B333BDC001BD09A /* Build configuration list for PBXNativeTarget "SYNQueueDemoTests" */; 173 | buildPhases = ( 174 | 9FD63FA21B333BDC001BD09A /* Sources */, 175 | 9FD63FA31B333BDC001BD09A /* Frameworks */, 176 | 9FD63FA41B333BDC001BD09A /* Resources */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | 9FD63FA81B333BDC001BD09A /* PBXTargetDependency */, 182 | ); 183 | name = SYNQueueDemoTests; 184 | productName = SYNQueueDemoTests; 185 | productReference = 9FD63FA61B333BDC001BD09A /* SYNQueueDemoTests.xctest */; 186 | productType = "com.apple.product-type.bundle.unit-test"; 187 | }; 188 | /* End PBXNativeTarget section */ 189 | 190 | /* Begin PBXProject section */ 191 | 9FD63F891B333BDC001BD09A /* Project object */ = { 192 | isa = PBXProject; 193 | attributes = { 194 | LastSwiftMigration = 0700; 195 | LastSwiftUpdateCheck = 0700; 196 | LastUpgradeCheck = 0630; 197 | ORGANIZATIONNAME = Syntertainment; 198 | TargetAttributes = { 199 | 9FD63F901B333BDC001BD09A = { 200 | CreatedOnToolsVersion = 6.3.2; 201 | DevelopmentTeam = SBKBKHJ464; 202 | }; 203 | 9FD63FA51B333BDC001BD09A = { 204 | CreatedOnToolsVersion = 6.3.2; 205 | TestTargetID = 9FD63F901B333BDC001BD09A; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 9FD63F8C1B333BDC001BD09A /* Build configuration list for PBXProject "SYNQueueDemo" */; 210 | compatibilityVersion = "Xcode 3.2"; 211 | developmentRegion = English; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | Base, 216 | ); 217 | mainGroup = 9FD63F881B333BDC001BD09A; 218 | productRefGroup = 9FD63F921B333BDC001BD09A /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 9FD63F901B333BDC001BD09A /* SYNQueueDemo */, 223 | 9FD63FA51B333BDC001BD09A /* SYNQueueDemoTests */, 224 | ); 225 | }; 226 | /* End PBXProject section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 9FD63F8F1B333BDC001BD09A /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 9FD63FA11B333BDC001BD09A /* LaunchScreen.xib in Resources */, 234 | 9FD63F9E1B333BDC001BD09A /* Images.xcassets in Resources */, 235 | DC67E47E1BB0F01200F3D9E2 /* Main.storyboard in Resources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | 9FD63FA41B333BDC001BD09A /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | /* End PBXResourcesBuildPhase section */ 247 | 248 | /* Begin PBXSourcesBuildPhase section */ 249 | 9FD63F8D1B333BDC001BD09A /* Sources */ = { 250 | isa = PBXSourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 9FD63FCD1B3417DF001BD09A /* Utils.swift in Sources */, 254 | 9FD63F991B333BDC001BD09A /* ViewController.swift in Sources */, 255 | DC67E4801BB0F1BD00F3D9E2 /* SettingsViewController.swift in Sources */, 256 | 9FD63FC51B338BA5001BD09A /* TaskCell.swift in Sources */, 257 | 9F70B5031B368AB500BDB945 /* ConsoleLogger.swift in Sources */, 258 | 9FD63F971B333BDC001BD09A /* AppDelegate.swift in Sources */, 259 | 9FD63FC71B33EA2F001BD09A /* NSUserDefaultsSerializer.swift in Sources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | 9FD63FA21B333BDC001BD09A /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 9FD63FAD1B333BDC001BD09A /* SYNQueueDemoTests.swift in Sources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXSourcesBuildPhase section */ 272 | 273 | /* Begin PBXTargetDependency section */ 274 | 9FD63FA81B333BDC001BD09A /* PBXTargetDependency */ = { 275 | isa = PBXTargetDependency; 276 | target = 9FD63F901B333BDC001BD09A /* SYNQueueDemo */; 277 | targetProxy = 9FD63FA71B333BDC001BD09A /* PBXContainerItemProxy */; 278 | }; 279 | /* End PBXTargetDependency section */ 280 | 281 | /* Begin PBXVariantGroup section */ 282 | 9FD63F9F1B333BDC001BD09A /* LaunchScreen.xib */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | 9FD63FA01B333BDC001BD09A /* Base */, 286 | ); 287 | name = LaunchScreen.xib; 288 | sourceTree = ""; 289 | }; 290 | /* End PBXVariantGroup section */ 291 | 292 | /* Begin XCBuildConfiguration section */ 293 | 9FD63FAE1B333BDC001BD09A /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 298 | CLANG_CXX_LIBRARY = "libc++"; 299 | CLANG_ENABLE_MODULES = YES; 300 | CLANG_ENABLE_OBJC_ARC = YES; 301 | CLANG_WARN_BOOL_CONVERSION = YES; 302 | CLANG_WARN_CONSTANT_CONVERSION = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INT_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_UNREACHABLE_CODE = YES; 309 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 310 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 311 | COPY_PHASE_STRIP = NO; 312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 313 | ENABLE_STRICT_OBJC_MSGSEND = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_DYNAMIC_NO_PIC = NO; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 330 | MTL_ENABLE_DEBUG_INFO = YES; 331 | ONLY_ACTIVE_ARCH = YES; 332 | SDKROOT = iphoneos; 333 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | }; 336 | name = Debug; 337 | }; 338 | 9FD63FAF1B333BDC001BD09A /* Release */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ALWAYS_SEARCH_USER_PATHS = NO; 342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 343 | CLANG_CXX_LIBRARY = "libc++"; 344 | CLANG_ENABLE_MODULES = YES; 345 | CLANG_ENABLE_OBJC_ARC = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_EMPTY_BODY = YES; 350 | CLANG_WARN_ENUM_CONVERSION = YES; 351 | CLANG_WARN_INT_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_UNREACHABLE_CODE = YES; 354 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 355 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 356 | COPY_PHASE_STRIP = NO; 357 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 358 | ENABLE_NS_ASSERTIONS = NO; 359 | ENABLE_STRICT_OBJC_MSGSEND = YES; 360 | GCC_C_LANGUAGE_STANDARD = gnu99; 361 | GCC_NO_COMMON_BLOCKS = YES; 362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 364 | GCC_WARN_UNDECLARED_SELECTOR = YES; 365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 366 | GCC_WARN_UNUSED_FUNCTION = YES; 367 | GCC_WARN_UNUSED_VARIABLE = YES; 368 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 369 | MTL_ENABLE_DEBUG_INFO = NO; 370 | SDKROOT = iphoneos; 371 | TARGETED_DEVICE_FAMILY = "1,2"; 372 | VALIDATE_PRODUCT = YES; 373 | }; 374 | name = Release; 375 | }; 376 | 9FD63FB11B333BDC001BD09A /* Debug */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 380 | CODE_SIGN_IDENTITY = "iPhone Developer"; 381 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 382 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 383 | INFOPLIST_FILE = SYNQueueDemo/Info.plist; 384 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 385 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 386 | PRODUCT_NAME = "$(TARGET_NAME)"; 387 | }; 388 | name = Debug; 389 | }; 390 | 9FD63FB21B333BDC001BD09A /* Release */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 394 | CODE_SIGN_IDENTITY = "iPhone Developer"; 395 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 396 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 397 | INFOPLIST_FILE = SYNQueueDemo/Info.plist; 398 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 399 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | }; 402 | name = Release; 403 | }; 404 | 9FD63FB41B333BDC001BD09A /* Debug */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | BUNDLE_LOADER = "$(TEST_HOST)"; 408 | GCC_PREPROCESSOR_DEFINITIONS = ( 409 | "DEBUG=1", 410 | "$(inherited)", 411 | ); 412 | INFOPLIST_FILE = SYNQueueDemoTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SYNQueueDemo.app/SYNQueueDemo"; 416 | }; 417 | name = Debug; 418 | }; 419 | 9FD63FB51B333BDC001BD09A /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | BUNDLE_LOADER = "$(TEST_HOST)"; 423 | INFOPLIST_FILE = SYNQueueDemoTests/Info.plist; 424 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SYNQueueDemo.app/SYNQueueDemo"; 427 | }; 428 | name = Release; 429 | }; 430 | /* End XCBuildConfiguration section */ 431 | 432 | /* Begin XCConfigurationList section */ 433 | 9FD63F8C1B333BDC001BD09A /* Build configuration list for PBXProject "SYNQueueDemo" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 9FD63FAE1B333BDC001BD09A /* Debug */, 437 | 9FD63FAF1B333BDC001BD09A /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | 9FD63FB01B333BDC001BD09A /* Build configuration list for PBXNativeTarget "SYNQueueDemo" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | 9FD63FB11B333BDC001BD09A /* Debug */, 446 | 9FD63FB21B333BDC001BD09A /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | 9FD63FB31B333BDC001BD09A /* Build configuration list for PBXNativeTarget "SYNQueueDemoTests" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 9FD63FB41B333BDC001BD09A /* Debug */, 455 | 9FD63FB51B333BDC001BD09A /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | /* End XCConfigurationList section */ 461 | }; 462 | rootObject = 9FD63F891B333BDC001BD09A /* Project object */; 463 | } 464 | -------------------------------------------------------------------------------- /SYNQueueDemo/SYNQueueDemo/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 58 | 73 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | --------------------------------------------------------------------------------