0

모의 날짜를 반환하는 스텁 NSDate는 - [NSDate init]을 제외한 카테고리를 사용하여 쉽게 수행 할 수 있습니다. - [NSDate init]은 다른 메소드와 달리 호출되지 않습니다. class_addMethod가 도움이되지 않습니다. method_exchangeImplementations, method_setImplementation on - [NSDate init]은 실제로 [NSObject init]을 변경하지만 - [NSDate init]에는 영향을 미치지 않습니다.스텁 - [NSDate init]

[NSDate setMockDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; 
NSDate *date1 = [NSDate date]; 
NSLog(@"%@", date1); 
NSLog(@"%.0f", [date1 timeIntervalSinceNow]); 

// _replacement_Method is not called! 
NSDate *date2 = [[NSDate alloc] init]; 
NSLog(@"%@", date2); 
NSLog(@"%.0f", [date2 timeIntervalSinceNow]); 

// _replacement_Method is called 
NSObject *object = [[NSObject alloc] init]; 
NSLog(@"%@", object); 

// A class with empty implementation to test inherited init from NSObject 
// _replacement_Method is called by -[MyObject init] 
MyObject *myobject = [[MyObject alloc] init]; 
NSLog(@"%@", myobject); 

출력은

2001-01-01 00:00:00 +0000 
-0 
2014-11-26 14:43:26 +0000 
438705806 
<NSObject: 0x7fbc50e19d90> 
<MyObject: 0x7fbc50e4ad30> 

있는 NSDate + Mock.m

#import "NSDate+Mock.h" 

#import <mach/clock.h> 
#import <mach/mach.h> 
#import <objc/runtime.h> 

static NSTimeInterval sTimeOffset; 
static IMP __original_Method_Imp; 

id _replacement_Method(id self, SEL _cmd) 
{ 
    return ((id(*)(id,SEL))__original_Method_Imp)(self, _cmd); 
} 

@implementation NSDate (Mock) 

+ (NSObject *)lock 
{ 
    static dispatch_once_t onceToken; 
    static NSObject *lock; 
    dispatch_once(&onceToken, ^{ 
     lock = [[NSObject alloc] init]; 
    }); 
    return lock; 
} 

+ (void)setMockDate:(NSDate *)date 
{ 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     Method m1 = class_getInstanceMethod([NSDate class], @selector(init)); 
     Method m2 = class_getInstanceMethod([NSDate class], @selector(initMock)); 
//  method_exchangeImplementations(m1, m2); 
//  class_addMethod([NSDate class], @selector(init), (IMP)_replacement_Method, "@@:"); 
     __original_Method_Imp = method_setImplementation(m1, (IMP)_replacement_Method); 
    }); 

    @synchronized([self lock]) { 
     sTimeOffset = [date timeIntervalSinceReferenceDate] - [self trueTimeIntervalSinceReferenceDate]; 
    } 
} 

+ (NSTimeInterval)mockTimeOffset 
{ 
    @synchronized([self lock]) { 
     return sTimeOffset; 
    } 
} 

+ (NSTimeInterval)trueTimeIntervalSinceReferenceDate 
{ 
    clock_serv_t cclock; 
    mach_timespec_t mts; 
    host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); 
    clock_get_time(cclock, &mts); 
    mach_port_deallocate(mach_task_self(), cclock); 
    NSTimeInterval now = mts.tv_sec + mts.tv_nsec * 1e-9 - NSTimeIntervalSince1970; 
    return now; 
} 

+ (NSTimeInterval)timeIntervalSinceReferenceDate 
{ 
    return [self trueTimeIntervalSinceReferenceDate] + [self mockTimeOffset]; 
} 

+ (instancetype)date 
{ 
    return [[NSDate alloc] initWithTimeIntervalSinceNow:0]; 
} 

+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs 
{ 
    return [[NSDate alloc] initWithTimeIntervalSinceNow:secs]; 
} 

//- (instancetype)init 
//{ 
// self = [super init]; 
// return self; 
//} 

//- (instancetype)initMock 
//{ 
// self = nil; 
// NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:0]; 
// return date; 
//} 

- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs 
{ 
    return [self initWithTimeIntervalSinceReferenceDate:[NSDate timeIntervalSinceReferenceDate] + secs]; 
} 

- (NSTimeInterval)timeIntervalSinceNow 
{ 
    NSTimeInterval t = [self timeIntervalSinceReferenceDate]; 
    return t - [NSDate timeIntervalSinceReferenceDate]; 
} 

@end 
+0

쉽게 조롱 한 코드 (예 :'[NSDate date]')에서 팩토리 메소드를 사용할 수 없습니까? –

+0

코드의 모든 부분이 [NSDate date] – keithyip

답변

4

NSDate 것은 중요한 특성을 갖는다 :

  • 그것은 클래스 클러스터
  • 그것은 이러한 경우, +alloc 만 반환 자리 표시 자에

불변이고 당신은 (클래스 __NSPlaceholderDate의) 그 자리에 -init…을 보낼 수 있습니다. 이 매개 변수가 없기 때문에 +alloc이 선택할 수있는 (개인) 서브 클래스를 결정할 수 없기 때문에 -init (__NSPlaceholderDate 또는 NSWhatever이 구현됩니다.)

이 인 경우 (NSDate) -init 교체, 아무런 효과가 없습니다. (그들은 -init…에 전달됩니다.)

당신은

  • 단순히 어떤 +alloc 반환 -init을 대체 __NSPlaceholderDate
  • -init을 교체 할 수 있습니다.
  • +alloc을 바꾸어 비공개 자리 표시자를 반환하고 -init을 덮어 씁니다.
+0

을 정확히 사용하여 NSPlaceholderDate의 init을 대체하지 못합니까? 이렇게? '''class_getInstanceMethod (NSClassFromString (@ "__ NSPlaceholderDate"), @selector (init)); ''' – hoangpx

+0

이것은 인스턴스 메소드를 얻습니다. 'class_replaceMethod()'를 찾으십시오. –

2

모의 날짜가 필요한 경우. 테스트에서 NSDate 오브젝트를 Factory pattern으로 인스턴스화하고 생산 또는 테스트를 위해 공장을 교체하는 것이 좋습니다. 이렇게하면 자신의 클래스 만 모의 날짜가되고 Apple 프레임 워크에서 사용할 수있는 메서드를 실수로 교체하는 것에 대해 걱정할 필요가 없습니다.