0

Following problem: I receive an Object from a framework (which is not instantiable) and I want to extend it. When I make a category, the problem is, that it doesn't have an effect on the existing object.

I thought of isa swizzling. So let the isa field point to the extended "list of selectors". But that doesn't seem to be possible? (Syntax for it?)

Does anyone know a better approach to do it ?

That is the code:

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
      //want to do something that uses the extension
}

And I want to extend CBATTRequest. I think the problem lies in CoreBluetooth?

This is how I make my category:

BLERequestable.h

@protocol BLERequestable <NSObject>

- (nonnull NSString *)getCentralUUID;
- (nonnull NSString *)getCharacteristicUUID;
- (nullable NSData*)getData;
- (void)setData:(nullable NSData *) data;

@end

CBATTRequest+Requestable.h

#import <CoreBluetooth/CoreBluetooth.h>
#import "BLERequestable.h"

@interface CBATTRequest (Requestable) <BLERequestable>

@end

CBATTRequest+Requestable.m

#import "CBATTRequest+Requestable.h"

@implementation CBATTRequest (Requestable)

 - (NSString *)getCentralUUID {
    return self.central.identifier.UUIDString;
}

- (NSString *)getCharacteristicUUID {
    return self.characteristic.UUID.UUIDString;
}

- (NSData*)getData {
    return self.value;
}

- (void)setData:(NSData *) data {
    self.value = data;
}


@end

And I import the Category everywhere I want to use it.

Maik
  • 85
  • 8
  • 3
    When you say "extend it," do you mean "add methods to it" or do you mean "change what an existing method does?" The former is what categories do, and absolutely should work (if you're having trouble, show the code and the error). The latter can be done with swizzling, but is very dangerous and there are many cases where it will not work, and cases where it may seem to work, but break things subtly, and cases where it works in one version of the OS and breaks in later versions. It should be strongly avoided in production code. – Rob Napier Dec 14 '17 at 15:54
  • I want to add methods to it. you are right , @RobNapier. normally it should work with an extension. but in this case it doesn't. I edited my post with some code. – Maik Dec 15 '17 at 08:33
  • How are you adding the category to `CBATTRequest`? It should just be `@interface CBATTRequest (MyExtension)` and then `@implementation CBATTRequest` and your code. That should work fine. – Rob Napier Dec 15 '17 at 14:49
  • @RobNapier, I added the code above. – Maik Dec 15 '17 at 15:18
  • Do you import `CBATTRequest+Requestable.h` where you use it? (`BLERequestable.h ` doesn't tell the compiler about your category.) And what errors do you receive? – Rob Napier Dec 15 '17 at 15:45
  • Note that these names are bad for ARC. Prefixing a method with `get` has a specific meaning in Cocoa memory management, and this doesn't match that. The names should just be `centralUUID`, etc, not `getCentralUUID`. (These names will "work" most of the time. They can just create subtle problems with KVC/KVO and ARC.) – Rob Napier Dec 15 '17 at 15:46
  • yep I import CBATTRequest+Requestable.h. I get "unrecognised selector error" – Maik Dec 15 '17 at 16:31
  • @RobNapier can you explain why it is a problem to prefix a method with get? When no property is called like this, where should be the problem ? – Maik Dec 15 '17 at 16:35
  • Cocoa uses naming to drive both KVC (key value coding) and memory management. For example, methods prefixed with `new` automatically return a +1 retain count (most methods return a net-0 retain count). KVC uses naming rules to construct selectors. The `get` prefix means that the method returns `void` or `BOOL` and returns its value by reference. See `[NSMatrix getRow:column:ofCell]` for an example of that. (`get` methods are very rare in Cocoa; they're usually are used when they can fail, or if you need to return multiple things.) – Rob Napier Dec 15 '17 at 16:42
  • I can't reproduce this problem. It compiles without problem for me. I suspect you're not importing `CBATTRequest+Requestable.h` into the `.m` file that tries to use the method. – Rob Napier Dec 15 '17 at 16:43
  • this can be the problem! I pass the extended object to another function where a object that conforms to the above protocol is expected. and there I don't import the CBATTRequest+Requestable.h. I will test this. But why should I need to do this? it is a runtime error. the object won't change cause of an import ? – Maik Dec 15 '17 at 17:12
  • when I make a category of an existing class. that I extend the interface and all objects that are created will get those new methods (table with methods gets extended). when I want to use those new methods, I need to import the category header, because otherwise the compiler doesn't know for syntax. but my code compiled and the new method should be in "function table" so it should can answer ? – Maik Dec 15 '17 at 17:16
  • I don't understand what you're doing. You still haven't listed what code you're actually calling, and what error you're receiving. – Rob Napier Dec 15 '17 at 19:33
  • I really don't understand what is going with my code. I made a new minimal project where I test it with the category and everything works fine. i make the same stuff in the "real" project and at runtime, when i get the writeRequests and wants call the method i added on one request, it throws the exception: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CBATTRequest someFunction]: unrecognized selector sent to instance 0x604000073080'. i called the added function "someFunction" just for testing. – Maik Dec 16 '17 at 14:50
  • even when I click on "Jump to definition" with xCode it finds the right implementation. – Maik Dec 16 '17 at 14:52
  • It is possible that `CBATTRequest` is part of a class cluster and that the thing being returned to you isn't really a `CBATTRequest` at all, but only has the same interface. If that's the case, you can't use categories on them. (And you likely can't swizzle them or otherwise modify them.) If you want to adapt this to your protocol, you'll have to build a wrapper to hold it. – Rob Napier Dec 16 '17 at 15:09
  • i found the source of error! The class with the bluetooth stuff is a part of a lib from me. I use this lib from a exe. the error disappears, when i put the the category header and implementation file in the exe target, not in the lib. – Maik Dec 16 '17 at 15:14
  • with isa swizzling i already get it to work. I subclasses CBATTRequest and define my new methods. then i isa swizzle my cbattrequest to this subclass, which should be kind of save in my opinion. but with just a category it is naturally nicer. – Maik Dec 16 '17 at 15:18

1 Answers1

0

After long researching and testing (thanks to Rob Napier), I found the source of error.

My project consists of a library and and executable target. In the library I defined my category and used it inside. The problem is, that when it comes to linking due to the executable, the o-file with my category isn't linked. See this stack post for further details on problems with categories in static libraries.

One possible solution would be setting the linker flag from the exe target to -Objc.

But I don't like this solution, because the library's ability to work properly would depend on the exe.

So I included the implementation of the category in the m-file where I use it.

If somebody has another (better) solution, I would be glad to see it. Otherwise I would close this question.

Maik
  • 85
  • 8