My presentation @ Cocoa Kansai #54, about CXCKeyValueObserver (https://github.com/cockscomb/CXCKeyValueObserver)
cockscomb
View Slide
ͯͳϒϩά
CXC
CXCKeyValueObserver
Key Value Observation
Key Value Observation• Key Value CodingͰΦϒδΣΫτͷঢ়ଶΛࢹ• @propertyKVCͷنଇʹै͏ͨΊࢹͰ͖Δ• Modelͷঢ়ଶΛࢹͯ͠ViewΛߋ৽ͳͲͰศར
- (void)viewDidLoad{[super viewDidLoad];[self.someObject addObserver:selfforKeyPath:@"example"options:NSKeyValueObservingOptionNewcontext:NULL];}!- (void)dealloc{[self.someObject removeObserver:self forKeyPath:@"example"];}!- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context{if (object == self.someObject) {if ([keyPath isEqualToString:@"example"]) {// do something}}}
- (void)viewDidLoad{[super viewDidLoad];[self.someObject addObserver:selfforKeyPath:@"example"options:NSKeyValueObservingOptionNewcontext:NULL];}!- (void)dealloc{[self.someObject removeObserver:self forKeyPath:@"example"];}!- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context{if (object == self.someObject) {if ([keyPath isEqualToString:@"example"]) {// do something}}}1
- (void)viewDidLoad{[super viewDidLoad];[self.someObject addObserver:selfforKeyPath:@"example"options:NSKeyValueObservingOptionNewcontext:NULL];}!- (void)dealloc{[self.someObject removeObserver:self forKeyPath:@"example"];}!- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context{if (object == self.someObject) {if ([keyPath isEqualToString:@"example"]) {// do something}}}12
- (void)viewDidLoad{[super viewDidLoad];[self.someObject addObserver:selfforKeyPath:@"example"options:NSKeyValueObservingOptionNewcontext:NULL];}!- (void)dealloc{[self.someObject removeObserver:self forKeyPath:@"example"];}!- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context{if (object == self.someObject) {if ([keyPath isEqualToString:@"example"]) {// do something}}}132
• observeValueForKeyPath:ofObject:change:context: ͱ͍͏ϝιουʹશͯهड़͠ͳ͚ΕͳΒͳ͍• removeObserver:forKeyPath: ΛݺͼΕ͍͚ͯͳ͍• ͩΜͩΜෳࡶʹͳ͍ͬͯ͘• ෳਓ։ൃʹ͔ͳ͍આ
- (void)viewDidLoad{[super viewDidLoad];_observer = [[CXCKeyValueObserver alloc]initWithObservee:self.someObjectforKeyPath:@"example"options:NSKeyValueObservingOptionNewblock:^(id observee, NSString *keyPath, NSDictionary *change) {// do something}];}
• BlockΛ༻͍ͯએݴతʹهड़• observerͷڧࢀর͕ͳ͘ͳΔͱ͖ࣗಈతʹ removeObserver:forKeyPath:• ϦʔζφϒϧͳΠϯλʔϑΣʔε
https://github.com/cockscomb/CXCKeyValueObserver
CXCKeyValueObserver• MIT License• CocoaPodsରԠ• ୯ମςετ
˒14
Unit Test
The Unit Testing ofObjective-C• https://speakerdeck.com/cockscomb/the-unit-testing-of-objective-c• Kiwi• Nocilla
Unit Testing Tools• XCTest• (BDDϥΠϒϥϦ)• ඇಉظςετϥΠϒϥϦ• ΧελϜϚονϟ• Mock/StubϥΠϒϥϦ• ωοτϫʔΫstubϥΠϒϥϦ
XCTest
BDD• ϥΠϒϥϦ• Kiwi• Specta• Behavior Driven Development
ඇಉظςετ• ϥΠϒϥϦ• Kiwi• Specta• TRVSMonitor• TKRGuard• ಉظతʹॲཧ͞Εͳ͍ͱ͖ͷςετ
ΧελϜϚονϟ• ϥΠϒϥϦ• Kiwi• OCHamcrest• Expecta• ศརͳAssert
Mock/Stub• ϥΠϒϥϦ• Kiwi• OCMock• ςετ͍ͨ͠ΦϒδΣΫτͷؔ࿈ΦϒδΣΫτΛٖࣅతʹ࡞Γग़ͯ͠ςετΛ؆ུԽ
ωοτϫʔΫStub• ϥΠϒϥϦ• Nocilla• OHHTTPStubs• ωοτϫʔΫͷঢ়ଶϨεϙϯεΛٖࣅతʹ࡞Γग़ͯ͠ωοτϫʔΫपΓͷςετΛ؆ུԽ
• BDD• Kiwi• Specta• ඇಉظςετ• Kiwi• Specta• TRVSMonitor• TKRGuard!• ΧελϜϚονϟ• Kiwi• OCHamcrest• Expecta• Mock/Stub• Kiwi• OCMock• ωοτϫʔΫstub• Nocilla• OHHTTPStubs
CXCKeyValueObserver• Specta• Expecta• OCMock
@interface CXCKeyValueObserverTestsObject : NSObject@property (nonatomic, strong) id exampleProperty;@end@implementation CXCKeyValueObserverTestsObject@end!SpecBegin(CXCKeyValueObserver)describe(@"KeyValueObserver", ^{!__block CXCKeyValueObservingBlock block;__block id gotObservee;__block NSString *gotKeyPath;__block NSDictionary *gotChange;__block CXCKeyValueObserverTestsObject *observee;__block NSString *keyPath;!beforeEach(^{block = ^(id _observee, NSString *_keyPath, NSDictionary *_change) {gotObservee = _observee;gotKeyPath = _keyPath;gotChange = _change;};gotObservee = nil;gotKeyPath = nil;gotChange = nil;observee = [[CXCKeyValueObserverTestsObject alloc] init];keyPath = NSStringFromSelector(@selector(exampleProperty));});!...});SpecEnd
it(@"should call its block when the observing property changed", ^{__unused CXCKeyValueObserver *observer = [[CXCKeyValueObserver alloc]initWithObservee:observeeforKeyPath:keyPathoptions:0block:block];!observee.exampleProperty = @"exampleNewValue";!expect(gotObservee).to.equal(observee);expect(gotKeyPath).to.beIdenticalTo(keyPath);expect(gotChange).notTo.beNil();});
it(@"should call removeObserver:forKayPath:context: when deallocated", ^{id mock = [OCMockObject niceMockForClass:[CXCKeyValueObserverTestsObject class]];[[mock expect] removeObserver:[OCMArg any]forKeyPath:keyPathcontext:[OCMArg anyPointer]];!@autoreleasepool {CXCKeyValueObserver *observer = [[CXCKeyValueObserver alloc]initWithObservee:mockforKeyPath:keyPathoptions:0block:block];!observer = nil;}![mock verify];});
ࡶஊίʔφʔ
description• descriptionॻ͍͓͖͍ͯͨ• ඞཁʹԠͯ͡debugDescription• AppCodeศར
description- (NSString *)description{NSMutableString *description = [NSMutableStringstringWithFormat:@"<%@: ", NSStringFromClass([self class])];[description appendFormat:@"self.observee=%@", self.observee];[description appendFormat:@", self.keyPath=%@", self.keyPath];[description appendFormat:@", self.options=%lu", (unsigned long)self.options];[description appendFormat:@", self.block=%p", self.block];[description appendString:@">"];return description;}
AppCode
AppCode• JetBrains IDE• ߴػೳ
AppCode• ݡ͍ΦʔτίϯϓϦʔτ• ϦϑΥʔϚοτ• ίʔυͷੜ• ϦϑΝΫλϦϯά• ίʔυͷղੳ
github.com/cockscomb/CXCKeyValueObserver
DPDLTDPNCIBUFOBCMPHDPN