iOS 开发每天分享优质文章学习资源收集iOS开发技术分享

AFNetworking源码:AFNetworking中的那些巧

2020-04-04  本文已影响0人  jlstmac

   我们都知道AFNetworking是一个非常好用且常见的网络库,那么AFNetworking的开发者是如何做到的呢?AFNetworking中有哪些巧妙设计是我们还不知道,以后开发中可以借鉴的呢?
   这篇文章将不定期更新AFNetworking中那些巧妙的设计,如果你觉得有哪些设计是我没收录的,也可留言以告诉我。

一.利用runtime黑魔法

目的:

   这里方法替换的目的主要是想在调用系统的NSURLSessionTask 的resume方法时,能够发送AFNSURLSessionTaskDidResumeNotification通知,以达到监测系统方法调用的目的。

实现:

   _AFURLSessionTaskSwizzling类在+load方法中将_AFURLSessionTaskSwizzling 中的af_resume方法与NSURLSessionTask的resume方法交换。

@interface _AFURLSessionTaskSwizzling : NSObject

@end

@implementation _AFURLSessionTaskSwizzling
+ (void)load {
    if (NSClassFromString(@"NSURLSessionTask")) {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        //通过[session dataTaskWithURL:nil]得到一个NSURLSessionDataTask实例
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        //通过NSURLSessionDataTask实例的class获得当前的类
        Class currentClass = [localDataTask class];
        
        //while循环确保每个类的resume都会被替换。
        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];
    }
}

- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}
疑问

   通常我们需要实现这种操作的方式是实现一个子类,然后使用的时候使用子类。但是AFNetworking并不想改变我们使用NSURLSessionTask的方式,所以采用了这种巧妙的方式。
到这里大部分人可能会有以下三个疑问,理解了这几个疑问也就理解了为什么说这里设计很巧妙。

替换之后:


resume的调用.png

可以看到
   1 . 给resume发送消息的时候,实际是调用af_resume的实现。
   2 .在af_resume中给af_resume 发送消息,实际是调用resume的实现。

/**
         iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
         Many Unit Tests have been built to validate as much of this behavior has possible.
         Here is what we know:
            - NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
            - Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
            - On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
            - On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
            - On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
            - On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
            - Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
        
         Some Assumptions:
            - No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
            - No background task classes override `resume` or `suspend`
         
         The current solution:
            1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
            2) Grab a pointer to the original implementation of `af_resume`
            3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
            4) Grab the super class of the current class.
            5) Grab a pointer for the current class to the current implementation of `resume`.
            6) Grab a pointer for the super class to the current implementation of `resume`.
            7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
            8) Set the current class to the super class, and repeat steps 3-8
         */

大意是:
   1. 在OC的实现中,NSURLSessionTask的类并不是NSURLSessionTask而是依靠类族.
也就是[NSURLSessionTask class]返回的结果并不是我们想要的结果,__NSCFURLSessionTask才是实际的类。
   2. iOS8中的resmue是唯一的实现,而iOS7中__NSCFLocalSessionTask并没有调用super的resume,__NSCFURLSessionTask和__NSCFLocalSessionTask都实现了resume,所以需要循环调用superclass把两个实现都替换掉。
所以开发者采用了这种方式确保所有版本的所有resume方法都会被替换掉。

关于method swizzle,AFNetworking的作者Mattt大神在这篇文章中已经讲的很清楚了:
https://nshipster.com/method-swizzling/

目的

在UIImageView的分类中的类方法中,给UIImageView的类添加关联变量。

实现
@implementation UIImageView (AFNetworking)
+ (AFImageDownloader *)sharedImageDownloader {
    return objc_getAssociatedObject([UIImageView class], @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
    objc_setAssociatedObject([UIImageView class], @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
疑问

同样的理解了一下几个疑问的原因,也就知道了设计的巧妙之处。

- (void)testResponseIsNilWhenLoadedFromCache {
    AFImageDownloader *downloader = [UIImageView sharedImageDownloader]; 
...
}

其实这里设计的巧妙之处不仅是这些,关联变量的key使用@selector(sharedImageDownloader)也是一个很巧妙的应用,因为这样就不需要单独去声明一个key,而且利用了属性本身的名称,即简单又明了。
关于关联变量的使用,Mattt大神有一篇文章专门讲到了:
https://nshipster.com/associated-objects/
感兴趣的可以去看看。

上一篇 下一篇

猜你喜欢

热点阅读