Утечка памяти при использовании IOKit (адаптировано из USBPrivateDataSample) - PullRequest
0 голосов
/ 14 мая 2018

Хорошо.Я вернулся с другим вопросом утечки памяти.Недавно я задал похожий вопрос здесь , но оказывается, что код не полностью выполняет то, что я пытаюсь выполнить (то есть выполнить действие, когда любое USB-устройство подключается / отключается, а не только когдаустройства HID делает).

Я начал с нуля и адаптировал USBPrivateDataSample от Apple и некоторые из проектов GitHub ORSSerialPort во что-то, что, кажется, работает так, как я хочу -- кроме - есть утечки памяти.: (

Я пытался применить знания, полученные из моих предыдущих постов о борьбе с утечками памяти, но я снова застрял. Что мне не хватает? Заранее большое спасибо.

Вот код:

 **AppDelegate.h:**

#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong) NSMutableDictionary *deviceDictionary;
@end


 **AppDelegate.m:**

#import "AppDelegate.h"
#include <IOKit/usb/IOUSBLib.h>

typedef struct DeviceData 
{
    io_object_t             notification;
    CFStringRef             deviceName;
    CFStringRef             serialNum;

} DeviceData;


static IONotificationPortRef    gNotifyPort;
static io_iterator_t            gAddedIter;
static CFRunLoopRef             gRunLoop;


@implementation AppDelegate


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{

    self.deviceDictionary = [[NSMutableDictionary alloc] init];

    CFMutableDictionaryRef  matchingDict;
    CFRunLoopSourceRef      runLoopSource;
    kern_return_t           kr;

    matchingDict = IOServiceMatching(kIOUSBDeviceClassName);

    if (matchingDict == NULL)
    {

        fprintf(stderr, "IOServiceMatching returned NULL.\n");

    }

    // Create a notification port and add its run loop event source to our run loop
    // This is how async notifications get set up.

    gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
    runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);

    gRunLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);

    // Now set up a notification to be called when a device is first matched by I/O Kit.
    kr = IOServiceAddMatchingNotification(gNotifyPort,                  // notifyPort
                                          kIOFirstMatchNotification,    // notificationType
                                          matchingDict,                 // matching
                                          DeviceAdded,                  // callback
                                          NULL,                         // refCon
                                          &gAddedIter                   // notification
                                          );

    // Iterate once to get already-present devices and arm the notification
    DeviceAdded(NULL, gAddedIter);

    // Start the run loop. Now we'll receive notifications.
    CFRunLoopRun();

    // can't do these, causes EXC_BAD_ACCESS crash
    //CFRelease(matchingDict);
    //CFRelease(gNotifyPort);
    //CFRelease(kr);

    CFRelease(runLoopSource);
    CFRelease(gRunLoop);

}


void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
{

    kern_return_t   kr;
    DeviceData   *deviceDataRef = (DeviceData *) refCon;

    if (messageType == kIOMessageServiceIsTerminated)
    {

        kr = IOObjectRelease(deviceDataRef->notification);

        AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];

        if ((deviceDataRef->serialNum) && (deviceDataRef->deviceName))
        {

            // remove device from dict
            [appDelegate updateDeviceDict:
             (__bridge NSString *)deviceDataRef->serialNum:
             (__bridge NSString *)deviceDataRef->deviceName:
             NO];

        }

        free(deviceDataRef);

    }

}


void DeviceAdded(void *refCon, io_iterator_t iterator)
{

    kern_return_t   kr;
    io_service_t    usbDevice;

    while ((usbDevice = IOIteratorNext(iterator)))
    {

        io_name_t   deviceName;
        CFStringRef deviceNameCF;

        DeviceData  *deviceDataRef = NULL;

        // Add some app-specific information about this device.
        // Create a buffer to hold the data.
        deviceDataRef = malloc(sizeof(DeviceData));
        bzero(deviceDataRef, sizeof(DeviceData));

        // Get the USB device's name.
        kr = IORegistryEntryGetName(usbDevice, deviceName);
        if (KERN_SUCCESS != kr)
        {

            deviceName[0] = '\0';

        }

        CFMutableDictionaryRef usbProperties = 0;
        if (IORegistryEntryCreateCFProperties(usbDevice, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
        {

            IOObjectRelease(usbDevice);
            continue;

        }

        NSDictionary *properties = CFBridgingRelease(usbProperties);

        NSString *serialNum = properties[(__bridge NSString *)CFSTR("USB Serial Number")];
        NSNumber *builtIn = properties[(__bridge NSString *)CFSTR("Built-In")];
        deviceNameCF = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII);

        if (builtIn.integerValue != 1)
        {

            // Save the device's name to our private data.
            deviceDataRef->deviceName = deviceNameCF;
            deviceDataRef->serialNum = (__bridge CFStringRef)(serialNum);

            AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];

            [appDelegate updateDeviceDict:
             (__bridge NSString *)deviceDataRef->serialNum:
             (__bridge NSString *)deviceDataRef->deviceName:
             YES];

        }

        // Register for an interest notification of this device being removed. Use a reference to our
        // private data as the refCon which will be passed to the notification callback.
        kr = IOServiceAddInterestNotification(gNotifyPort,                      // notifyPort
                                              usbDevice,                        // service
                                              kIOGeneralInterest,               // interestType
                                              DeviceNotification,               // callback
                                              deviceDataRef,                   // refCon
                                              &(deviceDataRef->notification)   // notification
                                              );

        // Done with this USB device; release the reference added by IOIteratorNext
        kr = IOObjectRelease(usbDevice);

    }

}

- (void) updateDeviceDict : (NSString *) deviceSerial : (NSString *) deviceName : (bool) keepDevice
{

    if (deviceName)
    {

        if ((!deviceSerial) || ([deviceSerial isEqualToString:@""])) deviceSerial = deviceName;

        @try
        {

            //@throw [[NSException alloc] initWithName:@"test" reason:@"test" userInfo:nil];

            // if the device needs to be added
            if ((keepDevice) && (deviceSerial))
            {

                if (![self.deviceDictionary objectForKey:deviceSerial])
                {

                    [self.deviceDictionary setObject:[NSDictionary dictionaryWithObjectsAndKeys:deviceName, @"name", nil] forKey:deviceSerial];
                    NSLog(@"Device added: %@", deviceName);

                }

            }
            else // if the device needs to be removed
            {

                [self.deviceDictionary removeObjectForKey:deviceSerial];
                NSLog(@"Device removed: %@", deviceName);

            }
        }
        @catch (NSException *exception)
        {

            [self.deviceDictionary removeAllObjects];
            CFRunLoopRun();

        }
        @finally
        {

            NSLog(@"\n%@", self.deviceDictionary);

        }

    }
    else // name is nil
    {

        [self.deviceDictionary removeAllObjects];
        CFRunLoopRun();

    }

}

memory leak info from instruments

...