Objective-C与僵尸对象崩溃

时间:2013-02-04 13:50:54

标签: objective-c cocoa automatic-ref-counting gcdasyncsocket

我在Objective-c中使用GCDAsyncSocket库为JSON-RPC(稍微修改过的)服务器/客户端实现了一个实现。但是应用程序在响应请求时崩溃了。没有调试僵尸我收到此错误:

JSONRPCTestServer(1301,0x7fff7f887960) malloc: *** error for object 0x10014db10: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

xcode中的屏幕截图显示错误位于GCDAsyncSocket库的completeCurrentRead方法中。 enter image description here

调试僵尸时会记录下来:

2013-02-04 14:36:16.430 JSONRPCTestServer [1367:603] *** - [__ NSArrayI发布]:消息发送到解除分配的实例0x1005b6fd0

和仪器显示: instruments

因为当对rpc调用的响应是nsarray类型时会发生这种情况,我猜它的这个数组会导致错误。 rpc方法是:

-(NSArray *)testArray:(GCDAsyncSocket *)sock {
    return [NSArray arrayWithObjects:@"test1",@"test2", nil];
}

JSON-RPC标头是:

#import <Foundation/Foundation.h>

#import "JSONRPCMethod.h"
#import "JSONRPCResponse.h"
#import "JSONRPCError.h"
#import "JSONRPCArgument.h"
#import "JSONRPCRequest.h"
#import "GCDAsyncSocket.h"

@class GCDAsyncSocket;

@protocol JSONRPCResponseDelegate <NSObject>
@optional
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcSocket:(GCDAsyncSocket*)sock returnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedValue:(id)retVal forMethod:(NSString*)m id:(id)i;
-(void)rpcReturnedError:(JSONRPCError*)err forMethod:(NSString*)m id:(id)i;
@end

@interface JSONRPC : NSObject {
    NSMutableArray *supportedMethods;

    GCDAsyncSocket *mainSocket;
    NSMutableArray *connectedSockets;

    NSMutableArray *responseDelegates;
    BOOL isServer;
}

+(JSONRPC*)sharedConnection;

-(BOOL)startServer:(NSUInteger)port;
-(BOOL)connectToServer:(NSString*)host port:(NSUInteger)port;

-(BOOL)addMethod:(JSONRPCMethod*)method;
-(void)removeMethod:(JSONRPCMethod*)method;
-(void)removeMethodsWithTarget:(id)target;

-(void)sendRequest:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendRequest:(JSONRPCRequest*)req;

-(void)sendNotification:(JSONRPCRequest*)req toSocket:(GCDAsyncSocket*)sock;
-(void)sendNotification:(JSONRPCRequest*)req;
-(void)sendNotification:(JSONRPCRequest*)req toSocketsWithUserData:(id)userData;


@end

.m是:

#import "JSONRPC.h"
#import "GCDAsyncSocket.h"

#define kGeneralReadTimeout -1.0
#define kGeneralWriteTimeout -1.0

@implementation JSONRPC

- (id)init
{
    self = [super init];
    if (self) {
        isServer = NO;
        supportedMethods = [[NSMutableArray alloc] init];
        mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        connectedSockets = [[NSMutableArray alloc] init];
        responseDelegates = [[NSMutableArray alloc] init];
    }
    return self;
}

+ (JSONRPC *)sharedConnection {
    static JSONRPC *sharedSingleton;

    @synchronized(self)
    {
        if (!sharedSingleton)
            sharedSingleton = [[JSONRPC alloc] init];

        return sharedSingleton;
    }
}

-(BOOL)startServer:(NSUInteger)port {
    // Now we tell the socket to accept incoming connections.
    // We don't care what port it listens on, so we pass zero for the port number.
    // This allows the operating system to automatically assign us an available port.
    isServer = YES;
    NSError *err = nil;
    if ([mainSocket acceptOnPort:port error:&err]) {

    } else {
        DDLogError(@"Error while starting JSON-RPC Server: %@",err);
        return NO;
    }

    DDLogInfo(@"Started JSON-RPC Server on port %hu",[mainSocket localPort]);
    return YES;
}

