Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hcl416029105 opened this issue Dec 8, 2017 · 9 comments
Open

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

hcl416029105 opened this issue Dec 8, 2017 · 9 comments
Assignees
Labels

Comments

@hcl416029105
Copy link

hcl416029105 commented Dec 8, 2017

我在使用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)

  • (void)load {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    swizzleClassMethod([self class],
    @selector(sessionWithConfiguration:delegate:delegateQueue:),
    @selector(hcz_sessionWithConfiguration:delegate:delegateQueue:));
    });

}

  • (NSURLSession *)hcz_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id )delegate delegateQueue:(nullable NSOperationQueue *)queue
    {
    if (delegate) {
    NSURLDelegateProxy *proxy = [[NSURLDelegateProxy alloc] initWithTarget:delegate];
    NSURLSession *session = [NSURLSession hcz_sessionWithConfiguration:configuration delegate:proxy delegateQueue:queue];

      session.delegateProxy = proxy;
      
      return session;
    

    }

    return [self hcz_sessionWithConfiguration:configuration delegate:delegate delegateQueue:queue];
    }

@EnD

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

@EnD

@implementation NSURLDelegateProxy

  • (instancetype)initWithTarget:(id)target
    {
    self.proxyTarget = target;
    return self;
    }

  • (void)forwardInvocation:(NSInvocation *)invocation
    {
    SEL sel = [invocation selector];
    if ( [self.proxyTarget respondsToSelector:sel]) {
    [invocation invokeWithTarget:self.proxyTarget];
    }

}

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

    NSMethodSignature *methodSignature = nil;

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

    return methodSignature;
    }

都是很简单的调用,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
Copy link
Author

image

@aozhimin
Copy link
Owner

aozhimin commented Dec 8, 2017

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

@aozhimin aozhimin self-assigned this Dec 8, 2017
@hcl416029105
Copy link
Author

如果不用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
Copy link
Owner

aozhimin commented Dec 8, 2017

@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
Copy link
Owner

aozhimin commented Dec 8, 2017

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

@hcl416029105
Copy link
Author

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

@aozhimin
Copy link
Owner

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

@MoShenGuo
Copy link

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

@karosLi
Copy link

karosLi commented May 31, 2019

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

同问

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants