├── 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 |
--------------------------------------------------------------------------------