-(BOOL)connectToServer:(NSString *)host port:(NSUInteger)port {
    NSError *err = nil;
    mainSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    [mainSocket connectToHost:host onPort:port error:&err];
    if(err != nil) {
        DDLogError(@"Couldn't connect to host %@:%lu (Error: %@)",host,port,err);
        return NO;
    }
    return YES;
}

-(BOOL)addMethod:(JSONRPCMethod *)method {
    for (JSONRPCMethod *meth in supportedMethods) {
        if([meth.name isEqualToString:method.name]) {
            return NO;
        }
    }
    [supportedMethods addObject:method];
    return YES;
}

-(void)removeMethod:(JSONRPCMethod *)method {
    [supportedMethods removeObject:method];
}

-(void)removeMethodsWithTarget:(id)target {
    NSMutableArray *toRemove = [[NSMutableArray alloc] init];
    for (JSONRPCMethod *meth in supportedMethods) {
        if(meth.target == target) {
            [toRemove addObject:meth];
        }
    }
    [supportedMethods removeObjectsInArray:toRemove];
}

-(void)sendRequest:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock {
    [responseDelegates addObject:req];
    [req setIdentifier:[NSNumber numberWithUnsignedInteger:[responseDelegates count]-1]];
    [self sendPackage:[req dictionary] toSocket:sock];
}

-(void)sendRequest:(JSONRPCRequest *)req {
    [self sendRequest:req toSocket:mainSocket];
}

-(void)sendNotification:(JSONRPCRequest *)req toSocket:(GCDAsyncSocket*)sock{
    [req setIdentifier:nil];
    [self sendPackage:[req dictionary] toSocket:sock];
}

-(void)sendNotification:(JSONRPCRequest *)req {
    [self sendNotification:req toSocket:mainSocket];
}

-(void)sendNotification:(JSONRPCRequest *)req toSocketsWithUserData:(id)userData {
    NSMutableArray *matchingSockets = [[NSMutableArray alloc] init];
    for (GCDAsyncSocket*sock in connectedSockets) {
        if(sock.userData == userData) {
            [matchingSockets addObject:sock];
        }
    }
    if(matchingSockets.count == 0)
        return;

    [req setIdentifier:nil];
    NSData *pkgData = [self writableDataFromDictionary:[req dictionary]];
    for (GCDAsyncSocket*sock in matchingSockets) {
        [sock writeData:pkgData withTimeout:kGeneralWriteTimeout tag:0];
    }
}


