Is there possibility to create custom annotation which will check if parameter of method is empty array or empty string? Something like #NotEmpty in Java. I already use _Nonnull and check parameter with NSParameterAssert but I am curious does we can write custom annotation?
Thanks.
You can use macros to define an inline function.
#define isNil(x) nil ==x
Objective-C has no customizable annotations of this sort, but one of its major strengths is the versatility of its runtime.
And so, if we really wanted to, we could implement this with a wrapper class type:
#interface NotEmpty<Object> : NSProxy
#property(readonly,copy) Object object;
+ (instancetype)notEmpty:(Object)object;
- (instancetype)initWithObject:(Object)object;
#end
#implementation NotEmpty {
id _object;
}
- (id)object {
return _object;
}
+ (instancetype)notEmpty:(id)object {
return [[self alloc] initWithObject:object];
}
- (instancetype)initWithObject:(id)object {
if ([object respondsToSelector:#selector(length)]) {
NSParameterAssert([object length] != 0);
} else {
NSParameterAssert([object count] != 0);
}
_object = [object copy];
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
if (selector == #selector(object)) {
return [NSMethodSignature signatureWithObjCTypes:"#:"];
} else {
return [_object methodSignatureForSelector:selector];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation {
invocation.target = _object;
[invocation invoke];
}
#end
#interface SomeClass : NSObject
#end
#implementation SomeClass
- (void)method:(NotEmpty<NSString*> *)nonEmptyString {
// Call NSString methods, option 1
unsigned long length1 = [(id)nonEmptyString length];
// Call NSString methods, option 2
unsigned long length2 = nonEmptyString.object.length;
// Note that just printing `nonEmptyString` (not nonEmptyString.object)
// will print an opaque value. If this is a concern, then also forward
// #selector(description) from -methodSignatureForSelector:.
NSLog(#"Received: %# (length1: %lu length2: %lu)", nonEmptyString.object, length1, length2);
}
#end
int main() {
SomeClass *sc = [SomeClass new];
[sc method:[NotEmpty notEmpty:#"Not an empty string"]];
// [sc method:[NotEmpty notEmpty:#""]]; // Raises error
}
Do note that this will cause some small performance penalties.
Related
Is there any isMember kind of method for Objective-C int array?
What i mean, how can i get a BOOL value with one-line control whether myMethod's parameter is in or out of myArray?
#implementation myController
int counter;
int *myArray[10] = {2,3,9,10,11,15,16,17,18,25};
- (id) init {
if (self = [super init]) {
}
return self;
}
- (void)myMethod:x:(int)x
{
if (myArray.isMember(x) ) {
<#statements#>
}
else {
<#some other statements#>
}
}
Like this
NSArray * array = #[#1, #2, #3, #4];
if ([array containsObject:#1]) // will return true
if ([array containsObject:#5]) // will return false
I'm looking for a way to log every call to every method of a given UIView for debug purposes.
This is the code I wrote to do it
Following steps are used:
Create a proxy class by subclassing from NSProxy
Swizzle allocWithZone: on the target class and wrap the returned object with the proxy class
Log message in forwardInvocation: at the proxy class
#import <objc/runtime.h>
#interface XLCProxy : NSProxy
+ (id)proxyWithObject:(id)obj;
#end
#implementation XLCProxy
{
id _obj;
}
+ (void)load
{
{
Class cls = NSClassFromString(#"IDESourceCodeDocument");
id metacls = object_getClass(cls);
IMP imp = class_getMethodImplementation(metacls, #selector(allocWithZone:));
IMP newimp = imp_implementationWithBlock(^id(id me, SEL cmd, NSZone *zone) {
id obj = ((id (*)(id,SEL,NSZone*))(imp))(me, cmd, zone);
return [XLCProxy proxyWithObject:obj];
});
BOOL success = class_addMethod(metacls, #selector(allocWithZone:), newimp, [[NSString stringWithFormat:#"##:%s", #encode(NSZone*)] UTF8String]);
if (!success) {
NSLog(#"Add method failed");
}
}
}
+ (id)proxyWithObject:(id)obj
{
XLCProxy *proxy = [self alloc];
proxy->_obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
const char *selname = sel_getName([invocation selector]);
[invocation setTarget:_obj];
[invocation invoke];
if ([#(selname) hasPrefix:#"init"] && [[invocation methodSignature] methodReturnType][0] == '#') {
const void * ret;
[invocation getReturnValue:&ret];
ret = CFBridgingRetain([XLCProxy proxyWithObject:_obj]);
[invocation setReturnValue:&ret];
}
NSLog(#"%# %s", [_obj class], selname);
// if ([[invocation methodSignature] methodReturnType][0] == '#') {
// NSObject __unsafe_unretained * obj;
// [invocation getReturnValue:&obj];
// NSLog(#"%#", obj);
// }
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [_obj methodSignatureForSelector:sel];
}
- (Class)class
{
return [_obj class];
}
#end
You can use DTrace. Here's a one-liner:
sudo dtrace -q -n 'objc$target:YourClass::entry { printf("%c[%s %s]\n", probefunc[0], probemod, probefunc + 1); }' -p <the PID of the target process>
Is it possible to swizzle the addObject: method of NSMutableArray?
Here is what I am trying.
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#implementation NSMutableArray (LoggingAddObject)
+ (void)load {
Method addObject = class_getInstanceMethod(self, #selector(addObject:));
Method logAddObject = class_getInstanceMethod(self, #selector(logAddObject:));
method_exchangeImplementations(addObject, logAddObject);
Method original = class_getInstanceMethod(self, #selector(setObject:atIndexedSubscript:));
Method swizzled = class_getInstanceMethod(self, #selector(swizzled_setObject:atIndexedSubscript:));
method_exchangeImplementations(original, swizzled);
}
- (void)logAddObject:(id)anObject {
[self logAddObject:anObject];
NSLog(#"Added object %# to array %#", anObject, self);
}
-(void)swizzled_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
{
NSLog(#"This gets called as expected!!-----");
[self swizzled_setObject:obj atIndexedSubscript:idx];
}
I am able to swizzle some of the methods like setObject:atIndexedSubscript: but I am worried that I cant do it do the addObject: and others.
I think the below can not be swizzled? Can someone explain why ? what I am doing wrong and or a way around this?
/**************** Mutable Array ****************/
#interface NSMutableArray : NSArray
- (void)addObject:(id)anObject;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeLastObject;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
#end
You can try this with NSProxy, but I don't suggest you to use it on production code because:
it will break something (some framework may require NSMutableArray to throw exception when add nil into it to prevent more serious error later. i.e. Fail fast)
it is slow
If you really want to avoid nil checking, I suggest you to make a subclass of NSMutableArray and use it everywhere in your code. But really? There are so many ObjC code using NSMutableArray, most of them doesn't need this feature. So why you are so special?
#import <objc/runtime.h>
#interface XLCProxy : NSProxy
+ (id)proxyWithObject:(id)obj;
#end
#implementation XLCProxy
{
id _obj;
}
+ (void)load
{
Method method = class_getClassMethod([NSMutableArray class], #selector(allocWithZone:));
IMP originalImp = method_getImplementation(method);
IMP imp = imp_implementationWithBlock(^id(id me, NSZone * zone) {
id obj = ((id (*)(id,SEL,NSZone *))originalImp)(me, #selector(allocWithZone:), zone);
return [XLCProxy proxyWithObject:obj];
});
method_setImplementation(method, imp);
}
+ (id)proxyWithObject:(id)obj
{
XLCProxy *proxy = [self alloc];
proxy->_obj = obj;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation setTarget:_obj];
[invocation invoke];
const char *selname = sel_getName([invocation selector]);
if ([#(selname) hasPrefix:#"init"] && [[invocation methodSignature] methodReturnType][0] == '#') {
const void * ret;
[invocation getReturnValue:&ret];
ret = CFBridgingRetain([XLCProxy proxyWithObject:CFBridgingRelease(ret)]);
[invocation setReturnValue:&ret];
}
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [_obj methodSignatureForSelector:sel];
}
- (Class)class
{
return [_obj class];
}
- (void)addObject:(id)obj
{
[_obj addObject:obj ?: [NSNull null]];
}
- (BOOL)isEqual:(id)object
{
return [_obj isEqual:object];
}
- (NSUInteger)hash {
return [_obj hash];
}
// you can add more methods to "override" methods in `NSMutableArray`
#end
#interface NSMutableArrayTests : XCTestCase
#end
#implementation NSMutableArrayTests
- (void)testExample
{
NSMutableArray *array = [NSMutableArray array];
[array addObject:nil];
[array addObject:#1];
[array addObject:nil];
XCTAssertEqualObjects(array, (#[[NSNull null], #1, [NSNull null]]));
}
#end
You can iterate over all registered classes, check if current class is a subclass of NSMutableArray, if so, swizzle.
I would advice against it, rather act on case-by-case basis to have more predictable behavior - you never know which other system frameworks rely in this particular behavior (e.g. I can see how CoreData might rely on this particular behavior)
You can swizzle any NSMutableArray method in the following way:
#implementation NSMutableArray (Swizzled)
+ (void)load
{
Method orig = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), NSSelectorFromString(#"addObject:"));
Method override = class_getInstanceMethod(NSClassFromString(#"__NSArrayM"), #selector(addObject_override:));
method_exchangeImplementations(orig, override);
}
- (void)addObject_override:(id)anObject
{
[self addObject_override:anObject];
NSLog(#"addObject called!");
}
While implementing a subclass of NSArray (a class cluster), I was surprised to see that my overridden description method was not called. Can somebody explain what is happening here?
#interface MyArrayClassCluster : NSArray
#end
#implementation MyArrayClassCluster
{
NSArray *_realArray;
}
// Implement the class cluser stuff here
- (NSUInteger)count
{
return [_realArray count];
}
- (id)objectAtIndex:(NSUInteger)index
{
return [_realArray objectAtIndex:index];
}
// lifeCycle
- (id)initWithItems:(NSArray *)items
{
self = [super init];
_realArray = [items retain];
return self;
}
- (void)dealloc
{
[_realArray release];
[super dealloc];
}
- (NSString *)description
{
return [NSString stringWithFormat:#"My Custom Array: %p, objs:%#", self, _realArray];
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *a = #[#1, #2, #3];
NSLog(#"a: %#", a);
MyArrayClassCluster *clzCluster = [[MyArrayClassCluster alloc] initWithItems:a];
NSLog(#"clzCluster: %#", clzCluster);
}
return 0;
}
Output
2013-01-29 18:52:38.704 ClassClusterTester[31649:303] a: (
1,
2,
3
)
2013-01-29 18:52:38.707 ClassClusterTester[31649:303] clzCluster: (
1,
2,
3
)
The link #Rob pointed to had the correct answer at the bottom. An obscure fact: if something implements descriptionWithLocale:, NSLog will call that instead. Since my class is a subclass of NSArray, and NSArray implements that, my version of description was not called.
So I'm new to iOS, but I'm a bit baffled by the complexity of a simple task. I'm trying to store my custom NSObject class called 'Vehicle' in NSUserDefaults. Obviously, this can't be done, so I'll need to encode it to NSDATA first. Fine.
But that means that I need to encode each property of the class as well in the decode...
Inside my Vehicle class...
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeInt: x forKey: #"x"];
[coder encodeInt: y forKey: #"y"];
[coder encodeInt: direction forKey: #"direction"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) coder
{
if (self = [super init]) {
x = [coder decodeIntForKey: #"x"];
y = [coder decodeIntForKey: #"y"];
direction = [coder decodeIntForKey: #"direction"];
}
return (self);
} // initWithCoder
If I end up adding a new property to the vehicle class, I've got to add the encode and decode logic too. This is the same for creating a copy of a class using CopyWithZone. This leaves 3 or 4 areas where adding a new property to a class can go wrong.
I currently program mostly in LabVIEW, and we have the ability to take a class, and feed it to an encoder, which will do all the versioning and property manipulation automatically.
So I guess my question is:
Is this not heard of in iOS?
If it's not possible, is there a way to enumerate through all properties in a class and write a function to do this automatically.
You can use the objective-c runtime to find all the properties of an object and decode them, but I wouldn't recommend it. If you'd ilke, I can create a simple example for you.
EDIT: Here's an example:
#import <objc/runtime.h>
void decodePropertiesOfObjectFromCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try
{
[obj setValue:[coder decodeObjectForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't decode value for key %#.", propName);
}
}
free(properties);
}
void encodePropertiesOfObjectToCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try {
[coder encodeObject:[obj valueForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't encode value for key %#.", propName);
}
}
free(properties);
}
__attribute__((constructor))
static void setDefaultNSCodingHandler()
{
class_addMethod([NSObject class], #selector(encodeWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
encodePropertiesOfObjectToCoder(self, coder);
} copy]), "v#:#");
class_addMethod([NSObject class], #selector(initWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
if ((self = [NSObject instanceMethodForSelector:#selector(init)](self, #selector(init))))
{
decodePropertiesOfObjectFromCoder(self, coder);
}
return self;
} copy]), "v#:#");
}
This allows you to encode any object that exposes enough properties to reconstruct itself.