diff --git a/MMAnonymousClass/MMAnonymousClass.h b/MMAnonymousClass/MMAnonymousClass.h new file mode 100644 index 0000000..b091f71 --- /dev/null +++ b/MMAnonymousClass/MMAnonymousClass.h @@ -0,0 +1,21 @@ +// +// MMAnonymousClass.h +// Runtime +// +// Created by Anton Bukov on 24.02.15. +// Copyright (c) 2015 Anton Bukov. All rights reserved. +// + +#import + +@class MMAnonymousClass; +id MM_ANON(void(^block)(MMAnonymousClass *anon)); + +@interface MMAnonymousClass : NSObject + ++ (MMAnonymousClass *)anonWithBlock:(void(^)(MMAnonymousClass *anon))block; + +- (void)addMethod:(SEL)sel fromProtocol:(Protocol *)proto blockImp:(id)block; +- (void)addMethod:(SEL)sel fromClass:(Class)class blockImp:(id)block; + +@end diff --git a/MMAnonymousClass/MMAnonymousClass.m b/MMAnonymousClass/MMAnonymousClass.m new file mode 100644 index 0000000..17ac38b --- /dev/null +++ b/MMAnonymousClass/MMAnonymousClass.m @@ -0,0 +1,123 @@ +// +// MMAnonymousClass.m +// Runtime +// +// Created by Anton Bukov on 24.02.15. +// Copyright (c) 2015 Anton Bukov. All rights reserved. +// + +#import +#import +#import "MMAnonymousClass.h" + +id MM_ANON(void(^block)(MMAnonymousClass *anon)) +{ + return [MMAnonymousClass anonWithBlock:block]; +} + +@interface MMAnonymousClass () + +@property (nonatomic, strong) NSMutableDictionary *blocks; +@property (nonatomic, strong) NSMutableDictionary *protocols; +@property (nonatomic, strong) NSMutableDictionary *classes; + +@end + +@implementation MMAnonymousClass + +- (NSMutableDictionary *)blocks +{ + if (_blocks == nil) + _blocks = [NSMutableDictionary dictionary]; + return _blocks; +} + +- (NSMutableDictionary *)protocols +{ + if (_protocols == nil) + _protocols = [NSMutableDictionary dictionary]; + return _protocols; +} + +- (NSMutableDictionary *)classes +{ + if (_classes == nil) + _classes = [NSMutableDictionary dictionary]; + return _classes; +} + +- (const char *)typesForSelector:(SEL)sel +{ + NSString *protoStr = self.protocols[NSStringFromSelector(sel)]; + if (protoStr) { + Protocol *proto = NSProtocolFromString(protoStr); + struct objc_method_description descript = protocol_getMethodDescription(proto, sel, NO, YES); + if (descript.types == nil) + descript = protocol_getMethodDescription(proto, sel, YES, YES); + return descript.types; + } + + NSString *classStr = self.classes[NSStringFromSelector(sel)]; + if (classStr) { + Class class = NSClassFromString(classStr); + Method method = class_getInstanceMethod(class, sel); + if (method) + return method_getTypeEncoding(method); + } + + return nil; +} + +#pragma mark - Public Methods + ++ (MMAnonymousClass *)anonWithBlock:(void(^)(MMAnonymousClass *anon))block +{ + MMAnonymousClass *anon = [[MMAnonymousClass alloc] init]; + block(anon); + return anon; +} + +- (void)addMethod:(SEL)sel fromProtocol:(Protocol *)proto blockImp:(id)block +{ + NSString *selStr = NSStringFromSelector(sel); + self.blocks[selStr] = block; + self.protocols[selStr] = NSStringFromProtocol(proto); +} + +- (void)addMethod:(SEL)sel fromClass:(Class)class blockImp:(id)block +{ + NSString *selStr = NSStringFromSelector(sel); + self.blocks[selStr] = block; + self.classes[selStr] = NSStringFromClass(class); +} + +#pragma mark - Messsage Forwarding + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + for (NSString *key in self.blocks) + if (NSSelectorFromString(key) == aSelector) + return YES; + return [super respondsToSelector:aSelector]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + const char *types = [self typesForSelector:aSelector]; + return [NSMethodSignature signatureWithObjCTypes:types]; +} + +- (void)forwardInvocation:(NSInvocation *)anInvocation +{ + for (NSString *key in self.blocks) { + if (NSSelectorFromString(key) == anInvocation.selector) { + id block = self.blocks[key]; + anInvocation.selector = nil; + [anInvocation invokeWithTarget:block]; + return; + } + } + return [super forwardInvocation:anInvocation]; +} + +@end diff --git a/MMAnonymousClass/NSObject+MMAnonymousClass.h b/MMAnonymousClass/NSObject+MMAnonymousClass.h index 3f2003e..002521b 100644 --- a/MMAnonymousClass/NSObject+MMAnonymousClass.h +++ b/MMAnonymousClass/NSObject+MMAnonymousClass.h @@ -8,31 +8,26 @@ #import -#define MM_DEFAULT_REUSE_ID [NSString stringWithFormat:@"%s_%d", __PRETTY_FUNCTION__, __LINE__] - -// Excceptions extern NSString *const kMMExeptionMethodError; extern NSString *const kMMExeptionSelector; -// C help functions -extern inline BOOL OVERRIDE(SEL sel, id blockIMP); -extern inline BOOL ADD_METHOD(SEL sel, Protocol *p, id blockIMP); -extern inline BOOL ADD_METHOD_C(SEL sel, Class c, id blockIMP); - -// Category -@interface NSObject(MMAnonymousClass) - -// MARK: - DEPRECATED! -- (id)modifyMethods:(void(^)())blockOv __attribute__((deprecated)); -- (id)addMethod:(SEL)sel fromProtocol:(Protocol *)p isRequired:(BOOL)isReq blockImp:(id)block __attribute__((deprecated)); -- (id)overrideMethod:(SEL)sel blockImp:(id)block __attribute__((deprecated)); -- (IMP)removeInstanceMethod:(SEL)sel; - -// MARK: - Allowed -+ (id)allocAnon:(void(^)())blockOv __attribute__((deprecated)); -+ (id)allocAnonWithReuserID:(NSString*)reuseID :(void(^)())blockOv; -+ (id)newInstAnon:(void(^)())blockOv __attribute__((deprecated)); -+ (id)newInstAnonWithReuseID:(NSString*)reuseID :(void(^)())blockOv; -+ (Class)anonWithReuserID:(NSString*)reuseID; +#define MM_REUSE [NSString stringWithFormat:@"MMAnonymousClass_%s_%d", __PRETTY_FUNCTION__, __LINE__] + +Class MM_CREATE_CLASS(NSString *reuseID, Class superclass, void(^block)(__strong Class class)); +Class MM_CREATE_CLASS_ALWAYS(Class superclass, void(^block)(__strong Class class)); +id MM_CREATE(NSString *reuseID, void(^block)(__strong Class class)); +id MM_CREATE_ALWAYS(void(^block)(__strong Class class)); + +@interface NSObject (MMAnonymousClass) + ++ (Class)subclassWithReuseID:(NSString *)reuseID + configBlock:(void(^)(Class))block; + ++ (void)addMethod:(SEL)sel fromProtocol:(Protocol *)proto blockImp:(id)block; ++ (void)addMethod:(SEL)sel fromClass:(Class)class blockImp:(id)block; ++ (void)overrideMethod:(SEL)sel blockImp:(id)block; ++ (void)removeMethod:(SEL)sel __attribute__((deprecated)); ++ (void)removeClassMethod:(SEL)sel __attribute__((deprecated)); ++ (void)deleteClass; @end diff --git a/MMAnonymousClass/NSObject+MMAnonymousClass.m b/MMAnonymousClass/NSObject+MMAnonymousClass.m index b2d410d..3a3ba81 100644 --- a/MMAnonymousClass/NSObject+MMAnonymousClass.m +++ b/MMAnonymousClass/NSObject+MMAnonymousClass.m @@ -13,158 +13,131 @@ NSString *const kMMExeptionMethodError = @"MMExeptionMethodError"; NSString *const kMMExeptionSelector = @"MMExeptionSelector"; -static Class newClass = nil; -static BOOL mm_error_flag = NO; - -inline BOOL OVERRIDE(SEL sel, id blockIMP) { - if (newClass) { - Method method = class_getInstanceMethod(newClass, sel); - if (method) { - class_replaceMethod(newClass, sel, imp_implementationWithBlock(blockIMP), method_getTypeEncoding(method)); - return YES; - } - } - - //method can't be overrided. Please, check params - mm_error_flag = YES; - NSString *reason = [NSString stringWithFormat:@"Method (%@) can't be overrided. Please, check params", NSStringFromSelector(sel)]; - @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; +Class MM_CREATE_CLASS(NSString *reuseID, Class superclass, void(^block)(__strong Class class)) +{ + return [superclass subclassWithReuseID:reuseID configBlock:block]; } -inline static BOOL ADD_METHOD_IN(SEL sel,const char *types, id blockIMP){ - if ((newClass) && (types)) { - Method method = class_getInstanceMethod(newClass, sel); - if (method) { - if (OVERRIDE(sel, blockIMP)) - return YES; - } else { - IMP newImp = imp_implementationWithBlock(blockIMP); - if (class_addMethod(newClass, sel, newImp, types)) - return YES; - } - } - - //method can't be added. Please, check params - mm_error_flag = YES; - NSString *reason = [NSString stringWithFormat:@"Method (%@) can't be added. Please, check params",NSStringFromSelector(sel)]; - @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; +Class MM_CREATE_CLASS_ALWAYS(Class superclass, void(^block)(__strong Class class)) +{ + return [superclass subclassWithReuseID:nil configBlock:block]; } -inline BOOL ADD_METHOD(SEL sel, Protocol *p, id blockIMP) { - struct objc_method_description descript = protocol_getMethodDescription(p, sel, NO, YES); - if (!descript.types) - descript = protocol_getMethodDescription(p, sel, YES, YES); - return ADD_METHOD_IN(sel, descript.types, blockIMP); +id MM_CREATE(NSString *reuseID, void(^block)(__strong Class class)) +{ + Class class = MM_CREATE_CLASS(reuseID, [NSObject class], block); + return [[class alloc] init]; } -inline BOOL ADD_METHOD_C(SEL sel, Class c, id blockIMP) { - Method method = class_getInstanceMethod(c, sel); - return ADD_METHOD_IN(sel, method_getTypeEncoding(method), blockIMP); +id MM_CREATE_ALWAYS(void(^block)(__strong Class class)) +{ + Class class = MM_CREATE_CLASS_ALWAYS([NSObject class], block); + return [[class alloc] init]; } @implementation NSObject (MMAnonymousClass) -+ (Class)anonClass:(void(^)())blockOv reuseID:(NSString*)reuseID { - //universal mutex ????? - @synchronized([NSObject class]){ - - //частично использован код Sergey Starukhin - //из форка см. https://github.com/pingvin4eg/MMMutableMethods - newClass = nil; - NSString *objClassStr = NSStringFromClass([self class]); - NSString *FORMAT = @"%@_anon_%@"; - NSString *newClassStr = nil; - if (reuseID) { - reuseID = [[reuseID componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@""]; - newClassStr = [NSString stringWithFormat:FORMAT,objClassStr,reuseID]; - newClass = NSClassFromString(newClassStr); - if (newClass) { - mm_error_flag = NO; - blockOv(); - if (mm_error_flag) - return NULL; - return newClass; - } - if (blockOv == nil) - return newClass; - } else { - NSUInteger i = 0; - do { - newClassStr = [NSString stringWithFormat:FORMAT,objClassStr,[@(i) stringValue]]; - newClass = NSClassFromString(newClassStr); - i++; - } while(newClass); ++ (Class)subclassWithReuseID:(NSString *)reuseID + configBlock:(void(^)(Class class))block +{ + reuseID = [[reuseID componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:@""]; + BOOL nilReuseID = (reuseID == nil); + if (reuseID == nil) { + static NSInteger index = 0; + reuseID = [NSString stringWithFormat:@"MMAnonymousClass%@",@(index++)]; + } + + Class ret = NSClassFromString(reuseID); + if (ret == nil) { + ret = objc_allocateClassPair([self class], reuseID.UTF8String, 0); + block(ret); + if (nilReuseID) { + SEL sel = NSSelectorFromString(@"dealloc"); + IMP imp = class_getMethodImplementation(self, sel); + IMP newImp = imp_implementationWithBlock(^(id this) { + [ret deleteClass]; + }); + [ret overrideMethod:sel blockImp:^(id this){ + ((void(*)(id))newImp)(this); + ((void(*)(id))imp)(this); + }]; } - - newClass = objc_allocateClassPair([self class], [newClassStr UTF8String], 0); - if (!newClass) - return newClass; - - mm_error_flag = NO; - blockOv(); - if (mm_error_flag) - return NULL; - objc_registerClassPair(newClass); - return newClass; + objc_registerClassPair(ret); } + + return ret; } -// MARK: - Primary - -+ (id)allocAnon:(void(^)())blockOv { - return [self allocAnonWithReuserID:nil :blockOv]; -} - -+ (id)allocAnonWithReuserID:(NSString*)reuseID :(void(^)())blockOv{ - Class newClass = [self anonClass:blockOv reuseID:reuseID]; - id inst = [newClass alloc]; - newClass = nil; - return inst; -} - -+ (id)newInstAnon:(void(^)())blockOv { - return [self newInstAnonWithReuseID:nil :blockOv]; ++ (void)addMethod:(SEL)sel fromProtocol:(Protocol *)proto blockImp:(id)block { + struct objc_method_description descript = protocol_getMethodDescription(proto, sel, NO, YES); + if (descript.types == nil) + descript = protocol_getMethodDescription(proto, sel, YES, YES); + if (descript.types) { + [self addMethod:sel blockImp:block types:descript.types]; + return; + } + + NSString *reason = [NSString stringWithFormat:@"Method (%@) can't be found. Please, check %@ protocol",NSStringFromSelector(sel),NSStringFromProtocol(proto)]; + @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; } -+ (id)newInstAnonWithReuseID:(NSString*)reuseID :(void(^)())blockOv { - return [[[self class] allocAnonWithReuserID:reuseID :blockOv] init]; ++ (void)addMethod:(SEL)sel fromClass:(Class)class blockImp:(id)block { + Method method = class_getInstanceMethod(class, sel); + if (method) { + const char *types = method_getTypeEncoding(method); + [self addMethod:sel blockImp:block types:types]; + return; + } + + NSString *reason = [NSString stringWithFormat:@"Method (%@) can't be found. Please, check %@ class",NSStringFromSelector(sel),NSStringFromClass(class)]; + @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; } -+ (Class)anonWithReuserID:(NSString*)reuseID { - return [self anonClass:nil reuseID:reuseID]; ++ (void)addMethod:(SEL)sel blockImp:(id)block types:(const char *)types { + IMP newImp = imp_implementationWithBlock(block); + class_replaceMethod(self, sel, newImp, types); } -// MARK: - Deprecated! - -- (id)modifyMethods:(void(^)())blockOv { - Class newClass = [[self class] anonClass:blockOv reuseID:nil]; - object_setClass(self, newClass); - return self; ++ (void)overrideMethod:(SEL)sel blockImp:(id)block { + Method method = class_getInstanceMethod(self, sel); + if (method) { + class_replaceMethod(self, sel, imp_implementationWithBlock(block), method_getTypeEncoding(method)); + return; + } + + NSString *reason = [NSString stringWithFormat:@"Method (%@) can't be overriden. It does not exists",NSStringFromSelector(sel)]; + @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; } -- (id)addMethod:(SEL)sel fromProtocol:(Protocol *)p isRequired:(BOOL)isReq blockImp:(id)block { - return [self modifyMethods:^{ - ADD_METHOD(sel, p, block); - }]; ++ (void)removeMethod:(SEL)sel +{ + Method method = class_getInstanceMethod(self, sel); + if (method != nil) { + method_setImplementation(method,(IMP)_objc_msgForward); + return; + } + + NSString *reason = [NSString stringWithFormat: @"Method (%@) can't be removed. Method not found",NSStringFromSelector(sel)]; + @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; } -- (id)overrideMethod:(SEL)sel blockImp:(id)block { - return [self modifyMethods:^{ - OVERRIDE(sel, block); - }]; ++ (void)removeClassMethod:(SEL)sel +{ + Method method = class_getClassMethod(self, sel); + if (method != nil) { + method_setImplementation(method,(IMP)_objc_msgForward); + return; + } + + NSString *reason = [NSString stringWithFormat: @"Method (%@) can't be removed. Method not found",NSStringFromSelector(sel)]; + @throw [NSException exceptionWithName:kMMExeptionMethodError reason:reason userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; } -- (IMP)removeInstanceMethod:(SEL)sel { - Class clas = [self class]; - Method method = class_getInstanceMethod(clas, sel); - if (!method) { - @throw [NSException exceptionWithName:kMMExeptionMethodError - reason:[NSString stringWithFormat: @"Method not found: %@",NSStringFromSelector(sel)] - userInfo:@{kMMExeptionSelector:NSStringFromSelector(sel)}]; - return nil; - } - IMP oldImpl = method_setImplementation(method,(IMP)_objc_msgForward); - return oldImpl; ++ (void)deleteClass +{ + dispatch_async(dispatch_get_main_queue(), ^{ + objc_disposeClassPair(self); + }); } @end diff --git a/MMMutableMethods.podspec b/MMMutableMethods.podspec index f16359b..34f2ae9 100644 --- a/MMMutableMethods.podspec +++ b/MMMutableMethods.podspec @@ -16,12 +16,12 @@ Pod::Spec.new do |s| # s.name = "MMMutableMethods" - s.version = "0.0.2" + s.version = "0.0.7" s.summary = "Anonymous classes for Objective-C" s.description = <<-DESC - Simple library to declare anonimous classes in Objective-C runtime, - override methods for instances of classes and add methods for instances of classes. + Simple library to declare anonymous classes in Objective-C + runtime, dynamically add methods and class methods. DESC s.homepage = "https://github.com/Flanker4/MMMutableMethods" @@ -74,7 +74,7 @@ Pod::Spec.new do |s| # Supports git, hg, bzr, svn and HTTP. # - s.source = { :git => "https://github.com/Flanker4/MMMutableMethods.git", :tag => "0.0.2" } + s.source = { :git => "https://github.com/Flanker4/MMMutableMethods.git", :tag => "0.0.7" } # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..32aebfa --- /dev/null +++ b/README.MD @@ -0,0 +1,56 @@ +MMMutableMethods +======== + +Simple library to declare anonymous classes in Objective-C runtime, dynamically add methods and class methods. + +##Installation + +``` +pod 'MMMutableMethods', :git => 'https://github.com/k06a/MMMutableMethods' +``` + +##Usage + +First mechanism works on obj-c runtime class creation: + +``` +#import + +MM_CREATE(MM_REUSE,^(Class class){ + [class addMethod:@selector(onResultWithId:) + fromProtocol:@protocol(AMCommandCallback) + blockImp:^(id this,id res){ + NSLog(@"onResultWithId: %@",res); + }]; + [class addMethod:@selector(onErrorWithJavaLangException:) + fromProtocol:@protocol(AMCommandCallback) + blockImp:^(id this,JavaLangException *e){ + NSLog(@"onErrorWithJavaLangException: %@",e); + }]; +}) +``` + +Second mechanism works on simple message forward implementation: + +``` +#import + +[cmd startWithAMCommandCallback:MM_ANON(^(MMAnonymousClass *anon){ + [anon addMethod:@selector(onResultWithId:) + fromProtocol:@protocol(AMCommandCallback) + blockImp:^(id this,id res){ + NSLog(@"onResultWithId: %@",res); + }]; + [anon addMethod:@selector(onErrorWithJavaLangException:) + fromProtocol:@protocol(AMCommandCallback) + blockImp:^(id this,JavaLangException *e){ + NSLog(@"onErrorWithJavaLangException: %@",e); + }]; +})]; +``` + +First one crates new obc-j classes in runtime, it allows you to create classes `MM_CREATE_CLASS(MM_REUSE, *)` and directly instances with `MM_CREATE(MM_REUSE, *)`. Classes will be created only on first execution and reused by default, but you can avoid reusing by calling `MM_CREATE_CLASS_ALWAYS(*)` and `MM_CREATE_ALWAYS(*)`. + +The second mechanism doesn't creates any runtime instances, just remember blocks for selectors and forward method calls to them. + +I prefere second way not to create a lot of classes in runtime. IMHO it is much safer and enough powerful. \ No newline at end of file diff --git a/Readme.rtf b/Readme.rtf deleted file mode 100644 index fdc424a..0000000 --- a/Readme.rtf +++ /dev/null @@ -1,7 +0,0 @@ -{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 -{\fonttbl\f0\fswiss\fcharset0 Helvetica;} -{\colortbl;\red255\green255\blue255;} -\paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 -\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural - -\f0\fs24 \cf0 Empty} \ No newline at end of file