#pragma mark Socket Delegate

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
    [connectedSockets addObject:newSocket];
    [newSocket readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
    DDLogVerbose(@"socket:didConnectToHost:%@ port:%hu", host, port);
    [sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    DDLogVerbose(@"socketDidDisconnect:%@", err);
    if(isServer)
        [connectedSockets removeObject:sock];
}


-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    // So, we've received something from the client
    // As we have to cut out the last 0x00 for JSONSerialization it has to be longer than 1 byte
    if(data.length > 1) {
        // Shorten out that 0x00
        data = [data subdataWithRange:NSMakeRange(0, data.length-1)];
        // Try to serialize
        NSError *err;
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
        DDLogVerbose(@"Dict: %@",dict);
        if(err != nil) {
            // The package isn't json
            JSONRPCResponse *response = [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
            [self sendPackage:[response dictionary] toSocket:sock];
        } else {
            JSONRPCResponse *response = [self handleDictionary:dict fromSocket:sock];
            if(response != nil) {
                [self sendPackage:[response dictionary] toSocket:sock];
            }
        }
    }
    [sock readDataToData:[GCDAsyncSocket ZeroData] withTimeout:kGeneralReadTimeout tag:0];
}

-(JSONRPCResponse*)handleDictionary:(NSDictionary*)dict fromSocket:(GCDAsyncSocket*)sock {
    // Check if the "id" is of a correct value/type
    id identifier = [dict valueForKey:@"id"];
    if(!(identifier == nil || [identifier isKindOfClass:[NSNumber class]])) {
        return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest]];
    }

    // Handle the package
    NSString *methodName = [dict valueForKey:@"method"];
    id errorValue = [dict valueForKey:@"error"];
    id resultValue = [dict valueForKey:@"result"];
    if([methodName isKindOfClass:[NSString class]]) {
        // We have a string as method
        DDLogInfo(@"Method: %@, object: %@",methodName,[dict valueForKey:@"params"]);
        for (JSONRPCMethod *method in supportedMethods) {
            if([method.name isEqualToString:methodName]) {
                id result = nil;
                if(isServer == YES) {
                    // It is a server and the method needs to know from where the call comes
                    result = [method invoke:[dict valueForKey:@"params"] fromSocket:sock];
                } else {
                    // It is a client and we don't need to know where the call is from. it can only be the server.
                    result = [method invoke:[dict valueForKey:@"params"]];
                }
                if([result isKindOfClass:[JSONRPCError class]]) {
                    return [JSONRPCResponse responseWithError:result id:identifier];
                } else if(result != nil) {
                    return [JSONRPCResponse responseWithResult:result id:identifier];
                } else {
                    return nil;
                }
            }
        }
    } else if(resultValue != nil) {
        // We have a response from our partner
        //DDLogInfo(@"Result: %@",resultValue);
        NSUInteger responseDelegateId = [identifier unsignedIntegerValue];
        if(responseDelegateId < [responseDelegates count]) {
            JSONRPCRequest *originalRequest = [responseDelegates objectAtIndex:responseDelegateId];
            if(originalRequest.sender == nil) {
                return nil;
            }
            @try {
                SEL selector;
                if(isServer) {
                    selector = @selector(rpcSocket:returnedValue:forMethod:id:);
                } else {
                    selector = @selector(rpcReturnedValue:forMethod:id:);
                }
                NSMethodSignature *signature = [originalRequest.sender methodSignatureForSelector:selector];
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                [invocation setTarget:originalRequest.sender];
                [invocation setSelector:selector];
                NSUInteger startArg = 2;
                if(isServer) {
                    [invocation setArgument:&sock atIndex:startArg];
                    startArg++;
                }
                NSString *method = [originalRequest method];
                id orgId = [originalRequest identifier];
                [invocation setArgument:&resultValue atIndex:startArg];
                [invocation setArgument:&method atIndex:startArg+1];
                [invocation setArgument:&orgId atIndex:startArg+2];
                [invocation invoke];
            }
            @catch (NSException *exception) {
                DDLogWarn(@"Couldn't find a response: %@",exception);
            }
        }
    } else if(errorValue != nil) {
        // We have a string as method
        DDLogInfo(@"Error: %@",errorValue);
    } else {
        return [JSONRPCResponse responseWithError:[JSONRPCError invalidRequest] id:identifier];
    }
    return nil;
}

-(void)sendPackage:(NSDictionary *)dict toSocket:(GCDAsyncSocket *)sock {
    NSData *answerData = [self writableDataFromDictionary:dict];
    [sock writeData:answerData withTimeout:kGeneralWriteTimeout tag:0];
}

-(NSData*)writableDataFromDictionary:(NSDictionary*)dict {
    NSMutableData *answerData = [[NSMutableData alloc] init];
    // Serialize the answer
    NSError *err = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&err];
    if(err != nil) {
        // Log
        DDLogError(@"JSON-RPC had an internal error while converting the answer to JSON. The answer-dictionary is: %@",dict);
        // Form answer manually
        jsonData = [NSData dataWithBytes:"{\"error\":{\"code\":-32700,\"message\":\"Parse error\"}}"
                                  length:49];
    }
    // Format the answer
    [answerData appendData:jsonData];
    [answerData appendData:[GCDAsyncSocket ZeroData]];
    return answerData;
}

我只是不知道如何解决这个问题。为什么我一直在用nsarray得到一个错误,但是当我返回一个nsnumber它有效?我该如何解决这个问题?

0 个答案:

没有答案