Repository: facebookarchive/KVOController Branch: master Commit: 1ee564830a46 Files: 59 Total size: 239.9 KB Directory structure: gitextract_aqb3k6nd/ ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Examples/ │ ├── Clock-OSX/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ └── MainMenu.xib │ │ ├── Clock-OSX-Info.plist │ │ ├── Clock-OSX-Prefix.pch │ │ ├── ClockView.h │ │ ├── ClockView.m │ │ ├── Images.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── en.lproj/ │ │ │ ├── Credits.rtf │ │ │ └── InfoPlist.strings │ │ └── main.m │ ├── Clock-iOS/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj/ │ │ │ ├── Main_iPad.storyboard │ │ │ └── Main_iPhone.storyboard │ │ ├── Clock-iOS-Info.plist │ │ ├── Clock-iOS-Prefix.pch │ │ ├── Clock.h │ │ ├── Clock.m │ │ ├── ClockLayer.h │ │ ├── ClockLayer.m │ │ ├── ClockView.h │ │ ├── ClockView.m │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ └── Examples.xcodeproj/ │ └── project.pbxproj ├── FBKVOController/ │ ├── FBKVOController.h │ ├── FBKVOController.m │ ├── Info.plist │ ├── KVOController.h │ ├── NSObject+FBKVOController.h │ └── NSObject+FBKVOController.m ├── FBKVOController.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ ├── FBKVOController-OSX-Dynamic.xcscheme │ ├── FBKVOController-iOS-Dynamic.xcscheme │ ├── FBKVOController-tvOS-Dynamic.xcscheme │ ├── FBKVOController-watchOS-Dynamic.xcscheme │ └── FBKVOController.xcscheme ├── FBKVOController.xcworkspace/ │ └── contents.xcworkspacedata ├── FBKVOControllerTests/ │ ├── FBKVOControllerTests-Info.plist │ ├── FBKVOControllerTests.m │ ├── FBKVOTesting.h │ ├── FBKVOTesting.m │ └── en.lproj/ │ └── InfoPlist.strings ├── KVOController.podspec ├── LICENSE ├── PATENTS ├── Podfile ├── README.md ├── Rakefile └── codecov.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## OS X .DS_Store ## Build generated build/ DerivedData ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata ## Other *.xccheckout *.moved-aside *.xcuserstate *.xcscmblueprint ## Obj-C/Swift specific *.hmap *.ipa ## Dependency Managers Pods/ Carthage/Build ## AppCode .idea/ ================================================ FILE: .travis.yml ================================================ branches: only: - master language: objective-c os: osx osx_image: xcode8.2 env: matrix: - TEST_TYPE=iOS - TEST_TYPE=CocoaPods - TEST_TYPE=Carthage install: - | if [ "$TEST_TYPE" = "iOS" ]; then gem install xcpretty -N --no-ri --no-rdoc gem update cocoapods pod install elif [ "$TEST_TYPE" = Carthage ]; then brew install carthage || brew upgrade carthage fi script: - | if [ "$TEST_TYPE" = "iOS" ]; then set -o pipefail xcodebuild test -workspace FBKVOController.xcworkspace -scheme FBKVOController -sdk iphonesimulator -destination "platform=iOS Simulator,OS=latest,name=iPhone 4s" -destination "platform=iOS Simulator,OS=latest,name=iPad Air 2" GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty -c elif [ "$TEST_TYPE" = CocoaPods ]; then pod lib lint KVOController.podspec pod lib lint --use-libraries KVOController.podspec elif [ "$TEST_TYPE" = Carthage ]; then carthage build --no-skip-current fi after_success: - | if [ "$TEST_TYPE" = "iOS" ]; then bash <(curl -s https://codecov.io/bash) fi ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing We want to make contributing to KVOController as easy and transparent as possible. If you run into problems, please open an issue. We also actively welcome pull requests. ## Pull Requests 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. Make sure your code lints. 6. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. Complete your CLA here: ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. ## Coding Style * 2 spaces for indentation rather than tabs ## License By contributing to KVOController, you agree that your contributions will be licensed under its BSD license. ================================================ FILE: Examples/Clock-OSX/AppDelegate.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @interface AppDelegate : NSObject @property (assign) IBOutlet NSWindow *window; @end ================================================ FILE: Examples/Clock-OSX/AppDelegate.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "AppDelegate.h" #import "Clock.h" #import "ClockView.h" @interface AppDelegate() @end #define CLOCK_VIEW_MAX_COUNT 10 #define CLOCK_VIEW_TIME_DELAY 3.0 @implementation AppDelegate { NSMutableArray *_clockViews; dispatch_source_t _timer; } - (void)dealloc { if (NULL != _timer) { dispatch_source_cancel(_timer); } } - (void)_addClockView { if (!_clockViews) { _clockViews = [NSMutableArray array]; } ClockView *clockView = [[ClockView alloc] initWithClock:[Clock clock]]; [_clockViews addObject:clockView]; [_window.contentView addSubview:clockView]; NSSize clockSize = clockView.bounds.size; NSRect contentBounds = [_window.contentView bounds]; [clockView setFrameOrigin:NSMakePoint(arc4random_uniform(contentBounds.size.width) - (clockSize.width / 2.), arc4random_uniform(contentBounds.size.height) - (clockSize.height / 2.))]; } - (void)_removeClockView { if (0 == _clockViews.count) { return; } [_clockViews[0] removeFromSuperview]; [_clockViews removeObjectAtIndex:0]; } - (void)_removeAddClockView { [self _removeClockView]; [self _addClockView]; } - (void)_scheduleTimer { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, CLOCK_VIEW_TIME_DELAY * NSEC_PER_SEC), CLOCK_VIEW_TIME_DELAY * NSEC_PER_SEC, 1.0); __weak id weakSelf = self; dispatch_source_set_event_handler(timer, ^{ [weakSelf _removeAddClockView]; }); _timer = timer; dispatch_resume(timer); } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { while (_clockViews.count < CLOCK_VIEW_MAX_COUNT) { [self _addClockView]; } [self _scheduleTimer]; } @end ================================================ FILE: Examples/Clock-OSX/Base.lproj/MainMenu.xib ================================================ Default Left to Right Right to Left Default Left to Right Right to Left ================================================ FILE: Examples/Clock-OSX/Clock-OSX-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier com.facebook.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: Examples/Clock-OSX/Clock-OSX-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #ifdef __OBJC__ #import #endif ================================================ FILE: Examples/Clock-OSX/ClockView.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @class Clock; @interface ClockView : NSDatePicker - (instancetype)initWithClock:(Clock *)clock; @end ================================================ FILE: Examples/Clock-OSX/ClockView.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "ClockView.h" #import "Clock.h" #import "FBKVOController.h" @implementation ClockView { FBKVOController *_KVOController; } - (instancetype)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; if (nil != self) { self.datePickerStyle = NSClockAndCalendarDatePickerStyle; self.datePickerElements = NSHourMinuteSecondDatePickerElementFlag; [self sizeToFit]; } return self; } - (instancetype)initWithClock:(Clock *)clock { self = [self init]; if (nil != self) { // create KVO controller instance _KVOController = [FBKVOController controllerWithObserver:self]; // handle clock change, including initial value [_KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { // update observer with new value clockView.dateValue = change[NSKeyValueChangeNewKey]; }]; } return self; } @end ================================================ FILE: Examples/Clock-OSX/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "size" : "16x16", "scale" : "1x" }, { "idiom" : "mac", "size" : "16x16", "scale" : "2x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "1x" }, { "idiom" : "mac", "size" : "32x32", "scale" : "2x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "1x" }, { "idiom" : "mac", "size" : "128x128", "scale" : "2x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "1x" }, { "idiom" : "mac", "size" : "256x256", "scale" : "2x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "1x" }, { "idiom" : "mac", "size" : "512x512", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/Clock-OSX/en.lproj/Credits.rtf ================================================ {\rtf1\ansi\ansicpg1252\cocoartf1265 {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset128 HiraKakuProN-W3;} {\colortbl;\red255\green255\blue255;} \vieww9600\viewh8400\viewkind0 \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 \f0\b\fs24 \cf0 Engineering: \b0 \ Kimon Tsinteris\ \ \b Testing: \b0 \ Kimon Tsinteris\ \ \b Documentation: \b0 \ Kimon Tsinteris\ \ \b With special thanks to: \b0 \ \f1\fs60 \uc0\u9731 \f0\fs24 \ } ================================================ FILE: Examples/Clock-OSX/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Examples/Clock-OSX/main.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); } ================================================ FILE: Examples/Clock-iOS/AppDelegate.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: Examples/Clock-iOS/AppDelegate.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "AppDelegate.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return YES; } @end ================================================ FILE: Examples/Clock-iOS/Base.lproj/Main_iPad.storyboard ================================================ ================================================ FILE: Examples/Clock-iOS/Base.lproj/Main_iPhone.storyboard ================================================ ================================================ FILE: Examples/Clock-iOS/Clock-iOS-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.facebook.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIMainStoryboardFile Main_iPhone UIMainStoryboardFile~ipad Main_iPad UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIStatusBarHidden UIStatusBarStyle UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Examples/Clock-iOS/Clock-iOS-Prefix.pch ================================================ // // Prefix header // // The contents of this file are implicitly included at the beginning of every source file. // #import #ifndef __IPHONE_5_0 #warning "This project uses features only available in iOS SDK 5.0 and later." #endif #ifdef __OBJC__ #import #import #endif ================================================ FILE: Examples/Clock-iOS/Clock.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @interface Clock : NSObject /// The shared clock instance. + (instancetype)clock; /// The curent date and time. Observable. @property (strong, readonly, nonatomic) NSDate *date; @end ================================================ FILE: Examples/Clock-iOS/Clock.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "Clock.h" @interface Clock () @property (strong, readwrite, nonatomic) NSDate *date; @end @implementation Clock { dispatch_source_t _timer; } + (instancetype)clock { static Clock *_clock = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _clock = [[Clock alloc] init]; }); return _clock; } - (id)init { self = [super init]; if (self) { [self _updateDate]; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0); __weak Clock *weakSelf = self; dispatch_source_set_event_handler(timer, ^{ [weakSelf _updateDate]; }); _timer = timer; dispatch_resume(timer); } return self; } - (void)dealloc { if (NULL != _timer) { dispatch_source_cancel(_timer); } } - (void)_updateDate { self.date = [NSDate date]; } @end ================================================ FILE: Examples/Clock-iOS/ClockLayer.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @interface ClockLayer : CALayer + (NSDictionary *)darkStyle; + (NSDictionary *)lightStyle; @property (strong, nonatomic) NSDate *date; @end ================================================ FILE: Examples/Clock-iOS/ClockLayer.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "ClockLayer.h" #import // number layer attributes #define NUMBER_LAYER_COUNT 12 #define NUMBER_FONT_NAME @"HelveticaNeue" #define NUMBER_FONT_SIZE 16.0 // normalized hand length, ratio of clock radius #define SECOND_HAND_LENGTH 0.57 #define MINUTE_HAND_LENGTH 0.65 #define HOUR_HAND_LENGTH 0.5 @interface ElipseLayer : CAShapeLayer @end @implementation ElipseLayer - (void)setBounds:(CGRect)bounds { if (!CGRectEqualToRect(self.bounds, bounds)) { super.bounds = bounds; if (CGRectEqualToRect(CGRectZero, bounds)) { self.path = NULL; } else { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, nil, bounds); self.path = path; CGPathRelease(path); } } } @end static CALayer *hand_layer(CGFloat contentsScale) { CALayer *layer = [CALayer layer]; layer.contentsScale = contentsScale; layer.shouldRasterize = YES; return layer; } static ElipseLayer *elipse_layer(CGFloat contentsScale) { ElipseLayer *layer = [ElipseLayer layer]; layer.contentsScale = contentsScale; layer.shouldRasterize = YES; return layer; } static CATextLayer *number_layer(CGFloat contentsScale, CTFontRef font, NSUInteger number) { CATextLayer *layer = [CATextLayer layer]; layer.string = [NSString stringWithFormat:@"%lu", (unsigned long)number]; layer.alignmentMode = kCAAlignmentCenter; layer.fontSize = NUMBER_FONT_SIZE; layer.font = font; layer.contentsScale = contentsScale; return layer; } // clock style keys static NSString * const kClockBackgroundColorKey = @"clockBackgroundColor"; static NSString * const kClockForegroundColorKey = @"clockForegroundColor"; static NSString * const kClockAccentColorKey = @"clockAccentColor"; // clock style properties @interface ClockLayer () @property (readonly) UIColor *clockBackgroundColor; @property (readonly) UIColor *clockForegroundColor; @property (readonly) UIColor *clockAccentColor; @end @implementation ClockLayer { ElipseLayer *_faceLayer; ElipseLayer *_largeDotLayer; ElipseLayer *_smallDotLayer; CALayer *_secondHandLayer; CALayer *_minuteHandLayer; CALayer *_hourHandLayer; NSArray *_numberLayers; CGFloat _radius; BOOL _needsFullLayout; } #pragma mark - Class // dark style definition + (NSDictionary *)darkStyle { return @{kClockBackgroundColorKey: [UIColor blackColor], kClockForegroundColorKey: [UIColor whiteColor], kClockAccentColorKey: [UIColor redColor]}; } // light style definition + (NSDictionary *)lightStyle { return @{kClockBackgroundColorKey: [UIColor whiteColor], kClockForegroundColorKey: [UIColor blackColor], kClockAccentColorKey: [UIColor redColor]}; } // default style definition + (id)defaultValueForKey:(NSString *)key { id value = [self darkStyle][key]; if (nil != value) { return value; } return [super defaultValueForKey:key]; } #pragma mark - Lifecycle - (id)init { self = [super init]; if (self) { // default contents scale CGFloat contentsScale = [UIScreen mainScreen].scale; // elipse layers _faceLayer = elipse_layer(contentsScale); _largeDotLayer = elipse_layer(contentsScale); _smallDotLayer = elipse_layer(contentsScale); // number layers CTFontRef font = CTFontCreateWithName((CFStringRef)NUMBER_FONT_NAME, NUMBER_FONT_SIZE, NULL); NSMutableArray *numberLayers = [NSMutableArray arrayWithCapacity:NUMBER_LAYER_COUNT]; for (NSUInteger i=1; i <= NUMBER_LAYER_COUNT; ++i) { [numberLayers addObject:number_layer(contentsScale, font, i)]; } _numberLayers = numberLayers; // hand layers _hourHandLayer = hand_layer(contentsScale); _minuteHandLayer = hand_layer(contentsScale); _secondHandLayer = hand_layer(contentsScale); // update sublayers NSMutableArray *sublayers = [NSMutableArray arrayWithObjects:_faceLayer, nil]; [sublayers addObjectsFromArray:_numberLayers]; [sublayers addObjectsFromArray:@[_largeDotLayer, _minuteHandLayer, _hourHandLayer, _secondHandLayer, _smallDotLayer]]; self.sublayers = sublayers; if (NULL != font) { CFRelease(font); } } return self; } #pragma mark - Properties @dynamic clockBackgroundColor; @dynamic clockForegroundColor; @dynamic clockAccentColor; - (void)setStyle:(NSDictionary *)style { super.style = style; [self _updatedStyle]; } - (void)setBounds:(CGRect)bounds { if (!CGRectEqualToRect(bounds, self.bounds)) { _radius = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds)) / 2.; _needsFullLayout = YES; super.bounds = bounds; } } - (void)setDate:(NSDate *)date { if (_date != date && ![_date isEqualToDate:date]) { _date = date; [self setNeedsLayout]; } } #pragma mark - Utilities - (void)_updatedStyle { CGColorRef backgroundColor = self.clockBackgroundColor.CGColor; CGColorRef foregroundColor = self.clockForegroundColor.CGColor; CGColorRef accentColor = self.clockAccentColor.CGColor; _faceLayer.fillColor = backgroundColor; _largeDotLayer.fillColor = foregroundColor; _smallDotLayer.fillColor = accentColor; [_numberLayers enumerateObjectsUsingBlock:^(CATextLayer *numberLayer, NSUInteger idx, BOOL *stop) { numberLayer.foregroundColor = foregroundColor; }]; _hourHandLayer.backgroundColor = foregroundColor; _minuteHandLayer.backgroundColor = foregroundColor; _secondHandLayer.backgroundColor = accentColor; } - (void)_rotateHandLayers { NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit fromDate:_date]; NSInteger minutesIntoDay = dateComponents.hour * 60 + dateComponents.minute; CGFloat percentMinutesIntoDay = (CGFloat)minutesIntoDay / (12.0 * 60.0); CGFloat percentMinutesIntoHour = (CGFloat)dateComponents.minute / 60.0; CGFloat percentSecondsIntoMinute = (CGFloat)dateComponents.second / 60.0; // XXX set fixed time // percentMinutesIntoDay = (10 * 60 + 12) / (12 * 60.); // percentMinutesIntoHour = 12.0 / 60.0; // percentSecondsIntoMinute = 47.0 / 60.0; _secondHandLayer.transform = CATransform3DMakeRotation(M_PI * 2 * percentSecondsIntoMinute, 0, 0, 1); _hourHandLayer.transform = CATransform3DMakeRotation(M_PI * 2 * percentMinutesIntoDay, 0, 0, 1); _minuteHandLayer.transform = CATransform3DMakeRotation(M_PI * 2 * percentMinutesIntoHour, 0, 0, 1); } - (void)_layoutElipseLayers { CGRect bounds = self.bounds; CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); _faceLayer.bounds = bounds; _faceLayer.position = center; _smallDotLayer.bounds = CGRectMake(0, 0, 3.0, 3.0); _smallDotLayer.position = center; _largeDotLayer.bounds = CGRectMake(0, 0, 9.0, 9.0); _largeDotLayer.position = center; } - (void)_layoutNumberLayers { // XXX document CGRect bounds = self.bounds; CGPoint p = CGPointMake(0, _radius - 14); CGFloat tickAngle = 2 * M_PI / NUMBER_LAYER_COUNT; [_numberLayers enumerateObjectsUsingBlock:^(CALayer *numberLayer, NSUInteger idx, BOOL *stop) { CGAffineTransform t = CGAffineTransformMakeRotation(7 * tickAngle + idx * tickAngle); CGPoint pp = CGPointApplyAffineTransform(p, t); pp.x += bounds.size.width / 2; pp.y += bounds.size.height / 2; numberLayer.position = pp; numberLayer.bounds = CGRectMake(0.0, 0.0, 18.0, 18.0); }]; } - (void)_layoutHandLayers { CGRect bounds = self.bounds; CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); _secondHandLayer.bounds = CGRectMake(0, 0, 1.0, SECOND_HAND_LENGTH * _radius); _secondHandLayer.anchorPoint = CGPointMake(0.5, 1); _secondHandLayer.position = center; _minuteHandLayer.bounds = CGRectMake(0, 0, 2.0, MINUTE_HAND_LENGTH * _radius); _minuteHandLayer.anchorPoint = CGPointMake(0.5, 1); _minuteHandLayer.position = center; _minuteHandLayer.cornerRadius = 1.5; _hourHandLayer.bounds = CGRectMake(0, 0, 3.0, HOUR_HAND_LENGTH * _radius); _hourHandLayer.anchorPoint = CGPointMake(0.5, 1); _hourHandLayer.position = center; _hourHandLayer.cornerRadius = 1.5; [self _rotateHandLayers]; } #pragma mark - Overides - (void)layoutSublayers { if (!_needsFullLayout) { [self _rotateHandLayers]; } else { [self _layoutElipseLayers]; [self _layoutNumberLayers]; [self _layoutHandLayers]; _needsFullLayout = NO; } } @end ================================================ FILE: Examples/Clock-iOS/ClockView.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @class Clock; enum { kClockViewStyleLight, kClockViewStyleDark, }; typedef NSUInteger ClockViewStyle; @interface ClockView : UIView - (instancetype)initWithClock:(Clock *)clock style:(ClockViewStyle)style; @end ================================================ FILE: Examples/Clock-iOS/ClockView.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "ClockView.h" #import "ClockLayer.h" #import #define CLOCK_LAYER(VIEW) ((ClockLayer *)VIEW.layer) static NSDictionary *layer_style(ClockViewStyle viewStyle) { NSDictionary *dict = nil; switch (viewStyle) { case kClockViewStyleLight: dict = [ClockLayer lightStyle]; break; case kClockViewStyleDark: dict = [ClockLayer darkStyle]; break; default: break; } return dict; } @implementation ClockView { FBKVOController *_KVOController; } + (Class)layerClass { return [ClockLayer class]; } - (instancetype)initWithClock:(Clock *)clock style:(ClockViewStyle)style { self = [super init]; if (nil != self) { CLOCK_LAYER(self).style = layer_style(style); // create KVO controller instance _KVOController = [FBKVOController controllerWithObserver:self]; // handle clock change, including initial value [_KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { // update observer with new value CLOCK_LAYER(clockView).date = change[NSKeyValueChangeNewKey]; }]; } return self; } @end ================================================ FILE: Examples/Clock-iOS/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "1x" }, { "idiom" : "ipad", "size" : "40x40", "scale" : "2x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "1x" }, { "idiom" : "ipad", "size" : "76x76", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/Clock-iOS/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Examples/Clock-iOS/ViewController.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import @interface ViewController : UIViewController @end ================================================ FILE: Examples/Clock-iOS/ViewController.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "ViewController.h" #import "Clock.h" #import "ClockView.h" #define CLOCK_VIEW_MAX_COUNT 10 #define CLOCK_VIEW_TIME_DELAY 3.0 #define RANDOM_ENABLED 1 @interface ViewController () @end @implementation ViewController { NSMutableArray *_clockViews; dispatch_source_t _timer; } - (void)dealloc { if (NULL != _timer) { dispatch_source_cancel(_timer); } } - (BOOL)prefersStatusBarHidden { return YES; } - (void)_addClockView { if (!_clockViews) { _clockViews = [NSMutableArray array]; } ClockView *clockView = [[ClockView alloc] initWithClock:[Clock clock] style:arc4random_uniform(kClockViewStyleDark+1)]; [_clockViews addObject:clockView]; [self.view addSubview:clockView]; clockView.bounds = CGRectMake(0, 0, 132, 132); CGRect contentBounds = self.view.bounds; #if RANDOM_ENABLED clockView.center = CGPointMake(arc4random_uniform(contentBounds.size.width), arc4random_uniform(contentBounds.size.height)); #else clockView.center = CGPointMake(contentBounds.size.width / 2., contentBounds.size.height / 2.); #endif } - (void)_removeClockView { if (0 == _clockViews.count) { return; } [_clockViews[0] removeFromSuperview]; [_clockViews removeObjectAtIndex:0]; } - (void)_removeAddClockView { [self _removeClockView]; [self _addClockView]; } - (void)_scheduleTimer { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, CLOCK_VIEW_TIME_DELAY * NSEC_PER_SEC), CLOCK_VIEW_TIME_DELAY * NSEC_PER_SEC, 1.0); __weak id weakSelf = self; dispatch_source_set_event_handler(timer, ^{ [weakSelf _removeAddClockView]; }); _timer = timer; #if RANDOM_ENABLED dispatch_resume(timer); #endif } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0]; while (_clockViews.count < CLOCK_VIEW_MAX_COUNT) { [self _addClockView]; } [self _scheduleTimer]; } @end ================================================ FILE: Examples/Clock-iOS/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Examples/Clock-iOS/main.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: Examples/Examples.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ EC45CACE18ADC7CE0063DD11 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC45CACD18ADC7CE0063DD11 /* Foundation.framework */; }; EC45CAD018ADC7CE0063DD11 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC45CACF18ADC7CE0063DD11 /* CoreGraphics.framework */; }; EC45CAD218ADC7CE0063DD11 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC45CAD118ADC7CE0063DD11 /* UIKit.framework */; }; EC45CAD818ADC7CE0063DD11 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC45CAD618ADC7CE0063DD11 /* InfoPlist.strings */; }; EC45CADA18ADC7CE0063DD11 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CAD918ADC7CE0063DD11 /* main.m */; }; EC45CADE18ADC7CE0063DD11 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CADD18ADC7CE0063DD11 /* AppDelegate.m */; }; EC45CAE118ADC7CE0063DD11 /* Main_iPhone.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EC45CADF18ADC7CE0063DD11 /* Main_iPhone.storyboard */; }; EC45CAE418ADC7CE0063DD11 /* Main_iPad.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EC45CAE218ADC7CE0063DD11 /* Main_iPad.storyboard */; }; EC45CAE718ADC7CE0063DD11 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CAE618ADC7CE0063DD11 /* ViewController.m */; }; EC45CAE918ADC7CE0063DD11 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC45CAE818ADC7CE0063DD11 /* Images.xcassets */; }; EC45CB0418ADC7E80063DD11 /* libFBKVOController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC45CB0318ADC7E80063DD11 /* libFBKVOController.a */; }; EC45CB7718ADD0B80063DD11 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC45CB0A18ADC8570063DD11 /* Cocoa.framework */; }; EC45CB7D18ADD0B80063DD11 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EC45CB7B18ADD0B80063DD11 /* InfoPlist.strings */; }; EC45CB7F18ADD0B80063DD11 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CB7E18ADD0B80063DD11 /* main.m */; }; EC45CB8318ADD0B80063DD11 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = EC45CB8118ADD0B80063DD11 /* Credits.rtf */; }; EC45CB8618ADD0B80063DD11 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CB8518ADD0B80063DD11 /* AppDelegate.m */; }; EC45CB8918ADD0B80063DD11 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC45CB8718ADD0B80063DD11 /* MainMenu.xib */; }; EC45CB8B18ADD0B80063DD11 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC45CB8A18ADD0B80063DD11 /* Images.xcassets */; }; EC45CBA518ADD0F60063DD11 /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CBA418ADD0F60063DD11 /* FBKVOController.m */; }; EC45CBA918ADD1A00063DD11 /* Clock.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CBA818ADD1A00063DD11 /* Clock.m */; }; EC45CBAA18ADD6BB0063DD11 /* Clock.m in Sources */ = {isa = PBXBuildFile; fileRef = EC45CBA818ADD1A00063DD11 /* Clock.m */; }; ECC5760818ADDE4E00C2BFB8 /* ClockView.m in Sources */ = {isa = PBXBuildFile; fileRef = ECC5760718ADDE4E00C2BFB8 /* ClockView.m */; }; ECC5761818ADF2F100C2BFB8 /* ClockView.m in Sources */ = {isa = PBXBuildFile; fileRef = ECC5761718ADF2F100C2BFB8 /* ClockView.m */; }; ECC5761B18ADF76500C2BFB8 /* ClockLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = ECC5761A18ADF76500C2BFB8 /* ClockLayer.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ EC45CACA18ADC7CE0063DD11 /* Clock-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; EC45CACD18ADC7CE0063DD11 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; EC45CACF18ADC7CE0063DD11 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; EC45CAD118ADC7CE0063DD11 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; EC45CAD518ADC7CE0063DD11 /* Clock-iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Clock-iOS-Info.plist"; sourceTree = ""; }; EC45CAD718ADC7CE0063DD11 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; EC45CAD918ADC7CE0063DD11 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; EC45CADB18ADC7CE0063DD11 /* Clock-iOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock-iOS-Prefix.pch"; sourceTree = ""; }; EC45CADC18ADC7CE0063DD11 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; EC45CADD18ADC7CE0063DD11 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; EC45CAE018ADC7CE0063DD11 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPhone.storyboard; sourceTree = ""; }; EC45CAE318ADC7CE0063DD11 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main_iPad.storyboard; sourceTree = ""; }; EC45CAE518ADC7CE0063DD11 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; EC45CAE618ADC7CE0063DD11 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; EC45CAE818ADC7CE0063DD11 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; EC45CAEF18ADC7CE0063DD11 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; EC45CB0318ADC7E80063DD11 /* libFBKVOController.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libFBKVOController.a; path = "../../../Library/Developer/Xcode/DerivedData/FBKVOController-amnevtoymcgzwwazjmzbwvbeduoi/Build/Products/Debug-iphoneos/libFBKVOController.a"; sourceTree = ""; }; EC45CB0A18ADC8570063DD11 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; EC45CB0D18ADC8570063DD11 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; EC45CB0E18ADC8570063DD11 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; EC45CB0F18ADC8570063DD11 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; EC45CB7618ADD0B80063DD11 /* Clock-OSX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clock-OSX.app"; sourceTree = BUILT_PRODUCTS_DIR; }; EC45CB7A18ADD0B80063DD11 /* Clock-OSX-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Clock-OSX-Info.plist"; sourceTree = ""; }; EC45CB7C18ADD0B80063DD11 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; EC45CB7E18ADD0B80063DD11 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; EC45CB8018ADD0B80063DD11 /* Clock-OSX-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock-OSX-Prefix.pch"; sourceTree = ""; }; EC45CB8218ADD0B80063DD11 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; EC45CB8418ADD0B80063DD11 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; EC45CB8518ADD0B80063DD11 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; EC45CB8818ADD0B80063DD11 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; EC45CB8A18ADD0B80063DD11 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; EC45CBA318ADD0F60063DD11 /* FBKVOController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FBKVOController.h; path = ../../FBKVOController/FBKVOController.h; sourceTree = ""; }; EC45CBA418ADD0F60063DD11 /* FBKVOController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FBKVOController.m; path = ../../FBKVOController/FBKVOController.m; sourceTree = ""; }; EC45CBA718ADD1A00063DD11 /* Clock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Clock.h; path = "Clock-iOS/Clock.h"; sourceTree = ""; }; EC45CBA818ADD1A00063DD11 /* Clock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Clock.m; path = "Clock-iOS/Clock.m"; sourceTree = ""; }; ECC5760618ADDE4E00C2BFB8 /* ClockView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClockView.h; sourceTree = ""; }; ECC5760718ADDE4E00C2BFB8 /* ClockView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClockView.m; sourceTree = ""; }; ECC5761618ADF2F100C2BFB8 /* ClockView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClockView.h; sourceTree = ""; }; ECC5761718ADF2F100C2BFB8 /* ClockView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClockView.m; sourceTree = ""; }; ECC5761918ADF76500C2BFB8 /* ClockLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClockLayer.h; sourceTree = ""; }; ECC5761A18ADF76500C2BFB8 /* ClockLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClockLayer.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ EC45CAC718ADC7CE0063DD11 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( EC45CB0418ADC7E80063DD11 /* libFBKVOController.a in Frameworks */, EC45CAD018ADC7CE0063DD11 /* CoreGraphics.framework in Frameworks */, EC45CAD218ADC7CE0063DD11 /* UIKit.framework in Frameworks */, EC45CACE18ADC7CE0063DD11 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; EC45CB7318ADD0B80063DD11 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( EC45CB7718ADD0B80063DD11 /* Cocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ EC45CABF18ADC7920063DD11 = { isa = PBXGroup; children = ( ECC5761518ADEDFC00C2BFB8 /* Clock */, EC45CACC18ADC7CE0063DD11 /* Frameworks */, EC45CACB18ADC7CE0063DD11 /* Products */, ); sourceTree = ""; }; EC45CACB18ADC7CE0063DD11 /* Products */ = { isa = PBXGroup; children = ( EC45CACA18ADC7CE0063DD11 /* Clock-iOS.app */, EC45CB7618ADD0B80063DD11 /* Clock-OSX.app */, ); name = Products; sourceTree = ""; }; EC45CACC18ADC7CE0063DD11 /* Frameworks */ = { isa = PBXGroup; children = ( EC45CB0318ADC7E80063DD11 /* libFBKVOController.a */, EC45CACD18ADC7CE0063DD11 /* Foundation.framework */, EC45CACF18ADC7CE0063DD11 /* CoreGraphics.framework */, EC45CAD118ADC7CE0063DD11 /* UIKit.framework */, EC45CAEF18ADC7CE0063DD11 /* XCTest.framework */, EC45CB0A18ADC8570063DD11 /* Cocoa.framework */, EC45CB0C18ADC8570063DD11 /* Other Frameworks */, ); name = Frameworks; sourceTree = ""; }; EC45CAD318ADC7CE0063DD11 /* Clock-iOS */ = { isa = PBXGroup; children = ( EC45CADC18ADC7CE0063DD11 /* AppDelegate.h */, EC45CADD18ADC7CE0063DD11 /* AppDelegate.m */, EC45CADF18ADC7CE0063DD11 /* Main_iPhone.storyboard */, EC45CAE218ADC7CE0063DD11 /* Main_iPad.storyboard */, EC45CAE518ADC7CE0063DD11 /* ViewController.h */, EC45CAE618ADC7CE0063DD11 /* ViewController.m */, ECC5761618ADF2F100C2BFB8 /* ClockView.h */, ECC5761718ADF2F100C2BFB8 /* ClockView.m */, ECC5761918ADF76500C2BFB8 /* ClockLayer.h */, ECC5761A18ADF76500C2BFB8 /* ClockLayer.m */, EC45CAE818ADC7CE0063DD11 /* Images.xcassets */, EC45CAD418ADC7CE0063DD11 /* Supporting Files */, ); path = "Clock-iOS"; sourceTree = ""; }; EC45CAD418ADC7CE0063DD11 /* Supporting Files */ = { isa = PBXGroup; children = ( EC45CAD518ADC7CE0063DD11 /* Clock-iOS-Info.plist */, EC45CAD618ADC7CE0063DD11 /* InfoPlist.strings */, EC45CAD918ADC7CE0063DD11 /* main.m */, EC45CADB18ADC7CE0063DD11 /* Clock-iOS-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; EC45CB0C18ADC8570063DD11 /* Other Frameworks */ = { isa = PBXGroup; children = ( EC45CB0D18ADC8570063DD11 /* AppKit.framework */, EC45CB0E18ADC8570063DD11 /* CoreData.framework */, EC45CB0F18ADC8570063DD11 /* Foundation.framework */, ); name = "Other Frameworks"; sourceTree = ""; }; EC45CB7818ADD0B80063DD11 /* Clock-OSX */ = { isa = PBXGroup; children = ( EC45CB8418ADD0B80063DD11 /* AppDelegate.h */, EC45CB8518ADD0B80063DD11 /* AppDelegate.m */, ECC5760618ADDE4E00C2BFB8 /* ClockView.h */, ECC5760718ADDE4E00C2BFB8 /* ClockView.m */, EC45CBA318ADD0F60063DD11 /* FBKVOController.h */, EC45CBA418ADD0F60063DD11 /* FBKVOController.m */, EC45CB8718ADD0B80063DD11 /* MainMenu.xib */, EC45CB8A18ADD0B80063DD11 /* Images.xcassets */, EC45CB7918ADD0B80063DD11 /* Supporting Files */, ); path = "Clock-OSX"; sourceTree = ""; }; EC45CB7918ADD0B80063DD11 /* Supporting Files */ = { isa = PBXGroup; children = ( EC45CB7A18ADD0B80063DD11 /* Clock-OSX-Info.plist */, EC45CB7B18ADD0B80063DD11 /* InfoPlist.strings */, EC45CB7E18ADD0B80063DD11 /* main.m */, EC45CB8018ADD0B80063DD11 /* Clock-OSX-Prefix.pch */, EC45CB8118ADD0B80063DD11 /* Credits.rtf */, ); name = "Supporting Files"; sourceTree = ""; }; ECC5761518ADEDFC00C2BFB8 /* Clock */ = { isa = PBXGroup; children = ( EC45CBA718ADD1A00063DD11 /* Clock.h */, EC45CBA818ADD1A00063DD11 /* Clock.m */, EC45CAD318ADC7CE0063DD11 /* Clock-iOS */, EC45CB7818ADD0B80063DD11 /* Clock-OSX */, ); name = Clock; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ EC45CAC918ADC7CE0063DD11 /* Clock-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = EC45CAFD18ADC7CE0063DD11 /* Build configuration list for PBXNativeTarget "Clock-iOS" */; buildPhases = ( EC45CAC618ADC7CE0063DD11 /* Sources */, EC45CAC718ADC7CE0063DD11 /* Frameworks */, EC45CAC818ADC7CE0063DD11 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Clock-iOS"; productName = "Clock-iOS"; productReference = EC45CACA18ADC7CE0063DD11 /* Clock-iOS.app */; productType = "com.apple.product-type.application"; }; EC45CB7518ADD0B80063DD11 /* Clock-OSX */ = { isa = PBXNativeTarget; buildConfigurationList = EC45CB9D18ADD0B80063DD11 /* Build configuration list for PBXNativeTarget "Clock-OSX" */; buildPhases = ( EC45CB7218ADD0B80063DD11 /* Sources */, EC45CB7318ADD0B80063DD11 /* Frameworks */, EC45CB7418ADD0B80063DD11 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Clock-OSX"; productName = "Clock-OSX"; productReference = EC45CB7618ADD0B80063DD11 /* Clock-OSX.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ EC45CAC018ADC7920063DD11 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0730; }; buildConfigurationList = EC45CAC318ADC7920063DD11 /* Build configuration list for PBXProject "Examples" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = EC45CABF18ADC7920063DD11; productRefGroup = EC45CACB18ADC7CE0063DD11 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( EC45CAC918ADC7CE0063DD11 /* Clock-iOS */, EC45CB7518ADD0B80063DD11 /* Clock-OSX */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ EC45CAC818ADC7CE0063DD11 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( EC45CAE418ADC7CE0063DD11 /* Main_iPad.storyboard in Resources */, EC45CAE918ADC7CE0063DD11 /* Images.xcassets in Resources */, EC45CAE118ADC7CE0063DD11 /* Main_iPhone.storyboard in Resources */, EC45CAD818ADC7CE0063DD11 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; EC45CB7418ADD0B80063DD11 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( EC45CB7D18ADD0B80063DD11 /* InfoPlist.strings in Resources */, EC45CB8B18ADD0B80063DD11 /* Images.xcassets in Resources */, EC45CB8318ADD0B80063DD11 /* Credits.rtf in Resources */, EC45CB8918ADD0B80063DD11 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ EC45CAC618ADC7CE0063DD11 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ECC5761B18ADF76500C2BFB8 /* ClockLayer.m in Sources */, EC45CAE718ADC7CE0063DD11 /* ViewController.m in Sources */, ECC5761818ADF2F100C2BFB8 /* ClockView.m in Sources */, EC45CADE18ADC7CE0063DD11 /* AppDelegate.m in Sources */, EC45CBA918ADD1A00063DD11 /* Clock.m in Sources */, EC45CADA18ADC7CE0063DD11 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; EC45CB7218ADD0B80063DD11 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( EC45CB8618ADD0B80063DD11 /* AppDelegate.m in Sources */, ECC5760818ADDE4E00C2BFB8 /* ClockView.m in Sources */, EC45CBA518ADD0F60063DD11 /* FBKVOController.m in Sources */, EC45CBAA18ADD6BB0063DD11 /* Clock.m in Sources */, EC45CB7F18ADD0B80063DD11 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ EC45CAD618ADC7CE0063DD11 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( EC45CAD718ADC7CE0063DD11 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; EC45CADF18ADC7CE0063DD11 /* Main_iPhone.storyboard */ = { isa = PBXVariantGroup; children = ( EC45CAE018ADC7CE0063DD11 /* Base */, ); name = Main_iPhone.storyboard; sourceTree = ""; }; EC45CAE218ADC7CE0063DD11 /* Main_iPad.storyboard */ = { isa = PBXVariantGroup; children = ( EC45CAE318ADC7CE0063DD11 /* Base */, ); name = Main_iPad.storyboard; sourceTree = ""; }; EC45CB7B18ADD0B80063DD11 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( EC45CB7C18ADD0B80063DD11 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; EC45CB8118ADD0B80063DD11 /* Credits.rtf */ = { isa = PBXVariantGroup; children = ( EC45CB8218ADD0B80063DD11 /* en */, ); name = Credits.rtf; sourceTree = ""; }; EC45CB8718ADD0B80063DD11 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( EC45CB8818ADD0B80063DD11 /* Base */, ); name = MainMenu.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ EC45CAC418ADC7920063DD11 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ENABLE_TESTABILITY = YES; ONLY_ACTIVE_ARCH = YES; }; name = Debug; }; EC45CAC518ADC7920063DD11 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { }; name = Release; }; EC45CAFE18ADC7CE0063DD11 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clock-iOS/Clock-iOS-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Clock-iOS/Clock-iOS-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(BUILT_PRODUCTS_DIR)", ); ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; }; name = Debug; }; EC45CAFF18ADC7CE0063DD11 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(DEVELOPER_FRAMEWORKS_DIR)", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clock-iOS/Clock-iOS-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Clock-iOS/Clock-iOS-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 7.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(BUILT_PRODUCTS_DIR)", ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; WRAPPER_EXTENSION = app; }; name = Release; }; EC45CB9E18ADD0B80063DD11 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clock-OSX/Clock-OSX-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Clock-OSX/Clock-OSX-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; WRAPPER_EXTENSION = app; }; name = Debug; }; EC45CB9F18ADD0B80063DD11 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Clock-OSX/Clock-OSX-Prefix.pch"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = "Clock-OSX/Clock-OSX-Info.plist"; MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ EC45CAC318ADC7920063DD11 /* Build configuration list for PBXProject "Examples" */ = { isa = XCConfigurationList; buildConfigurations = ( EC45CAC418ADC7920063DD11 /* Debug */, EC45CAC518ADC7920063DD11 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; EC45CAFD18ADC7CE0063DD11 /* Build configuration list for PBXNativeTarget "Clock-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( EC45CAFE18ADC7CE0063DD11 /* Debug */, EC45CAFF18ADC7CE0063DD11 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; EC45CB9D18ADD0B80063DD11 /* Build configuration list for PBXNativeTarget "Clock-OSX" */ = { isa = XCConfigurationList; buildConfigurations = ( EC45CB9E18ADD0B80063DD11 /* Debug */, EC45CB9F18ADD0B80063DD11 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = EC45CAC018ADC7920063DD11 /* Project object */; } ================================================ FILE: FBKVOController/FBKVOController.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import /** This macro ensures that key path exists at compile time. Given a real receiver with a key path as you would call it, it verifies at compile time that the key path exists, without calling it. For example: FBKVOKeyPath(string.length) => @"length" Or even the complex case: FBKVOKeyPath(string.lowercaseString.length) => @"lowercaseString.length". */ #define FBKVOKeyPath(KEYPATH) \ @(((void)(NO && ((void)KEYPATH, NO)), \ ({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; }))) /** This macro ensures that key path exists at compile time. Given a receiver type and a key path, it verifies at compile time that the key path exists, without calling it. For example: FBKVOClassKeyPath(NSString, length) => @"length" FBKVOClassKeyPath(NSString, lowercaseString.length) => @"lowercaseString.length" */ #define FBKVOClassKeyPath(CLASS, KEYPATH) \ @(((void)(NO && ((void)((CLASS *)(nil)).KEYPATH, NO)), #KEYPATH)) NS_ASSUME_NONNULL_BEGIN /** Key provided in the @c change dictionary of @c FBKVONotificationBlock that's value represents the key-path being observed */ extern NSString *const FBKVONotificationKeyPathKey; /** @abstract Block called on key-value change notification. @param observer The observer of the change. @param object The object changed. @param change The change dictionary which also includes @c FBKVONotificationKeyPathKey */ typedef void (^FBKVONotificationBlock)(id _Nullable observer, id object, NSDictionary *change); /** @abstract FBKVOController makes Key-Value Observing simpler and safer. @discussion FBKVOController adds support for handling key-value changes with blocks and custom actions, as well as the NSKeyValueObserving callback. Notification will never message a deallocated observer. Observer removal never throws exceptions, and observers are removed implicitly on controller deallocation. FBKVOController is also thread safe. When used in a concurrent environment, it protects observers from possible resurrection and avoids ensuing crash. By default, the controller maintains a strong reference to objects observed. */ @interface FBKVOController : NSObject ///-------------------------------------- #pragma mark - Initialize ///-------------------------------------- /** @abstract Creates and returns an initialized KVO controller instance. @param observer The object notified on key-value change. @return The initialized KVO controller instance. */ + (instancetype)controllerWithObserver:(nullable id)observer; /** @abstract The designated initializer. @param observer The object notified on key-value change. The specified observer must support weak references. @param retainObserved Flag indicating whether observed objects should be retained. @return The initialized KVO controller instance. @discussion Use retainObserved = NO when a strong reference between controller and observee would create a retain loop. When not retaining observees, special care must be taken to remove observation info prior to observee dealloc. */ - (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER; /** @abstract Convenience initializer. @param observer The object notified on key-value change. The specified observer must support weak references. @return The initialized KVO controller instance. @discussion By default, KVO controller retains objects observed. */ - (instancetype)initWithObserver:(nullable id)observer; /** @abstract Initializes a new instance. @warning This method is unavaialble. Please use `initWithObserver:` instead. */ - (instancetype)init NS_UNAVAILABLE; /** @abstract Allocates memory and initializes a new instance into it. @warning This method is unavaialble. Please use `controllerWithObserver:` instead. */ + (instancetype)new NS_UNAVAILABLE; ///-------------------------------------- #pragma mark - Observe ///-------------------------------------- /** The observer notified on key-value change. Specified on initialization. */ @property (nullable, nonatomic, weak, readonly) id observer; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPath The key path to observe. @param options The NSKeyValueObservingOptions to use for observation. @param block The block to execute on notification. @discussion On key-value change, the specified block is called. In order to avoid retain loops, the block must avoid referencing the KVO controller or an owner thereof. Observing an already observed object key path or nil results in no operation. */ - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPath The key path to observe. @param options The NSKeyValueObservingOptions to use for observation. @param action The observer selector called on key-value change. @discussion On key-value change, the observer's action selector is called. The selector provided should take the form of -propertyDidChange, -propertyDidChange: or -propertyDidChange:object:, where optional parameters delivered will be KVO change dictionary and object observed. Observing nil or observing an already observed object's key path results in no operation. */ - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPath The key path to observe. @param options The NSKeyValueObservingOptions to use for observation. @param context The context specified. @discussion On key-value change, the observer's -observeValueForKeyPath:ofObject:change:context: method is called. Observing an already observed object key path or nil results in no operation. */ - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPaths The key paths to observe. @param options The NSKeyValueObservingOptions to use for observation. @param block The block to execute on notification. @discussion On key-value change, the specified block is called. Inorder to avoid retain loops, the block must avoid referencing the KVO controller or an owner thereof. Observing an already observed object key path or nil results in no operation. */ - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPaths The key paths to observe. @param options The NSKeyValueObservingOptions to use for observation. @param action The observer selector called on key-value change. @discussion On key-value change, the observer's action selector is called. The selector provided should take the form of -propertyDidChange, -propertyDidChange: or -propertyDidChange:object:, where optional parameters delivered will be KVO change dictionary and object observed. Observing nil or observing an already observed object's key path results in no operation. */ - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action; /** @abstract Registers observer for key-value change notification. @param object The object to observe. @param keyPaths The key paths to observe. @param options The NSKeyValueObservingOptions to use for observation. @param context The context specified. @discussion On key-value change, the observer's -observeValueForKeyPath:ofObject:change:context: method is called. Observing an already observed object key path or nil results in no operation. */ - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options context:(nullable void *)context; ///-------------------------------------- #pragma mark - Unobserve ///-------------------------------------- /** @abstract Unobserve object key path. @param object The object to unobserve. @param keyPath The key path to observe. @discussion If not observing object key path, or unobserving nil, this method results in no operation. */ - (void)unobserve:(nullable id)object keyPath:(NSString *)keyPath; /** @abstract Unobserve all object key paths. @param object The object to unobserve. @discussion If not observing object, or unobserving nil, this method results in no operation. */ - (void)unobserve:(nullable id)object; /** @abstract Unobserve all objects. @discussion If not observing any objects, this method results in no operation. */ - (void)unobserveAll; @end NS_ASSUME_NONNULL_END ================================================ FILE: FBKVOController/FBKVOController.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "FBKVOController.h" #import #import #if !__has_feature(objc_arc) #error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag. #endif NS_ASSUME_NONNULL_BEGIN #pragma mark Utilities - static NSString *describe_option(NSKeyValueObservingOptions option) { switch (option) { case NSKeyValueObservingOptionNew: return @"NSKeyValueObservingOptionNew"; break; case NSKeyValueObservingOptionOld: return @"NSKeyValueObservingOptionOld"; break; case NSKeyValueObservingOptionInitial: return @"NSKeyValueObservingOptionInitial"; break; case NSKeyValueObservingOptionPrior: return @"NSKeyValueObservingOptionPrior"; break; default: NSCAssert(NO, @"unexpected option %tu", option); break; } return nil; } static void append_option_description(NSMutableString *s, NSUInteger option) { if (0 == s.length) { [s appendString:describe_option(option)]; } else { [s appendString:@"|"]; [s appendString:describe_option(option)]; } } static NSUInteger enumerate_flags(NSUInteger *ptrFlags) { NSCAssert(ptrFlags, @"expected ptrFlags"); if (!ptrFlags) { return 0; } NSUInteger flags = *ptrFlags; if (!flags) { return 0; } NSUInteger flag = 1 << __builtin_ctzl(flags); flags &= ~flag; *ptrFlags = flags; return flag; } static NSString *describe_options(NSKeyValueObservingOptions options) { NSMutableString *s = [NSMutableString string]; NSUInteger option; while (0 != (option = enumerate_flags(&options))) { append_option_description(s, option); } return s; } #pragma mark _FBKVOInfo - typedef NS_ENUM(uint8_t, _FBKVOInfoState) { _FBKVOInfoStateInitial = 0, // whether the observer registration in Foundation has completed _FBKVOInfoStateObserving, // whether `unobserve` was called before observer registration in Foundation has completed // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions _FBKVOInfoStateNotObserving, }; NSString *const FBKVONotificationKeyPathKey = @"FBKVONotificationKeyPathKey"; /** @abstract The key-value observation info. @discussion Object equality is only used within the scope of a controller instance. Safely omit controller from equality definition. */ @interface _FBKVOInfo : NSObject @end @implementation _FBKVOInfo { @public __weak FBKVOController *_controller; NSString *_keyPath; NSKeyValueObservingOptions _options; SEL _action; void *_context; FBKVONotificationBlock _block; _FBKVOInfoState _state; } - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(nullable FBKVONotificationBlock)block action:(nullable SEL)action context:(nullable void *)context { self = [super init]; if (nil != self) { _controller = controller; _block = [block copy]; _keyPath = [keyPath copy]; _options = options; _action = action; _context = context; } return self; } - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block { return [self initWithController:controller keyPath:keyPath options:options block:block action:NULL context:NULL]; } - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action { return [self initWithController:controller keyPath:keyPath options:options block:NULL action:action context:NULL]; } - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { return [self initWithController:controller keyPath:keyPath options:options block:NULL action:NULL context:context]; } - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath { return [self initWithController:controller keyPath:keyPath options:0 block:NULL action:NULL context:NULL]; } - (NSUInteger)hash { return [_keyPath hash]; } - (BOOL)isEqual:(id)object { if (nil == object) { return NO; } if (self == object) { return YES; } if (![object isKindOfClass:[self class]]) { return NO; } return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath]; } - (NSString *)debugDescription { NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p keyPath:%@", NSStringFromClass([self class]), self, _keyPath]; if (0 != _options) { [s appendFormat:@" options:%@", describe_options(_options)]; } if (NULL != _action) { [s appendFormat:@" action:%@", NSStringFromSelector(_action)]; } if (NULL != _context) { [s appendFormat:@" context:%p", _context]; } if (NULL != _block) { [s appendFormat:@" block:%p", _block]; } [s appendString:@">"]; return s; } @end #pragma mark _FBKVOSharedController - /** @abstract The shared KVO controller instance. @discussion Acts as a receptionist, receiving and forwarding KVO notifications. */ @interface _FBKVOSharedController : NSObject /** A shared instance that never deallocates. */ + (instancetype)sharedController; /** observe an object, info pair */ - (void)observe:(id)object info:(nullable _FBKVOInfo *)info; /** unobserve an object, info pair */ - (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info; /** unobserve an object with a set of infos */ - (void)unobserve:(id)object infos:(nullable NSSet *)infos; @end @implementation _FBKVOSharedController { NSHashTable<_FBKVOInfo *> *_infos; pthread_mutex_t _mutex; } + (instancetype)sharedController { static _FBKVOSharedController *_controller = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _controller = [[_FBKVOSharedController alloc] init]; }); return _controller; } - (instancetype)init { self = [super init]; if (nil != self) { NSHashTable *infos = [NSHashTable alloc]; #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0]; #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) { _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0]; } else { // silence deprecated warnings #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" _infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0]; #pragma clang diagnostic pop } #endif pthread_mutex_init(&_mutex, NULL); } return self; } - (void)dealloc { pthread_mutex_destroy(&_mutex); } - (NSString *)debugDescription { NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self]; // lock pthread_mutex_lock(&_mutex); NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:_infos.count]; for (_FBKVOInfo *info in _infos) { [infoDescriptions addObject:info.debugDescription]; } [s appendFormat:@" contexts:%@", infoDescriptions]; // unlock pthread_mutex_unlock(&_mutex); [s appendString:@">"]; return s; } - (void)observe:(id)object info:(nullable _FBKVOInfo *)info { if (nil == info) { return; } // register info pthread_mutex_lock(&_mutex); [_infos addObject:info]; pthread_mutex_unlock(&_mutex); // add observer [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info]; if (info->_state == _FBKVOInfoStateInitial) { info->_state = _FBKVOInfoStateObserving; } else if (info->_state == _FBKVOInfoStateNotObserving) { // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions, // and the observer is unregistered within the callback block. // at this time the object has been registered as an observer (in Foundation KVO), // so we can safely unobserve it. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } } - (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info { if (nil == info) { return; } // unregister info pthread_mutex_lock(&_mutex); [_infos removeObject:info]; pthread_mutex_unlock(&_mutex); // remove observer if (info->_state == _FBKVOInfoStateObserving) { [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } info->_state = _FBKVOInfoStateNotObserving; } - (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos { if (0 == infos.count) { return; } // unregister info pthread_mutex_lock(&_mutex); for (_FBKVOInfo *info in infos) { [_infos removeObject:info]; } pthread_mutex_unlock(&_mutex); // remove observer for (_FBKVOInfo *info in infos) { if (info->_state == _FBKVOInfoStateObserving) { [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } info->_state = _FBKVOInfoStateNotObserving; } } - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void *)context { NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change); _FBKVOInfo *info; { // lookup context in registered infos, taking out a strong reference only if it exists pthread_mutex_lock(&_mutex); info = [_infos member:(__bridge id)context]; pthread_mutex_unlock(&_mutex); } if (nil != info) { // take strong reference to controller FBKVOController *controller = info->_controller; if (nil != controller) { // take strong reference to observer id observer = controller.observer; if (nil != observer) { // dispatch custom block or action, fall back to default action if (info->_block) { NSDictionary *changeWithKeyPath = change; // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed if (keyPath) { NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey]; [mChange addEntriesFromDictionary:change]; changeWithKeyPath = [mChange copy]; } info->_block(observer, object, changeWithKeyPath); } else if (info->_action) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [observer performSelector:info->_action withObject:change withObject:object]; #pragma clang diagnostic pop } else { [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context]; } } } } } @end #pragma mark FBKVOController - @implementation FBKVOController { NSMapTable *> *_objectInfosMap; pthread_mutex_t _lock; } #pragma mark Lifecycle - + (instancetype)controllerWithObserver:(nullable id)observer { return [[self alloc] initWithObserver:observer]; } - (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved { self = [super init]; if (nil != self) { _observer = observer; NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality; _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0]; pthread_mutex_init(&_lock, NULL); } return self; } - (instancetype)initWithObserver:(nullable id)observer { return [self initWithObserver:observer retainObserved:YES]; } - (void)dealloc { [self unobserveAll]; pthread_mutex_destroy(&_lock); } #pragma mark Properties - - (NSString *)debugDescription { NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self]; [s appendFormat:@" observer:<%@:%p>", NSStringFromClass([_observer class]), _observer]; // lock pthread_mutex_lock(&_lock); if (0 != _objectInfosMap.count) { [s appendString:@"\n "]; } for (id object in _objectInfosMap) { NSMutableSet *infos = [_objectInfosMap objectForKey:object]; NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:infos.count]; [infos enumerateObjectsUsingBlock:^(_FBKVOInfo *info, BOOL *stop) { [infoDescriptions addObject:info.debugDescription]; }]; [s appendFormat:@"%@ -> %@", object, infoDescriptions]; } // unlock pthread_mutex_unlock(&_lock); [s appendString:@">"]; return s; } #pragma mark Utilities - - (void)_observe:(id)object info:(_FBKVOInfo *)info { // lock pthread_mutex_lock(&_lock); NSMutableSet *infos = [_objectInfosMap objectForKey:object]; // check for info existence _FBKVOInfo *existingInfo = [infos member:info]; if (nil != existingInfo) { // observation info already exists; do not observe it again // unlock and return pthread_mutex_unlock(&_lock); return; } // lazilly create set of infos if (nil == infos) { infos = [NSMutableSet set]; [_objectInfosMap setObject:infos forKey:object]; } // add info and oberve [infos addObject:info]; // unlock prior to callout pthread_mutex_unlock(&_lock); [[_FBKVOSharedController sharedController] observe:object info:info]; } - (void)_unobserve:(id)object info:(_FBKVOInfo *)info { // lock pthread_mutex_lock(&_lock); // get observation infos NSMutableSet *infos = [_objectInfosMap objectForKey:object]; // lookup registered info instance _FBKVOInfo *registeredInfo = [infos member:info]; if (nil != registeredInfo) { [infos removeObject:registeredInfo]; // remove no longer used infos if (0 == infos.count) { [_objectInfosMap removeObjectForKey:object]; } } // unlock pthread_mutex_unlock(&_lock); // unobserve [[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo]; } - (void)_unobserve:(id)object { // lock pthread_mutex_lock(&_lock); NSMutableSet *infos = [_objectInfosMap objectForKey:object]; // remove infos [_objectInfosMap removeObjectForKey:object]; // unlock pthread_mutex_unlock(&_lock); // unobserve [[_FBKVOSharedController sharedController] unobserve:object infos:infos]; } - (void)_unobserveAll { // lock pthread_mutex_lock(&_lock); NSMapTable *objectInfoMaps = [_objectInfosMap copy]; // clear table and map [_objectInfosMap removeAllObjects]; // unlock pthread_mutex_unlock(&_lock); _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController]; for (id object in objectInfoMaps) { // unobserve each registered object and infos NSSet *infos = [objectInfoMaps objectForKey:object]; [shareController unobserve:object infos:infos]; } } #pragma mark API - - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block { NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block); if (nil == object || 0 == keyPath.length || NULL == block) { return; } // create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block]; // observe object with info [self _observe:object info:info]; } - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block { NSAssert(0 != keyPaths.count && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPaths, block); if (nil == object || 0 == keyPaths.count || NULL == block) { return; } for (NSString *keyPath in keyPaths) { [self observe:object keyPath:keyPath options:options block:block]; } } - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action { NSAssert(0 != keyPath.length && NULL != action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPath, NSStringFromSelector(action)); NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action)); if (nil == object || 0 == keyPath.length || NULL == action) { return; } // create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options action:action]; // observe object with info [self _observe:object info:info]; } - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action { NSAssert(0 != keyPaths.count && NULL != action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPaths, NSStringFromSelector(action)); NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action)); if (nil == object || 0 == keyPaths.count || NULL == action) { return; } for (NSString *keyPath in keyPaths) { [self observe:object keyPath:keyPath options:options action:action]; } } - (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context { NSAssert(0 != keyPath.length, @"missing required parameters observe:%@ keyPath:%@", object, keyPath); if (nil == object || 0 == keyPath.length) { return; } // create info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options context:context]; // observe object with info [self _observe:object info:info]; } - (void)observe:(nullable id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options context:(nullable void *)context { NSAssert(0 != keyPaths.count, @"missing required parameters observe:%@ keyPath:%@", object, keyPaths); if (nil == object || 0 == keyPaths.count) { return; } for (NSString *keyPath in keyPaths) { [self observe:object keyPath:keyPath options:options context:context]; } } - (void)unobserve:(nullable id)object keyPath:(NSString *)keyPath { // create representative info _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath]; // unobserve object property [self _unobserve:object info:info]; } - (void)unobserve:(nullable id)object { if (nil == object) { return; } [self _unobserve:object]; } - (void)unobserveAll { [self _unobserveAll]; } @end NS_ASSUME_NONNULL_END ================================================ FILE: FBKVOController/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.2.0 CFBundleSignature ???? CFBundleVersion 1.2.0 NSPrincipalClass ================================================ FILE: FBKVOController/KVOController.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import #import ================================================ FILE: FBKVOController/NSObject+FBKVOController.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import #import "FBKVOController.h" NS_ASSUME_NONNULL_BEGIN /** Category that adds built-in `KVOController` and `KVOControllerNonRetaining` on any instance of `NSObject`. This makes it convenient to simply create and forget a `FBKVOController`, and when this object gets dealloc'd, so will the associated controller and the observation info. */ @interface NSObject (FBKVOController) /** @abstract Lazy-loaded FBKVOController for use with any object @return FBKVOController associated with this object, creating one if necessary @discussion This makes it convenient to simply create and forget a FBKVOController, and when this object gets dealloc'd, so will the associated controller and the observation info. */ @property (nonatomic, strong) FBKVOController *KVOController; /** @abstract Lazy-loaded FBKVOController for use with any object @return FBKVOController associated with this object, creating one if necessary @discussion This makes it convenient to simply create and forget a FBKVOController. Use this version when a strong reference between controller and observed object would create a retain cycle. When not retaining observed objects, special care must be taken to remove observation info prior to deallocation of the observed object. */ @property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining; @end NS_ASSUME_NONNULL_END ================================================ FILE: FBKVOController/NSObject+FBKVOController.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "NSObject+FBKVOController.h" #import #if !__has_feature(objc_arc) #error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag. #endif #pragma mark NSObject Category - NS_ASSUME_NONNULL_BEGIN static void *NSObjectKVOControllerKey = &NSObjectKVOControllerKey; static void *NSObjectKVOControllerNonRetainingKey = &NSObjectKVOControllerNonRetainingKey; @implementation NSObject (FBKVOController) - (FBKVOController *)KVOController { id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey); // lazily create the KVOController if (nil == controller) { controller = [FBKVOController controllerWithObserver:self]; self.KVOController = controller; } return controller; } - (void)setKVOController:(FBKVOController *)KVOController { objc_setAssociatedObject(self, NSObjectKVOControllerKey, KVOController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (FBKVOController *)KVOControllerNonRetaining { id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey); if (nil == controller) { controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO]; self.KVOControllerNonRetaining = controller; } return controller; } - (void)setKVOControllerNonRetaining:(FBKVOController *)KVOControllerNonRetaining { objc_setAssociatedObject(self, NSObjectKVOControllerNonRetainingKey, KVOControllerNonRetaining, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end NS_ASSUME_NONNULL_END ================================================ FILE: FBKVOController.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXAggregateTarget section */ ECEA612C18A4A7360064AFF4 /* Framework */ = { isa = PBXAggregateTarget; buildConfigurationList = ECEA612D18A4A7360064AFF4 /* Build configuration list for PBXAggregateTarget "Framework" */; buildPhases = ( ECEA613218A4A76E0064AFF4 /* ShellScript */, ); dependencies = ( ECEA613118A4A73D0064AFF4 /* PBXTargetDependency */, ); name = Framework; productName = Framework; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 46B05A2E1A076AD70022AB70 /* NSObject+FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */; }; 46B05A301A076CC40022AB70 /* NSObject+FBKVOController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */; }; 81BD70BD1CA4B57F00FB8E4D /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA610818A49C620064AFF4 /* FBKVOController.m */; }; 81BD70BE1CA4B57F00FB8E4D /* NSObject+FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */; }; 81BD70C11CA4B57F00FB8E4D /* KVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EC40D81CA3623D00BD9226 /* KVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD70C21CA4B57F00FB8E4D /* FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = ECEA610618A49C620064AFF4 /* FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD70C31CA4B57F00FB8E4D /* NSObject+FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD70FE1CA607F500FB8E4D /* KVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EC40D81CA3623D00BD9226 /* KVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD70FF1CA607F500FB8E4D /* NSObject+FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD71001CA607F500FB8E4D /* NSObject+FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */; }; 81BD71011CA607F500FB8E4D /* FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = ECEA610618A49C620064AFF4 /* FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81BD71021CA607F500FB8E4D /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA610818A49C620064AFF4 /* FBKVOController.m */; }; 81EC40D01CA3621E00BD9226 /* NSObject+FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC40D11CA3621E00BD9226 /* NSObject+FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */; }; 81EC40D21CA3621E00BD9226 /* FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = ECEA610618A49C620064AFF4 /* FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC40D31CA3621E00BD9226 /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA610818A49C620064AFF4 /* FBKVOController.m */; }; 81EC40D91CA3623D00BD9226 /* KVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EC40D81CA3623D00BD9226 /* KVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC410C1CA363FC00BD9226 /* KVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EC40D81CA3623D00BD9226 /* KVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC41101CA3640600BD9226 /* NSObject+FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC41111CA3640600BD9226 /* FBKVOController.h in Headers */ = {isa = PBXBuildFile; fileRef = ECEA610618A49C620064AFF4 /* FBKVOController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EC41151CA3640B00BD9226 /* NSObject+FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */; }; 81EC41161CA3640B00BD9226 /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA610818A49C620064AFF4 /* FBKVOController.m */; }; EC8BB5AE18A5792D00EB2793 /* FBKVOTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = EC8BB5AD18A5792D00EB2793 /* FBKVOTesting.m */; }; ECEA610218A49C620064AFF4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEA610118A49C620064AFF4 /* Foundation.framework */; }; ECEA610718A49C620064AFF4 /* FBKVOController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = ECEA610618A49C620064AFF4 /* FBKVOController.h */; }; ECEA610918A49C620064AFF4 /* FBKVOController.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA610818A49C620064AFF4 /* FBKVOController.m */; }; ECEA611018A49C620064AFF4 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEA610F18A49C620064AFF4 /* XCTest.framework */; }; ECEA611118A49C620064AFF4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEA610118A49C620064AFF4 /* Foundation.framework */; }; ECEA611318A49C620064AFF4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEA611218A49C620064AFF4 /* UIKit.framework */; }; ECEA611618A49C620064AFF4 /* libFBKVOController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEA60FE18A49C620064AFF4 /* libFBKVOController.a */; }; ECEA611C18A49C620064AFF4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = ECEA611A18A49C620064AFF4 /* InfoPlist.strings */; }; ECEA611E18A49C620064AFF4 /* FBKVOControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ECEA611D18A49C620064AFF4 /* FBKVOControllerTests.m */; }; F2755F5F5CED1DAAAFED68B2 /* libPods-FBKVOControllerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 044A6FECA9893D2B98CA99B7 /* libPods-FBKVOControllerTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ ECEA611418A49C620064AFF4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = ECEA60F618A49C620064AFF4 /* Project object */; proxyType = 1; remoteGlobalIDString = ECEA60FD18A49C620064AFF4; remoteInfo = FBKVOController; }; ECEA613018A4A73D0064AFF4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = ECEA60F618A49C620064AFF4 /* Project object */; proxyType = 1; remoteGlobalIDString = ECEA60FD18A49C620064AFF4; remoteInfo = FBKVOController; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ ECEA60FC18A49C620064AFF4 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = "$(PRODUCT_NAME)Headers"; dstSubfolderSpec = 16; files = ( 46B05A301A076CC40022AB70 /* NSObject+FBKVOController.h in CopyFiles */, ECEA610718A49C620064AFF4 /* FBKVOController.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 044A6FECA9893D2B98CA99B7 /* libPods-FBKVOControllerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FBKVOControllerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2316F70E2DD4D80B87440A05 /* Pods-FBKVOControllerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FBKVOControllerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FBKVOControllerTests/Pods-FBKVOControllerTests.debug.xcconfig"; sourceTree = ""; }; 3E0BB792112F905E950F2AE4 /* Pods-FBKVOControllerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FBKVOControllerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FBKVOControllerTests/Pods-FBKVOControllerTests.release.xcconfig"; sourceTree = ""; }; 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+FBKVOController.m"; sourceTree = ""; }; 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+FBKVOController.h"; sourceTree = ""; }; 81BD70C81CA4B57F00FB8E4D /* KVOController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KVOController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81BD70EA1CA4B98D00FB8E4D /* KVOController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KVOController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81EC40C61CA3620A00BD9226 /* KVOController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KVOController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81EC40D81CA3623D00BD9226 /* KVOController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KVOController.h; sourceTree = ""; }; 81EC40EE1CA3630200BD9226 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81EC40F81CA3639C00BD9226 /* KVOController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KVOController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC8BB5AC18A5792D00EB2793 /* FBKVOTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBKVOTesting.h; sourceTree = ""; }; EC8BB5AD18A5792D00EB2793 /* FBKVOTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBKVOTesting.m; sourceTree = ""; }; EC8BB5B318A5A1EE00EB2793 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; EC8BB5B418A5A1F500EB2793 /* PATENTS */ = {isa = PBXFileReference; lastKnownFileType = text; path = PATENTS; sourceTree = ""; }; EC8BB5B518A5A30700EB2793 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = CONTRIBUTING.md; sourceTree = ""; }; EC8BB5B618A5A30F00EB2793 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = ""; }; ECAFBD5E18BD47BE009B4EC6 /* KVOController.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = KVOController.podspec; sourceTree = ""; }; ECEA60FE18A49C620064AFF4 /* libFBKVOController.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFBKVOController.a; sourceTree = BUILT_PRODUCTS_DIR; }; ECEA610118A49C620064AFF4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; ECEA610618A49C620064AFF4 /* FBKVOController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBKVOController.h; sourceTree = ""; }; ECEA610818A49C620064AFF4 /* FBKVOController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBKVOController.m; sourceTree = ""; tabWidth = 4; }; ECEA610E18A49C620064AFF4 /* FBKVOControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FBKVOControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; ECEA610F18A49C620064AFF4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; ECEA611218A49C620064AFF4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; ECEA611918A49C620064AFF4 /* FBKVOControllerTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FBKVOControllerTests-Info.plist"; sourceTree = ""; }; ECEA611B18A49C620064AFF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; ECEA611D18A49C620064AFF4 /* FBKVOControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBKVOControllerTests.m; sourceTree = ""; tabWidth = 4; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 81BD70BF1CA4B57F00FB8E4D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81BD70E61CA4B98D00FB8E4D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40C21CA3620A00BD9226 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40F41CA3639C00BD9226 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; ECEA60FB18A49C620064AFF4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ECEA610218A49C620064AFF4 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; ECEA610B18A49C620064AFF4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ECEA611018A49C620064AFF4 /* XCTest.framework in Frameworks */, ECEA611318A49C620064AFF4 /* UIKit.framework in Frameworks */, ECEA611618A49C620064AFF4 /* libFBKVOController.a in Frameworks */, ECEA611118A49C620064AFF4 /* Foundation.framework in Frameworks */, F2755F5F5CED1DAAAFED68B2 /* libPods-FBKVOControllerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ DE977FB840686E597267F657 /* Pods */ = { isa = PBXGroup; children = ( 2316F70E2DD4D80B87440A05 /* Pods-FBKVOControllerTests.debug.xcconfig */, 3E0BB792112F905E950F2AE4 /* Pods-FBKVOControllerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; ECEA60F518A49C620064AFF4 = { isa = PBXGroup; children = ( EC8BB5B618A5A30F00EB2793 /* README.md */, EC8BB5B518A5A30700EB2793 /* CONTRIBUTING.md */, EC8BB5B318A5A1EE00EB2793 /* LICENSE */, EC8BB5B418A5A1F500EB2793 /* PATENTS */, ECAFBD5E18BD47BE009B4EC6 /* KVOController.podspec */, ECEA610318A49C620064AFF4 /* FBKVOController */, ECEA611718A49C620064AFF4 /* FBKVOControllerTests */, ECEA610018A49C620064AFF4 /* Frameworks */, ECEA60FF18A49C620064AFF4 /* Products */, DE977FB840686E597267F657 /* Pods */, ); indentWidth = 2; sourceTree = ""; tabWidth = 2; }; ECEA60FF18A49C620064AFF4 /* Products */ = { isa = PBXGroup; children = ( ECEA60FE18A49C620064AFF4 /* libFBKVOController.a */, ECEA610E18A49C620064AFF4 /* FBKVOControllerTests.xctest */, 81EC40C61CA3620A00BD9226 /* KVOController.framework */, 81EC40F81CA3639C00BD9226 /* KVOController.framework */, 81BD70C81CA4B57F00FB8E4D /* KVOController.framework */, 81BD70EA1CA4B98D00FB8E4D /* KVOController.framework */, ); name = Products; sourceTree = ""; }; ECEA610018A49C620064AFF4 /* Frameworks */ = { isa = PBXGroup; children = ( ECEA610118A49C620064AFF4 /* Foundation.framework */, ECEA610F18A49C620064AFF4 /* XCTest.framework */, ECEA611218A49C620064AFF4 /* UIKit.framework */, 044A6FECA9893D2B98CA99B7 /* libPods-FBKVOControllerTests.a */, ); name = Frameworks; sourceTree = ""; }; ECEA610318A49C620064AFF4 /* FBKVOController */ = { isa = PBXGroup; children = ( 81EC40D81CA3623D00BD9226 /* KVOController.h */, 46B05A2F1A076ADD0022AB70 /* NSObject+FBKVOController.h */, 46B05A2D1A076AD70022AB70 /* NSObject+FBKVOController.m */, ECEA610618A49C620064AFF4 /* FBKVOController.h */, ECEA610818A49C620064AFF4 /* FBKVOController.m */, ECEA610418A49C620064AFF4 /* Supporting Files */, ); path = FBKVOController; sourceTree = ""; }; ECEA610418A49C620064AFF4 /* Supporting Files */ = { isa = PBXGroup; children = ( 81EC40EE1CA3630200BD9226 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; ECEA611718A49C620064AFF4 /* FBKVOControllerTests */ = { isa = PBXGroup; children = ( ECEA611D18A49C620064AFF4 /* FBKVOControllerTests.m */, EC8BB5AC18A5792D00EB2793 /* FBKVOTesting.h */, EC8BB5AD18A5792D00EB2793 /* FBKVOTesting.m */, ECEA611818A49C620064AFF4 /* Supporting Files */, ); path = FBKVOControllerTests; sourceTree = ""; }; ECEA611818A49C620064AFF4 /* Supporting Files */ = { isa = PBXGroup; children = ( ECEA611918A49C620064AFF4 /* FBKVOControllerTests-Info.plist */, ECEA611A18A49C620064AFF4 /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 81BD70C01CA4B57F00FB8E4D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 81BD70C11CA4B57F00FB8E4D /* KVOController.h in Headers */, 81BD70C21CA4B57F00FB8E4D /* FBKVOController.h in Headers */, 81BD70C31CA4B57F00FB8E4D /* NSObject+FBKVOController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 81BD70E71CA4B98D00FB8E4D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 81BD70FE1CA607F500FB8E4D /* KVOController.h in Headers */, 81BD71011CA607F500FB8E4D /* FBKVOController.h in Headers */, 81BD70FF1CA607F500FB8E4D /* NSObject+FBKVOController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40C31CA3620A00BD9226 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 81EC40D91CA3623D00BD9226 /* KVOController.h in Headers */, 81EC40D21CA3621E00BD9226 /* FBKVOController.h in Headers */, 81EC40D01CA3621E00BD9226 /* NSObject+FBKVOController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40F51CA3639C00BD9226 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 81EC410C1CA363FC00BD9226 /* KVOController.h in Headers */, 81EC41101CA3640600BD9226 /* NSObject+FBKVOController.h in Headers */, 81EC41111CA3640600BD9226 /* FBKVOController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 81BD70BB1CA4B57F00FB8E4D /* FBKVOController-tvOS-Dynamic */ = { isa = PBXNativeTarget; buildConfigurationList = 81BD70C51CA4B57F00FB8E4D /* Build configuration list for PBXNativeTarget "FBKVOController-tvOS-Dynamic" */; buildPhases = ( 81BD70BC1CA4B57F00FB8E4D /* Sources */, 81BD70BF1CA4B57F00FB8E4D /* Frameworks */, 81BD70C01CA4B57F00FB8E4D /* Headers */, 81BD70C41CA4B57F00FB8E4D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "FBKVOController-tvOS-Dynamic"; productName = KVOController; productReference = 81BD70C81CA4B57F00FB8E4D /* KVOController.framework */; productType = "com.apple.product-type.framework"; }; 81BD70E91CA4B98D00FB8E4D /* FBKVOController-watchOS-Dynamic */ = { isa = PBXNativeTarget; buildConfigurationList = 81BD70EF1CA4B98D00FB8E4D /* Build configuration list for PBXNativeTarget "FBKVOController-watchOS-Dynamic" */; buildPhases = ( 81BD70E51CA4B98D00FB8E4D /* Sources */, 81BD70E61CA4B98D00FB8E4D /* Frameworks */, 81BD70E71CA4B98D00FB8E4D /* Headers */, 81BD70E81CA4B98D00FB8E4D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "FBKVOController-watchOS-Dynamic"; productName = "FBKVOController-watchOS-Dynamic"; productReference = 81BD70EA1CA4B98D00FB8E4D /* KVOController.framework */; productType = "com.apple.product-type.framework"; }; 81EC40C51CA3620A00BD9226 /* FBKVOController-iOS-Dynamic */ = { isa = PBXNativeTarget; buildConfigurationList = 81EC40CD1CA3620A00BD9226 /* Build configuration list for PBXNativeTarget "FBKVOController-iOS-Dynamic" */; buildPhases = ( 81EC40C11CA3620A00BD9226 /* Sources */, 81EC40C21CA3620A00BD9226 /* Frameworks */, 81EC40C31CA3620A00BD9226 /* Headers */, 81EC40C41CA3620A00BD9226 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "FBKVOController-iOS-Dynamic"; productName = KVOController; productReference = 81EC40C61CA3620A00BD9226 /* KVOController.framework */; productType = "com.apple.product-type.framework"; }; 81EC40F71CA3639C00BD9226 /* FBKVOController-OSX-Dynamic */ = { isa = PBXNativeTarget; buildConfigurationList = 81EC40FD1CA3639C00BD9226 /* Build configuration list for PBXNativeTarget "FBKVOController-OSX-Dynamic" */; buildPhases = ( 81EC40F31CA3639C00BD9226 /* Sources */, 81EC40F41CA3639C00BD9226 /* Frameworks */, 81EC40F51CA3639C00BD9226 /* Headers */, 81EC40F61CA3639C00BD9226 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "FBKVOController-OSX-Dynamic"; productName = "KVOController-OSX-Dynamic"; productReference = 81EC40F81CA3639C00BD9226 /* KVOController.framework */; productType = "com.apple.product-type.framework"; }; ECEA60FD18A49C620064AFF4 /* FBKVOController */ = { isa = PBXNativeTarget; buildConfigurationList = ECEA612118A49C620064AFF4 /* Build configuration list for PBXNativeTarget "FBKVOController" */; buildPhases = ( ECEA60FA18A49C620064AFF4 /* Sources */, ECEA60FB18A49C620064AFF4 /* Frameworks */, ECEA60FC18A49C620064AFF4 /* CopyFiles */, ECEA612718A49DDD0064AFF4 /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = FBKVOController; productName = FBKVOController; productReference = ECEA60FE18A49C620064AFF4 /* libFBKVOController.a */; productType = "com.apple.product-type.library.static"; }; ECEA610D18A49C620064AFF4 /* FBKVOControllerTests */ = { isa = PBXNativeTarget; buildConfigurationList = ECEA612418A49C620064AFF4 /* Build configuration list for PBXNativeTarget "FBKVOControllerTests" */; buildPhases = ( BEB3825BB272703E7F8D5FAD /* [CP] Check Pods Manifest.lock */, ECEA610A18A49C620064AFF4 /* Sources */, ECEA610B18A49C620064AFF4 /* Frameworks */, ECEA610C18A49C620064AFF4 /* Resources */, EC1E7A550C7FEF37584E8438 /* [CP] Embed Pods Frameworks */, 4815293B1AA905E7D81CAD3F /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ECEA611518A49C620064AFF4 /* PBXTargetDependency */, ); name = FBKVOControllerTests; productName = FBKVOControllerTests; productReference = ECEA610E18A49C620064AFF4 /* FBKVOControllerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ ECEA60F618A49C620064AFF4 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0820; ORGANIZATIONNAME = "Kimon Tsinteris"; TargetAttributes = { 81BD70E91CA4B98D00FB8E4D = { CreatedOnToolsVersion = 7.3; }; 81EC40C51CA3620A00BD9226 = { CreatedOnToolsVersion = 7.3; }; 81EC40F71CA3639C00BD9226 = { CreatedOnToolsVersion = 7.3; }; }; }; buildConfigurationList = ECEA60F918A49C620064AFF4 /* Build configuration list for PBXProject "FBKVOController" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = ECEA60F518A49C620064AFF4; productRefGroup = ECEA60FF18A49C620064AFF4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( ECEA60FD18A49C620064AFF4 /* FBKVOController */, ECEA610D18A49C620064AFF4 /* FBKVOControllerTests */, ECEA612C18A4A7360064AFF4 /* Framework */, 81EC40C51CA3620A00BD9226 /* FBKVOController-iOS-Dynamic */, 81EC40F71CA3639C00BD9226 /* FBKVOController-OSX-Dynamic */, 81BD70BB1CA4B57F00FB8E4D /* FBKVOController-tvOS-Dynamic */, 81BD70E91CA4B98D00FB8E4D /* FBKVOController-watchOS-Dynamic */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 81BD70C41CA4B57F00FB8E4D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81BD70E81CA4B98D00FB8E4D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40C41CA3620A00BD9226 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40F61CA3639C00BD9226 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; ECEA610C18A49C620064AFF4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ECEA611C18A49C620064AFF4 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 4815293B1AA905E7D81CAD3F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FBKVOControllerTests/Pods-FBKVOControllerTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; BEB3825BB272703E7F8D5FAD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; EC1E7A550C7FEF37584E8438 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FBKVOControllerTests/Pods-FBKVOControllerTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; ECEA612718A49DDD0064AFF4 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "set -e\n\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers\"\n\n# Link the \"Current\" version to \"A\"\n/bin/ln -sfh A \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/Current\"\n/bin/ln -sfh Versions/Current/Headers \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Headers\"\n/bin/ln -sfh \"Versions/Current/${PRODUCT_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\n# The -a ensures that the headers maintain the source modification date so that we don't constantly\n# cause propagating rebuilds of files that import these headers.\n/bin/cp -a \"${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers\""; }; ECEA613218A4A76E0064AFF4 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "set -e\nset +u\n# Avoid recursively calling this script.\nif [[ $SF_MASTER_SCRIPT_RUNNING ]]\nthen\nexit 0\nfi\nset -u\nexport SF_MASTER_SCRIPT_RUNNING=1\n\nSF_TARGET_NAME=${PROJECT_NAME}\nSF_EXECUTABLE_PATH=\"lib${SF_TARGET_NAME}.a\"\nSF_WRAPPER_NAME=\"${SF_TARGET_NAME}.framework\"\n\n# The following conditionals come from\n# https://github.com/kstenerud/iOS-Universal-Framework\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nSF_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nSF_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SF_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nSF_OTHER_PLATFORM=iphonesimulator\nelse\nSF_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$SF_SDK_PLATFORM$ ]]\nthen\nSF_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}\"\nelse\necho \"Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\n# Build the other platform.\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${SF_OTHER_PLATFORM}${SF_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" OBJROOT=\"${OBJROOT}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\" $ACTION\n\n# Smash the two static libraries into one fat binary and store it in the .framework\nlipo -create \"${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}\" -output \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"\n\n# Copy the binary to the other architecture folder to have a complete framework in both.\ncp -a \"${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\" \"${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/${FRAMEWORK_VERSION}/${SF_TARGET_NAME}\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 81BD70BC1CA4B57F00FB8E4D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81BD70BD1CA4B57F00FB8E4D /* FBKVOController.m in Sources */, 81BD70BE1CA4B57F00FB8E4D /* NSObject+FBKVOController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 81BD70E51CA4B98D00FB8E4D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81BD71021CA607F500FB8E4D /* FBKVOController.m in Sources */, 81BD71001CA607F500FB8E4D /* NSObject+FBKVOController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40C11CA3620A00BD9226 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81EC40D31CA3621E00BD9226 /* FBKVOController.m in Sources */, 81EC40D11CA3621E00BD9226 /* NSObject+FBKVOController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 81EC40F31CA3639C00BD9226 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 81EC41161CA3640B00BD9226 /* FBKVOController.m in Sources */, 81EC41151CA3640B00BD9226 /* NSObject+FBKVOController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; ECEA60FA18A49C620064AFF4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 46B05A2E1A076AD70022AB70 /* NSObject+FBKVOController.m in Sources */, ECEA610918A49C620064AFF4 /* FBKVOController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; ECEA610A18A49C620064AFF4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ECEA611E18A49C620064AFF4 /* FBKVOControllerTests.m in Sources */, EC8BB5AE18A5792D00EB2793 /* FBKVOTesting.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ ECEA611518A49C620064AFF4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = ECEA60FD18A49C620064AFF4 /* FBKVOController */; targetProxy = ECEA611418A49C620064AFF4 /* PBXContainerItemProxy */; }; ECEA613118A4A73D0064AFF4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = ECEA60FD18A49C620064AFF4 /* FBKVOController */; targetProxy = ECEA613018A4A73D0064AFF4 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ ECEA611A18A49C620064AFF4 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( ECEA611B18A49C620064AFF4 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 81BD70C61CA4B57F00FB8E4D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.tvos; PRODUCT_NAME = KVOController; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 81BD70C71CA4B57F00FB8E4D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.tvos; PRODUCT_NAME = KVOController; SDKROOT = appletvos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 3; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 81BD70F01CA4B98D00FB8E4D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.watchos; PRODUCT_NAME = KVOController; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; 81BD70F11CA4B98D00FB8E4D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.watchos; PRODUCT_NAME = KVOController; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; 81EC40CB1CA3620A00BD9226 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.ios; PRODUCT_NAME = KVOController; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 81EC40CC1CA3620A00BD9226 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.ios; PRODUCT_NAME = KVOController; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 81EC40FE1CA3639C00BD9226 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_VERSION = A; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.7; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.osx; PRODUCT_NAME = KVOController; SDKROOT = macosx; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 81EC40FF1CA3639C00BD9226 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_VERSION = A; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = "$(SRCROOT)/FBKVOController/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.7; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.kvocontroller.osx; PRODUCT_NAME = KVOController; SDKROOT = macosx; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; ECEA611F18A49C620064AFF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; ECEA612018A49C620064AFF4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; ECEA612218A49C620064AFF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = NO; DSTROOT = /tmp/FBKVOController.dst; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = "$(PROJECT_NAME)Headers"; SKIP_INSTALL = YES; STRIP_STYLE = "non-global"; }; name = Debug; }; ECEA612318A49C620064AFF4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = NO; DSTROOT = /tmp/FBKVOController.dst; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; PUBLIC_HEADERS_FOLDER_PATH = "$(PROJECT_NAME)Headers"; SKIP_INSTALL = YES; STRIP_STYLE = "non-global"; }; name = Release; }; ECEA612518A49C620064AFF4 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2316F70E2DD4D80B87440A05 /* Pods-FBKVOControllerTests.debug.xcconfig */; buildSettings = { GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "FBKVOControllerTests/FBKVOControllerTests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Debug; }; ECEA612618A49C620064AFF4 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3E0BB792112F905E950F2AE4 /* Pods-FBKVOControllerTests.release.xcconfig */; buildSettings = { INFOPLIST_FILE = "FBKVOControllerTests/FBKVOControllerTests-Info.plist"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = xctest; }; name = Release; }; ECEA612E18A4A7360064AFF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; ECEA612F18A4A7360064AFF4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 81BD70C51CA4B57F00FB8E4D /* Build configuration list for PBXNativeTarget "FBKVOController-tvOS-Dynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( 81BD70C61CA4B57F00FB8E4D /* Debug */, 81BD70C71CA4B57F00FB8E4D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81BD70EF1CA4B98D00FB8E4D /* Build configuration list for PBXNativeTarget "FBKVOController-watchOS-Dynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( 81BD70F01CA4B98D00FB8E4D /* Debug */, 81BD70F11CA4B98D00FB8E4D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81EC40CD1CA3620A00BD9226 /* Build configuration list for PBXNativeTarget "FBKVOController-iOS-Dynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( 81EC40CB1CA3620A00BD9226 /* Debug */, 81EC40CC1CA3620A00BD9226 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 81EC40FD1CA3639C00BD9226 /* Build configuration list for PBXNativeTarget "FBKVOController-OSX-Dynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( 81EC40FE1CA3639C00BD9226 /* Debug */, 81EC40FF1CA3639C00BD9226 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; ECEA60F918A49C620064AFF4 /* Build configuration list for PBXProject "FBKVOController" */ = { isa = XCConfigurationList; buildConfigurations = ( ECEA611F18A49C620064AFF4 /* Debug */, ECEA612018A49C620064AFF4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; ECEA612118A49C620064AFF4 /* Build configuration list for PBXNativeTarget "FBKVOController" */ = { isa = XCConfigurationList; buildConfigurations = ( ECEA612218A49C620064AFF4 /* Debug */, ECEA612318A49C620064AFF4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; ECEA612418A49C620064AFF4 /* Build configuration list for PBXNativeTarget "FBKVOControllerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( ECEA612518A49C620064AFF4 /* Debug */, ECEA612618A49C620064AFF4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; ECEA612D18A4A7360064AFF4 /* Build configuration list for PBXAggregateTarget "Framework" */ = { isa = XCConfigurationList; buildConfigurations = ( ECEA612E18A4A7360064AFF4 /* Debug */, ECEA612F18A4A7360064AFF4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = ECEA60F618A49C620064AFF4 /* Project object */; } ================================================ FILE: FBKVOController.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: FBKVOController.xcodeproj/xcshareddata/xcschemes/FBKVOController-OSX-Dynamic.xcscheme ================================================ ================================================ FILE: FBKVOController.xcodeproj/xcshareddata/xcschemes/FBKVOController-iOS-Dynamic.xcscheme ================================================ ================================================ FILE: FBKVOController.xcodeproj/xcshareddata/xcschemes/FBKVOController-tvOS-Dynamic.xcscheme ================================================ ================================================ FILE: FBKVOController.xcodeproj/xcshareddata/xcschemes/FBKVOController-watchOS-Dynamic.xcscheme ================================================ ================================================ FILE: FBKVOController.xcodeproj/xcshareddata/xcschemes/FBKVOController.xcscheme ================================================ ================================================ FILE: FBKVOController.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: FBKVOControllerTests/FBKVOControllerTests-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: FBKVOControllerTests/FBKVOControllerTests.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import #define HC_SHORTHAND #import #define MOCKITO_SHORTHAND #import #import #import #import "FBKVOTesting.h" @interface FBKVOControllerTests : XCTestCase @end @implementation FBKVOControllerTests static NSString *radius = @"radius"; static NSString *borderWidth = @"borderWidth"; static void *context = (void *)@"context"; static NSKeyValueObservingOptions const optionsNone = 0; static NSKeyValueObservingOptions const optionsBasic = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial; static NSKeyValueObservingOptions const optionsAll = optionsBasic | NSKeyValueObservingOptionPrior; - (void)testDebugDescriptionContainsClassName { id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestCircle *circle = [FBKVOTestCircle circle]; [controller observe:circle keyPaths:@[radius, borderWidth] options:optionsAll context:context]; assertThat([controller debugDescription], containsSubstring(@"FBKVOController")); } - (void)testBlockOptionsBasic { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // add reference observe [circle addObserver:referenceObserver forKeyPath:radius options:optionsBasic context:context]; __block NSUInteger blockCallCount = 0; __block id blockObserver = nil; __block id blockObject = nil; __block NSString *blockKeyPath = nil; __block NSDictionary *blockChange = nil; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic block:^(id observer, id object, NSDictionary *change) { blockObserver = observer; blockObject = object; NSMutableDictionary *mChange = [change mutableCopy]; [mChange removeObjectForKey:FBKVONotificationKeyPathKey]; blockChange = [mChange copy]; blockKeyPath = change[FBKVONotificationKeyPathKey]; blockCallCount++; }]; XCTAssert(1 == blockCallCount, @"unexpected block call count:%lu expected:%d", (unsigned long)blockCallCount, 1); XCTAssert(blockObserver == observer, @"value:%@ expected:%@", blockObserver, observer); XCTAssert(blockObject == referenceObserver.lastObject, @"value:%@ expected:%@", blockObject, referenceObserver.lastObject); XCTAssert([blockKeyPath isEqualToString:radius], @"value:%@ expected:%@", blockKeyPath, radius); XCTAssertEqualObjects(blockChange, referenceObserver.lastChange, @"value:%@ expected:%@", blockChange, referenceObserver.lastChange); circle.radius = 1.0; XCTAssert(2 == blockCallCount, @"unexpected block call count:%lu expected:%d", (unsigned long)blockCallCount, 2); XCTAssert(blockObserver == observer, @"value:%@ expected:%@", blockObserver, observer); XCTAssert(blockObject == referenceObserver.lastObject, @"value:%@ expected:%@", blockObject, referenceObserver.lastObject); XCTAssert([blockKeyPath isEqualToString:radius], @"value:%@ expected:%@", blockKeyPath, radius); XCTAssertEqualObjects(blockChange, referenceObserver.lastChange, @"value:%@ expected:%@", blockChange, referenceObserver.lastChange); // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testNSKeyValueObservingOptionsNone { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // add observers [circle addObserver:referenceObserver forKeyPath:radius options:optionsNone context:context]; [controller observe:circle keyPath:radius options:optionsNone context:context]; // mutate circle.radius = 1.0; // verify [verify(observer) observeValueForKeyPath:referenceObserver.lastKeyPath ofObject:referenceObserver.lastObject change:referenceObserver.lastChange context:referenceObserver.lastContext]; // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testNSKeyValueObservingOptionsBasic { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // initial value circle.radius = 1.0; // add reference observe [circle addObserver:referenceObserver forKeyPath:radius options:optionsBasic context:context]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic context:context]; // verify [verify(observer) observeValueForKeyPath:referenceObserver.lastKeyPath ofObject:referenceObserver.lastObject change:referenceObserver.lastChange context:referenceObserver.lastContext]; // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testNSKeyValueObservingOptionsAll { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // initial value circle.radius = 1.0; // add reference observe [circle addObserver:referenceObserver forKeyPath:radius options:optionsAll context:context]; // add mock observer [controller observe:circle keyPath:radius options:optionsAll context:context]; // verify initial [verify(observer) observeValueForKeyPath:referenceObserver.lastKeyPath ofObject:referenceObserver.lastObject change:referenceObserver.lastChange context:referenceObserver.lastContext]; circle.radius = 2.0; // verify mutation [verify(observer) observeValueForKeyPath:referenceObserver.lastKeyPath ofObject:referenceObserver.lastObject change:referenceObserver.lastChange context:referenceObserver.lastContext]; // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testObserveKeyPathsOptionsBlockWhenKeyPathsIsNilRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; FBKVONotificationBlock arbitraryBlock = ^(id observer, id object, NSDictionary *change) { /* noop */ }; XCTAssertThrows([controller observe:nil keyPaths:(id _Nonnull)nil options:0 block:arbitraryBlock]); } - (void)testObserveKeyPathsOptionsBlockWhenKeyPathsIsEmptyRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *emptyKeyPaths = @[]; FBKVONotificationBlock arbitraryBlock = ^(id observer, id object, NSDictionary *change) { /* noop */ }; XCTAssertThrows([controller observe:nil keyPaths:emptyKeyPaths options:0 block:arbitraryBlock]); } - (void)testObserveKeyPathsOptionsBlockWhenBlockIsNilRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"ante", @"bellum"]; XCTAssertThrows([controller observe:nil keyPaths:arbitraryKeyPaths options:0 block:(id _Nonnull)nil]); } - (void)testObserveKeyPathsOptionsBlockWhenObjectIsNilDoesNothing { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"ante", @"bellum"]; FBKVONotificationBlock arbitraryBlock = ^(id observer, id object, NSDictionary *change) { /* noop */ }; XCTAssertNoThrow([controller observe:nil keyPaths:arbitraryKeyPaths options:0 block:arbitraryBlock]); } - (void)testObserveKeyPathsOptionsBlockObservesEachOfTheKeyPaths { // Arrange 1: Create a controller to observe changes to a circle. id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // Arrange 2: Observe the key paths "radius" and "borderWidth" on the circle. // Aggregate the new values in an array. NSMutableArray *newValues = [NSMutableArray array]; FBKVOTestCircle *circle = [FBKVOTestCircle circle]; [controller observe:circle keyPaths:@[radius, borderWidth] options:NSKeyValueObservingOptionNew block:^(id observer, FBKVOTestCircle *circle, NSDictionary *change) { [newValues addObject:change[NSKeyValueChangeNewKey]]; }]; // Act: Change the values to trigger the KVO notification block. circle.radius = 1.f; circle.borderWidth = 10.f; // Assert: Changes to the radius and borderWidth should have been observed. assertThat(newValues, equalTo(@[@1, @10])); } - (void)testObserveKeyPathsOptionsActionWhenKeyPathsIsNilRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; SEL arbitrarySelector = @selector(cookies); XCTAssertThrows([controller observe:nil keyPaths:(id _Nonnull)nil options:0 action:arbitrarySelector]); } - (void)testObserveKeyPathsOptionsActionWhenKeyPathsIsEmptyRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *emptyKeyPaths = @[]; SEL arbitrarySelector = @selector(cookies); XCTAssertThrows([controller observe:nil keyPaths:emptyKeyPaths options:0 action:arbitrarySelector]); } - (void)testObserveKeyPathsOptionsActionWhenActionIsNullRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"carpe", @"diem"]; XCTAssertThrows([controller observe:nil keyPaths:arbitraryKeyPaths options:0 action:(SEL _Nonnull)NULL]); } - (void)testObserveKeyPathsOptionsActionWhenObjectIsNilRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"ante", @"bellum"]; XCTAssertThrows([controller observe:nil keyPaths:arbitraryKeyPaths options:0 action:@selector(debugDescription)]); } - (void)testObserveKeyPathsOptionsActionWhenObjectDoesNotRespondToSelectorRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"caveat", @"emptor"]; XCTAssertThrows([controller observe:[NSObject new] keyPaths:arbitraryKeyPaths options:0 action:@selector(cookies)]); } - (void)testObserveKeyPathsOptionsActionObservesEachOfTheKeyPaths { // Arrange 1: Create a controller to observe changes to a circle. id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // Arrange 2: Observe the key paths "radius" and "borderWidth" on the circle. FBKVOTestCircle *circle = [FBKVOTestCircle circle]; [controller observe:circle keyPaths:@[radius, borderWidth] options:NSKeyValueObservingOptionNew action:@selector(propertyDidChange)]; // Act: Change the values to trigger the KVO action selector. circle.radius = 1.f; circle.borderWidth = 10.f; // Assert: Changes to the radius and borderWidth should have been observed. [verifyCount(observer, times(2)) propertyDidChange]; } - (void)testObserveKeyPathsOptionsContextWhenKeyPathsIsNilRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; XCTAssertThrows([controller observe:nil keyPaths:(id _Nonnull)nil options:0 context:NULL]); } - (void)testObserveKeyPathsOptionsContextWhenKeyPathsIsEmptyRaises { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *emptyKeyPaths = @[]; XCTAssertThrows([controller observe:nil keyPaths:emptyKeyPaths options:0 context:NULL]); } - (void)testObserveKeyPathsOptionsContextWhenObjectIsNilDoesNothing { FBKVOController *controller = [FBKVOController controllerWithObserver:nil]; NSArray *arbitraryKeyPaths = @[@"terra", @"firma"]; XCTAssertNoThrow([controller observe:nil keyPaths:arbitraryKeyPaths options:0 context:NULL]); } - (void)testObserveKeyPathsOptionsContextObservesEachOfTheKeyPaths { // Arrange 1: Create a controller to observe changes to a circle. id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // Arrange 2: Observe the key paths "radius" and "borderWidth" on the circle. FBKVOTestCircle *circle = [FBKVOTestCircle circle]; [controller observe:circle keyPaths:@[radius, borderWidth] options:NSKeyValueObservingOptionNew context:NULL]; // Act: Change the values to trigger the KVO action selector. circle.radius = 1.f; circle.borderWidth = 10.f; // Assert: Changes to the radius and borderWidth should have been observed. [verifyCount(observer, times(1)) observeValueForKeyPath:radius ofObject:circle change:anything() context:NULL]; [verifyCount(observer, times(1)) observeValueForKeyPath:borderWidth ofObject:circle change:anything() context:NULL]; } - (void)testObserveKeyPathsOptionsContextUnobserveWithinBlockWithOptionInitial { id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestCircle *circle = [FBKVOTestCircle circle]; circle.radius = 1.0; __block float lastObservedRadius = 0.f; [controller observe:circle keyPath:radius options:NSKeyValueObservingOptionInitial block:^(id observer, id object, NSDictionary *change) { lastObservedRadius = circle.radius; [controller unobserve:circle]; }]; XCTAssertNoThrow(circle.radius = 2.f); XCTAssertEqual(lastObservedRadius, 1.f); } - (void)testCustomActionOptionsBasic { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic action:@selector(propertyDidChange)]; // verify initial [verifyCount(observer, times(1)) propertyDidChange]; // verify mutation circle.radius = 1.0; [verifyCount(observer, times(2)) propertyDidChange]; } - (void)testCustomActionWithChangeOptionsBasic { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // add reference observe [circle addObserver:referenceObserver forKeyPath:radius options:optionsBasic context:context]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic action:@selector(propertyDidChange:)]; // verify initial [verify(observer) propertyDidChange:referenceObserver.lastChange]; circle.radius = 2.0; // verify mutation [verify(observer) propertyDidChange:referenceObserver.lastChange]; // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testCustomActionWithChangeObjectOptionsBasic { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // add reference observe [circle addObserver:referenceObserver forKeyPath:radius options:optionsBasic context:context]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic action:@selector(propertyDidChange:object:)]; // verify initial [verify(observer) propertyDidChange:referenceObserver.lastChange object:referenceObserver.lastObject]; circle.radius = 2.0; // verify mutation [verify(observer) propertyDidChange:referenceObserver.lastChange object:referenceObserver.lastObject]; // cleanup [circle removeObserver:referenceObserver forKeyPath:radius]; } - (void)testUnobserveKeyPath { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // observe radius and borderWidth [controller observe:circle keyPath:radius options:optionsNone context:context]; [controller observe:circle keyPath:borderWidth options:optionsNone context:context]; // mutate both properties circle.radius = 1.0; circle.borderWidth = 1.0; // verify [verifyCount(observer, times(2)) observeValueForKeyPath:anything() ofObject:circle change:anything() context:context]; // unobserve borderWidth [controller unobserve:circle keyPath:borderWidth]; // mutate both properties circle.radius = 2.0; circle.borderWidth = 2.0; // verify [verifyCount(observer, times(3)) observeValueForKeyPath:anything() ofObject:circle change:anything() context:context]; } - (void)testUnobserveObject { FBKVOTestCircle *circle1 = [FBKVOTestCircle circle]; FBKVOTestCircle *circle2 = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; FBKVOTestObserver *referenceObserver = [FBKVOTestObserver observer]; // observe circle1 and circle2 [controller observe:circle1 keyPath:radius options:optionsNone action:@selector(propertyDidChange:object:)]; [controller observe:circle2 keyPath:radius options:optionsNone action:@selector(propertyDidChange:object:)]; // unobserve circle2 [controller unobserve:circle2]; // add reference observer [circle1 addObserver:referenceObserver forKeyPath:radius options:optionsNone context:context]; // mutate circle1 and circle2 circle1.radius = 1.0; circle2.radius = 1.0; // verify mutation [verifyCount(observer, times(1)) propertyDidChange:referenceObserver.lastChange object:referenceObserver.lastObject]; // cleanup [circle1 removeObserver:referenceObserver forKeyPath:radius]; } - (void)testDeallocatedController { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; id observer = mockProtocol(@protocol(FBKVOTestObserving)); __attribute__((objc_precise_lifetime)) FBKVOController *controller = nil; @autoreleasepool { controller = [FBKVOController controllerWithObserver:observer]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic action:@selector(propertyDidChange)]; // verify initial [verifyCount(observer, times(1)) propertyDidChange]; // dealloc controller controller = nil; } // mutate circle.radius = 1.0; // verify unobserve all [verifyCount(observer, times(1)) propertyDidChange]; } - (void)testDeallocatedObserver { FBKVOTestCircle *circle = [FBKVOTestCircle circle]; __attribute__((objc_precise_lifetime)) id observer = mockProtocol(@protocol(FBKVOTestObserving)); FBKVOController *controller = [FBKVOController controllerWithObserver:observer]; // add mock observer [controller observe:circle keyPath:radius options:optionsBasic action:@selector(propertyDidChange)]; // verify initial [verifyCount(observer, times(1)) propertyDidChange]; // dealloc observer observer = nil; // mutate witout throwing exception circle.radius = 1.0; } - (void)testTravisContinuousIntegrationHappyDance { // happy dance return; } @end ================================================ FILE: FBKVOControllerTests/FBKVOTesting.h ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import /** Circle test object. */ @interface FBKVOTestCircle : NSObject + (instancetype)circle; @property (assign, nonatomic) float radius; @property (assign, nonatomic) float borderWidth; @end /** Observer protocol for mocking. */ @protocol FBKVOTestObserving - (void)propertyDidChange; - (void)propertyDidChange:(NSDictionary *)change; - (void)propertyDidChange:(NSDictionary *)change object:(id)object; - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; @end /** Observer test object that records the last set of values of an NSKeyValueObserving notification. */ @interface FBKVOTestObserver : NSObject + (instancetype)observer; @property (strong, nonatomic) id lastObject; @property (copy, nonatomic) NSString *lastKeyPath; @property (copy, nonatomic) NSDictionary *lastChange; @property (assign, nonatomic) void *lastContext; @end ================================================ FILE: FBKVOControllerTests/FBKVOTesting.m ================================================ /** Copyright (c) 2014-present, Facebook, Inc. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory. */ #import "FBKVOTesting.h" @implementation FBKVOTestCircle + (instancetype)circle { return [[self alloc] init]; } - (NSString *)debugDescription { return [NSString stringWithFormat:@"<%@:%p radius:%f borderWidth:%f>", self, self.class, _radius, _borderWidth]; } @end @implementation FBKVOTestObserver + (instancetype)observer { return [[self alloc] init]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { self.lastKeyPath = keyPath; self.lastObject = object; self.lastChange = change; self.lastContext = context; } - (NSString *)debugDescription { return [NSString stringWithFormat:@"<%@:%p lastKeyPath:%@ lastObject:%@ lastChange:%@ lastContext:%p>", self, self.class, _lastKeyPath, _lastObject, _lastChange, _lastContext]; } @end ================================================ FILE: FBKVOControllerTests/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: KVOController.podspec ================================================ Pod::Spec.new do |spec| spec.name = 'KVOController' spec.version = '1.2.0' spec.license = { :type => 'BSD' } spec.homepage = 'https://github.com/facebook/KVOController' spec.authors = { 'Kimon Tsinteris' => 'kimon@mac.com', 'Nikita Lutsenko' => 'nlutsenko@me.com' } spec.summary = 'Simple, modern, thread-safe key-value observing.' spec.description = <<-DESC KVOController builds on Cocoa's time-tested key-value observing implementation. It offers a simple, modern API, that is also thread safe. Benefits include: Notification using blocks, custom actions, or NSKeyValueObserving callback. No exceptions on observer removal. Implicit observer removal on controller dealloc. Thread-safety with special guards against observer resurrection. DESC spec.source = { :git => 'https://github.com/facebook/KVOController.git', :tag => "v#{spec.version.to_s}" } spec.source_files = 'FBKVOController/*.{h,m}' spec.requires_arc = true spec.ios.deployment_target = '6.0' spec.osx.deployment_target = '10.7' spec.tvos.deployment_target = '9.0' spec.watchos.deployment_target = '2.0' end ================================================ FILE: LICENSE ================================================ BSD License For KVOController software Copyright (c) 2014, Facebook, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Facebook nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: PATENTS ================================================ Additional Grant of Patent Rights Version 2 "Software" means the KVOController software distributed by Facebook, Inc. Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (subject to the termination provision below) license under any Necessary Claims, to make, have made, use, sell, offer to sell, import, and otherwise transfer the Software. For avoidance of doubt, no license is granted under Facebook’s rights in any patent claims that are infringed by (i) modifications to the Software made by you or any third party or (ii) the Software in combination with any software or other technology. The license granted hereunder will terminate, automatically and without notice, if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporate affiliates, (ii) against any party if such Patent Assertion arises in whole or in part from any software, technology, product or service of Facebook or any of its subsidiaries or corporate affiliates, or (iii) against any party relating to the Software. Notwithstanding the foregoing, if Facebook or any of its subsidiaries or corporate affiliates files a lawsuit alleging patent infringement against you in the first instance, and you respond by filing a patent infringement counterclaim in that lawsuit against that party that is unrelated to the Software, the license granted hereunder will not terminate under section (i) of this paragraph due to such counterclaim. A "Necessary Claim" is a claim of a patent owned by Facebook that is necessarily infringed by the Software standing alone. A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, or contributory infringement or inducement to infringe any patent, including a cross-claim or counterclaim. ================================================ FILE: Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' project 'FBKVOController.xcodeproj' target :FBKVOControllerTests do pod 'OCMockito', '~> 1.0' end ================================================ FILE: README.md ================================================ # [KVOController](https://github.com/facebook/KVOController) [![Build Status](https://img.shields.io/travis/facebook/KVOController/master.svg?style=flat)](https://travis-ci.org/facebook/KVOController) [![Coverage Status](https://img.shields.io/codecov/c/github/facebook/KVOController/master.svg)](https://codecov.io/github/facebook/KVOController) [![Version](https://img.shields.io/cocoapods/v/KVOController.svg?style=flat)](http://cocoadocs.org/docsets/KVOController) [![Platform](https://img.shields.io/cocoapods/p/KVOController.svg?style=flat)](http://cocoadocs.org/docsets/KVOController) Key-value observing is a particularly useful technique for communicating between layers in a Model-View-Controller application. KVOController builds on Cocoa's time-tested key-value observing implementation. It offers a simple, modern API, that is also thread safe. Benefits include: - Notification using blocks, custom actions, or NSKeyValueObserving callback. - No exceptions on observer removal. - Implicit observer removal on controller dealloc. - Thread-safety with special guards against observer resurrection – [rdar://15985376](http://openradar.appspot.com/radar?id=5305010728468480). For more information on KVO, see Apple's [Introduction to Key-Value Observing](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html). ## Usage Example apps for iOS and OS X are included with the project. Here is one simple usage pattern: ```objective-c // create KVO controller with observer FBKVOController *KVOController = [FBKVOController controllerWithObserver:self]; self.KVOController = KVOController; // observe clock date property [self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(ClockView *clockView, Clock *clock, NSDictionary *change) { // update clock view with new value clockView.date = change[NSKeyValueChangeNewKey]; }]; ``` While simple, the above example is complete. A clock view creates a KVO controller to observe the clock date property. A block callback is used to handle initial and change notification. Unobservation happens implicitly on controller deallocation, since a strong reference to the `KVOController` is kept. Note: the observer specified must support weak references. The zeroing weak reference guards against notification of a deallocated observer instance. #### NSObject Category For an even easier usage, just `#import ` for an automatic `KVOController` property on all objects. ```objc [self.KVOController observe:clock keyPath:@"date" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew action:@selector(updateClockWithDateChange:)]; ``` ## Swift KVOController works great in Swift but there are few requirements: - Your observer should subclass `NSObject`. - Properties that you observe must be marked as `dynamic`. Check the following example: ```Swift class TasksListViewModel: NSObject { dynamic var tasksList: [TaskList] = [] } /// In ViewController.swift import KVOController kvoController.observe(viewModel, keyPath: "listsDidChange", options: [.new, .initial]) { (viewController, viewModel, change) in self.taskListsTableView.reloadData() } ``` ## Prerequisites KVOController takes advantage of recent Objective-C runtime advances, including ARC and weak collections. It requires: - iOS 6 or later. - OS X 10.7 or later. ## Installation To install using [CocoaPods](https://github.com/cocoapods/cocoapods), add the following to your project Podfile: ```ruby pod 'KVOController' ``` To install using [Carthage](https://github.com/carthage/carthage), add the following to your project Cartfile: ``` github "facebook/KVOController" ``` Alternatively, drag and drop FBKVOController.h and FBKVOController.m into your Xcode project, agreeing to copy files if needed. For iOS applications, you can choose to link against the static library target of the KVOController project. Having installed using CocoaPods or Carthage, add the following to import in Objective-C: ```objective-c #import ``` ## Testing The unit tests included use CocoaPods for managing dependencies. Install CocoaPods if you haven't already done so. Then, at the command line, navigate to the root KVOController directory and type: ```sh pod install ``` This will install and add test dependencies on OCHamcrest and OCMockito. Re-open the Xcode KVOController workspace and Test, ⌘U. ## License KVOController is released under a BSD License. See LICENSE file for details. ================================================ FILE: Rakefile ================================================ # # Copyright (c) 2016-present, Facebook, Inc. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. An additional grant # of patent rights can be found in the PATENTS file in the same directory. # carthage_archive_name = 'KVOController.framework.zip' version_plist_path = File.join(File.dirname(__FILE__), 'FBKVOController', 'Info.plist') desc 'Build release archive.' task :archive do `rm -rf Carthage build` unless system('which carthage > /dev/null') abort 'Failed to find Carthage. Make sure it is installed first.' end puts 'Building release package.' unless system('carthage build --no-skip-current') abort 'Failed to build with Carthage.' end puts 'Archiving release package.' unless system('carthage archive') abort 'Failed to archive package' end system("mv #{carthage_archive_name} Carthage/") puts "Created release archive at Carthage/#{carthage_archive_name}" end desc 'Update version for next release.' task :version, [:version] do |_, args| version = args[:version] if version.nil? return end require 'plist' info_plist = Plist.parse_xml(version_plist_path) info_plist['CFBundleShortVersionString'] = version info_plist['CFBundleVersion'] = version File.open(version_plist_path, 'w') { |f| f.write(info_plist.to_plist) } end desc 'Build and archive a release.' task :release, [:version] => [:version, :archive] ================================================ FILE: codecov.yml ================================================ coverage: ignore: - FBKVOControllerTests/* status: patch: false changes: false project: default: target: 60 comment: false