AFNetworking의 예쁜 디테일
첫머리에 쓰다
최근에 AFNetworking 원본 코드를 다시 읽었는데 예전에 읽지 못했던 코드가 많아서 왜 이렇게 썼는지 천천히 읽을 수 있게 되었다.그 과정에서 AFNetworking 작가의 디테일, 편안하고 깔끔한 추구에 탄복했다.일부 개인이 쓴 아름다운 용법을 총괄해 보면 본고는AFNetworking 원본의 구체적인 아마추어 실현을 연구하는 것이 아니라 코드 자체와 디자인 측면에서 총괄하고자 한다(원본 해석 추천AFNetworking은 도대체 무엇을 했는가? 이 글).
1.Dispatch_once 메서드 선언 C 언어 변수 메서드
OC의property Getter 방법처럼 느껴진다. C 언어가 불러오기 게으른 느낌.static 수식자는 이 컴파일러에서만 볼 수 있고 (OC에 대응하는 것은.m 파일) 한 번만 실행된다.
if(!object)
와 비슷한 느낌.다음 예에서 queue를 만드는 방법을 만들었습니다. 호출한 후에 같은 변수로 되돌아옵니다.
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
2. 반사 메커니즘을 사용하여 KeyPath 결정
KVO에서 우리는 일반적으로 하나의 속성을 통과하는 것을 관찰할 수 있지만 @property는 사실 문법 설탕이고 속성 = ivar (실례 대상) +setter 방법 +getter 방법이다.Getter 방법명은 속성 d의 이름입니다.OC의 반사 메커니즘
NSStringFromSelector
방법을 이용하여 속성을 얻는 Getter 방법의 문자열은 사실 속성의KeyPath이다.이러한 KeyPath는 잘못 쓰거나 속성 정의를 보기 쉽습니다. [progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
3. 로unused 수식자 수식에 사용되지 않는 Delegate의 변수
일반 프로토콜delegate가 성명할 때delegate 약한 소유자를 첫 번째 매개 변수로delegate 방법에 전송하지만,delegate의 실현자는 delegate 대상이 누가 가지고 있는지 무관심하거나 구분하지 않을 때가 있습니다.
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error{
//......
}
4. 코코아 족의 구덩이를 피해
Foudation 프레임워크에서 일부 클래스는 사실 클래스족이다. 예를 들어 NSArray, 생성된 어떤 NSArray 대상은 실제로는 NSArray의 하위 클래스일 수 있다.그래서 Method Swizzling으로 Hook의 일부 시스템 클래스를 제거할 때 일부 클래스는 사실상 하위 클래스이고 심지어 서로 다른 시스템 버전의 계승 체인과 방법의 실현이 모두 다르다는 것을 주의해야 한다(부류의 동명 방법을 사용한 것은 아니다).AFURL Session Manager에서 훅 시스템의 NSURL Session Task의resume와suspend 방법을 위한 알림을 추가합니다.NSURLSessionTask는 클래스족이고 iOS7과 iOS8에서 Task 클래스의 계승체인이 다르기 때문에 다음과 같은 엄격한 코드가 있다.
if (NSClassFromString(@"NSURLSessionTask")) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];// NSURLSession
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];// session task
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));//
Class currentClass = [localDataTask class];//
while (class_getInstanceMethod(currentClass, @selector(resume))) //
{
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));//
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));//
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {//
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];//
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
5. 메소드를 클래스에 추가하여 Method Swizzling 진행
또한 직접 바꾸는 것이 여러 번의 교환을 가져와 원래의 방법을 어지럽히는 문제를 피하기 위해 먼저 바꿔야 하는 방법add를 교체해야 하는 클래스(하나의 던전을 생성하는 것과 같다)에 넣고 그 클래스 안의 던전 방법을 교환한다. 즉, 원래 이 방법을 가진 클래스에 영향을 주지 않는 방법이다.
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
6. GCD의 동기화 방법으로 코드 블록을 봉인
iOS8 이하
dataTaskWithRequest
는 생성된task가 동시에 실행되어taskIdentifer가 우발적으로 유일한 것이 아니다. 해결 방법은 이 방법을 직렬로 실행하는 동시에Dispatchsync 결과가 반환될 때까지 기다립니다.호출할 때 C 방법을 봉인했다. url_session_manager_create_task_safely(^{
downloadTask = [self.session downloadTaskWithRequest:request];
});
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
// ,
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
//iOS8 ,
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
7.respondsToSelector를 다시 써서Delegate가 실현한 판단 근거를 변경합니다
AFNetworking 내부의 AFURL Session Manager는 모든 NSURL Session Delegate 방법을 인수하여 외부셋이 들어오는 블록으로 전환시켰다. 그 중 일부 전환은 아무런 처리도 하지 않고 단순히 블록으로 전환되었다.그래서 이Delegate 방법에 응답하는지 여부는 사실 Block이 존재하는지 여부입니다. 그래서 내부에서respondsTo Selector를 다시 썼습니다.
- (BOOL)respondsToSelector:(SEL)selector {
if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
return self.taskWillPerformHTTPRedirection != nil;
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
return self.dataTaskDidReceiveResponse != nil;
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
return self.dataTaskWillCacheResponse != nil;
} else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
return self.didFinishEventsForBackgroundURLSession != nil;
}
return [[self class] instancesRespondToSelector:selector];
}
8.@dynamic 키워드는 부모setter &getter를 기본적으로 호출합니다
하위 클래스가 부모 클래스의 속성을 다시 설명할 때 기본적으로 setter &getter를 합성하고 부모 클래스의 기본 구현을 덮어씁니다.하위 클래스 성명 속성을 기본적으로 호출하고자 하는 setter &getter는 @dynamic 키워드를 사용할 수 있습니다.
AFHTTPSessionManager는AFURLSessionManager의 하위 클래스입니다. 또한 보안 Policy 속성을 설명하고 setter 방법을 다시 쓰지만, Getter 방법은 부모 클래스를 호출하기를 바랍니다.
@dynamic securityPolicy;
- (void)setSecurityPolicy:(AFSecurityPolicy *)securityPolicy {
if (securityPolicy.SSLPinningMode != AFSSLPinningModeNone && ![self.baseURL.scheme isEqualToString:@"https"]) {
NSString *pinningMode = @"Unknown Pinning Mode";
switch (securityPolicy.SSLPinningMode) {
case AFSSLPinningModeNone: pinningMode = @"AFSSLPinningModeNone"; break;
case AFSSLPinningModeCertificate: pinningMode = @"AFSSLPinningModeCertificate"; break;
case AFSSLPinningModePublicKey: pinningMode = @"AFSSLPinningModePublicKey"; break;
}
NSString *reason = [NSString stringWithFormat:@"A security policy configured with `%@` can only be applied on a manager with a secure base URL (i.e. https)", pinningMode];
@throw [NSException exceptionWithName:@"Invalid Security Policy" reason:reason userInfo:nil];
}
[super setSecurityPolicy:securityPolicy];
}
9. KVO는 Context를 사용하여 상위 클래스와 하위 클래스를 구분합니다.
KVO의 Api
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
는 항상 dealloc 방법- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context
에 맞추어 관찰자를 제거한다.그러나 부류와 부류가 하나의 키Path를 동시에 관찰하면 addObserver와removeObserver의 개수가 일치하지 않기 쉽다. (부류의 addObserver 방법은 호출되지 않았지만 부류의 dealloc는 호출되었다.) 리모브와 같은 키Path를 반복해서 호출하고 Crash를 호출하게 된다.그래서 context를 분류의 유일한 표식으로 하는 것이 비교적 안전한 방법이다.- (void)dealloc {
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
}
}
}
10. GCD로 동기화 속성 구현
어떤 속성 값 setter 방법과 Getter 방법의 동기화를 실현하려면 NSLock이나 @synchronized 키워드로 자물쇠를 채우는 것 외에, 병렬 대기열에서 setter와dispatchbarrier_async에 Getter를 더해 디스패치sync 구현이렇게 읽기는 동기화되고 쓰기는 읽지 않은 후에 동기화되며 잠금장치보다 성능이 좋습니다.AFHTTPRequest Serializer의 HTTP Header Field가 그렇습니다.
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}
11. NSStream 클래스에 대한 작업
AFHTTPRequestServizer가 Multipart 프로토콜 지원을 할 때 NSStream 대상을 사용했습니다.
일반적인 사용 방법은요.
//
NSInputStream *stream = [[NSInputStream alloc] initWithData:[NSData data]];
// Runloop
[stream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
//
[stream open];
//.......
// Runloop
[stream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
//
[stream close];
그 결과AFNetworking이 NSStream을 분석할 때removeFromRunLoop을 호출하지 않고 close만 호출한 것을 발견했다. 나는 처음에 빠진 줄 알았는데 나중에 데모 검증을 써서 인용 계수의 변화를 발견했을 때 다음과 같다.
// Runloop ----- +2
[stream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
// -----
[stream open];
//.......
// Runloop ----- -2
[stream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode];
// ----- -2
[stream close];
즉, 오픈과 close가 인용계수에 미치는 영향은 한 쌍이 아니다. 어느 정도에 close만 호출해도 RemoveFromRunLoop에 도달할 수 있는 이유를 설명하고 개인적으로 close와 RemoveFromRunLoop은 임의의 것을 호출해도 된다고 추측한다.참고로 AFMultipart Body Stream은 NSStream Stauts의 일부 읽기 전용 속성을 읽기와 쓰기로 바꾸고 모든 흐름을 사용자 정의합니다.
12.Category에서 속성 게으름 불러오기
OC의 관련 대상을 사용하여 빈 것인지 아닌지를 판단하지 않으면 생성되고 연결된다.
- (AFRefreshControlNotificationObserver *)af_notificationObserver {
AFRefreshControlNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFRefreshControlNotificationObserver alloc] initWithActivityRefreshControl:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
13. 헤더 파일은 Nonull 및 Nullable을 명시적으로 나타냅니다.
Swift 지원 이후 Swift의그리고!이에 대응하여 OC는 Nonull과 Nullable 키워드를 도입했다. 물론 모든 방법의 매개 변수와 속성에 대해 키워드를 성명하는 것은 매우 많은 작업량이다. 이때 우리는 시스템 매크로를 가장 앞과 마지막에 포함할 수 있다. 중간의 기본 키워드는 Nonull이다. 이때 Nullable의 매개 변수나 속성을 보충하면 된다.
//.h
NS_ASSUME_NONNULL_BEGIN
//...
@property (nonatomic, strong, nullable) id imageCache;
//...
NS_ASSUME_NONNULL_END
최후
AFNetworking은 매우 엄밀하고 예쁘게 쓴 원본으로 그 중에서 KVO & KVC, GCD, 블록에 대한 관련 대상의 운용이 매우 교묘하고 정확하며 인터페이스의 봉인, 코드의 구분도 적절하다.읽은 후에 코드 규범에 대해 또 새로운 이해가 생겼다.
QQ: 757765420 Email:[email protected]: Nemocdz Microbor: @Nemocdz
시청해주셔서 감사합니다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.