aozhimin / iOS-Monitor-Platform

:books: iOS 性能监控 SDK —— Wedjat(华狄特)开发过程的调研和整理
https://aozhimin.github.io/iOS-Monitor-Platform/
MIT License
2.58k stars 491 forks source link

使用NSProxy的时候遇到问题了 #11

Open hcl416029105 opened 6 years ago

hcl416029105 commented 6 years ago

我在使用NSProxy替换NSURLSession原来的的delegate的时候,再使用AFNetwork的AFHTTPSessionManager的GET方法的时候崩溃了,不知道是什么原因,具体代码如下:

static void swizzleClassMethod(Class theClass, SEL originalSelector, SEL swizzledSelector) { Method origMethod = class_getClassMethod(theClass, originalSelector); Method newMethod = class_getClassMethod(theClass, swizzledSelector);

theClass = object_getClass((id)theClass);

if(class_addMethod(theClass, originalSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
    class_replaceMethod(theClass, swizzledSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
    method_exchangeImplementations(origMethod, newMethod);

}

@interface NSURLSession()

@property(nonatomic, strong) NSURLDelegateProxy *delegateProxy;

@end;

@implementation NSURLSession (DelegateMonitor)

}

@end

@interface NSURLDelegateProxy() @property(nonatomic, weak) id proxyTarget;

@end

@implementation NSURLDelegateProxy

}

都是很简单的调用,AFHTTPSessionManager的调用方法如下:

    AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
    NSURLSessionDataTask *task = [session GET:@"https://hcz-static.pingan.com.cn/fuelCard/fuelCard.zip" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

    }];

错误提示: 2017-12-08 18:08:44.923871+0800 AFNetworkingDemo[82876:14788953] *** NSForwarding: warning: object 0x608000036560 of class '__NSMessageBuilder' does not implement doesNotRecognizeSelector: -- abort

hcl416029105 commented 6 years ago

image

aozhimin commented 6 years ago

@hcl416029105 我先看看,晚上给你答复。

hcl416029105 commented 6 years ago

如果不用AFHTTPSessionManager,直接用原生的NSURLSession,没有问题: NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];

    NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://hcz-static.pingan.com.cn/fuelCard/fuelCard.zip"]];

    [task resume];

这种可以正常调用。

好的,非常感谢你的解答

aozhimin commented 6 years ago

@hcl416029105 多谢提供的信息,我这边看了下,用 AFNetworking 出问题的原因是在 CFNetwork -[__NSCFURLLocalSessionConnection initWithTask:delegate:delegateQueue:] 方法中会调用 __NSURLSessionLocalcan_delegate_task_willSendRequestForEstablishedConnection 方法,而这个方法中会执行下列方法:

[delegate respondsToSelector:@selector(URLSession:task:_willSendRequestForEstablishedConnection:completionHandler:)]

delegate 此时已经是你的 NSURLDelegateProxy 对象,他无法响应 respondsToSelector,所以会走消息转发的流程中,也就会走到 NSURLDelegateProxy 对象 的 methodSignatureForSelector

- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{

    NSMethodSignature *methodSignature = nil;

    if ( [self.proxyTarget respondsToSelector:sel]) {
        methodSignature = [self.proxyTarget methodSignatureForSelector:sel];
    }

    return methodSignature;
}

但是因为你的 proxyTargetinitWithTarget 被赋值为 AFHTTPSessionManager 对象,而且这个属性为弱引用,而执行到 methodSignatureForSelector 时,AFHTTPSessionManager 对象已经被释放,所以这里 return 的 methodSignature 实际为 nil,导致消息转发流程失败,也就有了下面的输出:

*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort

将属性改为 strong,发现问题已经没有了。

@property(nonatomic, strong) id proxyTarget;

当然你可能会问为什么直接使用 NSURLSession 的 API 无此问题。

    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];

    NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://hcz-static.pingan.com.cn/fuelCard/fuelCard.zip"]];

    [task resume];

事实上使用 NSURLSession 的 API 和 AF 在 CFNetwork 层都会走上面的那段逻辑,但是由于sessionWithConfiguration:delegate:delegateQueue 方法的入参 delegate 恰好是一个强引用的对象,比如我这里可能是一个 ViewController,所以即使 proxy 那边是弱引用,执行到 methodSignatureForSelector 能够正常返回方法签名对象,消息转发的流程得以正常执行,所以这种场景无此问题。

具体你可以在自己项目中调试来验证,建议在 NSURLDelegateProxymethodSignatureForSelector 方法下断点来验证。

aozhimin commented 6 years ago

@hcl416029105 另外关于网络监控这块可以看下我的另外一篇文章揭秘 APM iOS SDK 的核心技术,文章对听云的网络监控实现进行了一些探索,希望能对你有所帮助。

hcl416029105 commented 6 years ago

网络监控搞复杂了,不用各种swizze,各种hook,直接取getifaddrs获取的数据

aozhimin commented 6 years ago

@hcl416029105 getifaddrs() 函数拿到的 ifaddrs struct 最多也是本地地址的信息吧,类似 Local IP 这些,网络监控要获取到的远远不止这个。

MoShenGuo commented 5 years ago

image 如果强引用的话 会不会引起对象释放不了的问题吗

karosLi commented 5 years ago

image 如果强引用的话 会不会引起对象释放不了的问题吗

同问