日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之深入解析少见却神奇的NSProxy类的多种使用

發布時間:2024/5/21 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之深入解析少见却神奇的NSProxy类的多种使用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、NSProxy 簡介

  • NSProxy 是一個實現了 NSObject 協議類似于 NSObject 的抽象基類,是根類,與 NSObject 類似:
NS_ROOT_CLASS @interface NSProxy <NSObject> {Class isa; }+ (id)alloc; + (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE; + (Class)class;- (void)forwardInvocation:(NSInvocation *)invocation; - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available"); - (void)dealloc; - (void)finalize;@property (readonly, copy) NSString *description; @property (readonly, copy) NSString *debugDescription;+ (BOOL)respondsToSelector:(SEL)aSelector; - (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos); - (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);// - (id)forwardingTargetForSelector:(SEL)aSelector;
  • 蘋果的官方文檔描述如下:
Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.NSProxy implements the basic methods required of a root class, including those defined in the NSObject protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation: and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself. A subclass’s implementation of forwardInvocation: should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation. methodSignatureForSelector: is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct an NSMethodSignature object accordingly. See the NSDistantObject, NSInvocation, and NSMethodSignature class specifications for more information.
  • 看完文檔的介紹,我們應該能對 NSProxy 有個初步印象,它僅僅是個轉發消息的場所,至于如何轉發,取決于派生類的具體實現,比如可以在內部 hold 住(或創建)一個對象,然后把消息轉發給該對象,那我們就可以在轉發的過程中做些“手腳”了,甚至也可以不去創建這些對象,去做任何你想做的事情,但是必須要實現它的 forwardInvocation: 和 methodSignatureForSelector: 方法。

二、NSProxy 模擬多繼承

  • 大致過程就是讓它持有要實現多繼承的類的對象,然后用多個接口定義不同的行為,并讓 Proxy 去實現這些接口,然后在轉發的時候把消息轉發到實現了該接口的對象去執行,這樣就好像實現了多重繼承一樣。注意:這個真不是多重繼承,只是包含,然后把消息路由到指定的對象而已,其實完全可以用 NSObject 類來實現;
  • NSObject 尋找方法順序:本類 -> 父類 -> 動態方法解析 -> 備用對象 -> 消息轉發;
  • NSproxy 尋找方法順序:本類 -> 消息轉發;
  • 同樣做“消息轉發”,NSObject 會比 NSProxy 多做好多事,也就意味著耽誤很多時間。
  • 首先新建兩個基類如下:
@implementation classA - (void)infoA {NSLog(@"classA"); } @end@implementation classB - (void)infoB {NSLog(@"classB"); } @end
  • 代理實現如下:
@interface ClassProxy : NSProxy@property(nonatomic, strong, readonly) NSMutableArray *targetArray;- (void)target:(id)target; - (void)handleTargets:(NSArray *)targets;@end
  • NSProxy 必須以子類的形式出現,因為考慮到很可能還有其他類需要 ClassProxy 來代理,這里做一個數組來存放需要代理的類:
@interface ClassProxy() @property (nonatomic, strong) NSMutableArray *targetArray; // 多個 targets 皆可代理 @property (nonatomic, strong) NSMutableDictionary *methodDic; @property (nonatomic, strong) id target; @end
  • 然后 target 和相對應的 method name 做了一個字典來存儲,方便獲取:
- (void)registMethodWithTarget:(id)target {unsigned int countOfMethods = 0;Method *method_list = class_copyMethodList([target class], &countOfMethods);for (int i = 0; i < countOfMethods; i++) {Method method = method_list[i];// 得到方法的符號SEL sel = method_getName(method);// 得到方法的符號字符串const char *sel_name = sel_getName(sel);// 得到方法的名字NSString *method_name = [NSString stringWithUTF8String:sel_name];self.methodDic[method_name] = target;}free(method_list); }
  • 然后就是最主要的兩個方法:
- (void)forwardInvocation:(NSInvocation *)invocation {SEL sel = invocation.selector;NSString *methodName = NSStringFromSelector(sel);id target = self.methodDic[methodName];if (target) {[invocation invokeWithTarget:target];} }- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {NSMethodSignature *Method;NSString *methodName = NSStringFromSelector(sel);id target = self.methodDic[methodName];if (target) {Method = [target methodSignatureForSelector:sel];} else {Method = [super methodSignatureForSelector:sel];}return Method; }
  • methodSignatureForSelector: 得到對應的方法簽名,通過 forwardInvocation: 轉發,調用和打印結果如下所示:
- (void)viewDidLoad {[super viewDidLoad];[self classInheritance]; }/*** 多繼承*/ - (void)classInheritance {classA *A = [[classA alloc] init];classB *B = [[classB alloc] init];ClassProxy *proxy = [ClassProxy alloc];[proxy handleTargets:@[A, B]];[proxy performSelector:@selector(infoA)];[proxy performSelector:@selector(infoB)]; }// 運行結果 classA classB
  • 這就利用 NSProxy 實現了多繼承。

三、NSProxy 避免循環引用

  • 由于蘋果在 iOS10 以上給出了 timer 的 block 方式,已經可以解決循環引用的問題。因此這里只說明利用 NSProxy 如何解決循環引用,實際情況可直接使用系統的方法。
  • 首先因為 NSTimer 創建的時候需要傳入一個 target,并且持有它,而 target 本身也會持有 timer 所以會造成循環引用,因此將 target 用 NSProxy 的子類代替,如下所示:
- (void)viewDidLoad {[super viewDidLoad];self.timer = [NSTimer timerWithTimeInterval:1target:[WeakProxy proxyWithTarget:self]selector:@selector(invoked:)userInfo:nilrepeats:YES];[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; }- (void)invoked:(NSTimer *)timer {NSLog(@"1"); }
  • 在 WeakProxy 中設定 target 為弱引用:
@interface WeakProxy () @property (nonatomic, weak) id target; @end@implementation WeakProxy + (instancetype)proxyWithTarget:(id)target {return [[self alloc] initWithTarget:target]; }- (instancetype)initWithTarget:(id)target {self.target = target; return self; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {return [self.target methodSignatureForSelector:sel]; }- (void)forwardInvocation:(NSInvocation *)invocation {SEL sel = invocation.selector;if ([self.target respondsToSelector:sel]) {[invocation invokeWithTarget:self.target];} } @end
  • 然后同樣利用上述兩個方法進行消息轉發即可。

四、NSProxy 實現 AOP

  • AOP(Aspect Oriented Programming),它是可以通過預編譯方式和運行時動態代理實現在不修改源代碼的情況下給程序動態添加功能的一種技術。iOS 中面向切片編程一般有兩種方式 ,一種是直接基于 runtime 的 method-Swizzling 機制來實現方法替換從而達到 hook 的目的,另一種就是基于 NSProxy。
  • OC 的動態語言的核心部分應該就是 objc_msgSend 方法的調用,該函數的聲明大致如下:
/*** 參數 1:接受消息的 target* 參數 2:要執行的 selector* 參數 3:要調用的方法* 可變參數:若干個要傳給 selector 的參數 */ id objc_msgSend(id self, SEL _cmd, ...)
  • 只要能夠 Hook 到對某個對象的 objc_msgSend 的調用,并且可以修改其參數甚至于修改成任意其它 selector 的 IMP,就可以實現 AOP:
@interface MyProxy : NSProxy {id _innerObject; // 在內部持有要 hook 的對象 } + (instancetype)proxyWithObj:(id)object; @end@interface Dog : NSObject - (NSString *)barking:(NSInteger)months; @end
  • 具體實現:
@implementation MyProxy+ (instancetype)proxyWithObj:(id)object {MyProxy * proxy = [MyProxy alloc];// 持有要 hook 的對象proxy->_innerObject = object;// 注意返回 Proxy 對象return proxy; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {// 可以返回任何 NSMethodSignature 對象,也可以完全自己構造return [_innerObject methodSignatureForSelector:sel]; }- (void)forwardInvocation:(NSInvocation *)invocation {if([_innerObject respondsToSelector:invocation.selector]){NSString *selectorName = NSStringFromSelector(invocation.selector);NSLog(@"Before calling %@",selectorName);[invocation retainArguments];NSMethodSignature *sig = [invocation methodSignature];// 獲取參數個數,本例這里值是 3,因為 objc_msgSend 隱含了 self、selector 參數NSUInteger cnt = [sig numberOfArguments];// 將簡單的將參數和返回值打印出來for (int i = 0; i < cnt; i++) {// 參數類型const char * type = [sig getArgumentTypeAtIndex:i];if(strcmp(type, "@") == 0){NSObject *obj;[invocation getArgument:&obj atIndex:i];// 這里輸出的是:"parameter (0)'class is MyProxy",也證明了這是 objc_msgSend 的第一個參數NSLog(@"parameter (%d)'class is %@", i, [obj class]);} else if(strcmp(type, ":") == 0){SEL sel;[invocation getArgument:&sel atIndex:i];// 這里輸出的是:"parameter (1) is barking:",也就是 objc_msgSend 的第二個參數NSLog(@"parameter (%d) is %@", i, NSStringFromSelector(sel));} else if(strcmp(type, "q") == 0){int arg = 0;[invocation getArgument:&arg atIndex:i];// 輸出的是:"parameter (2) is int value is 4",稍后會看到在調用 barking 的時候傳遞的參數就是 4NSLog(@"parameter (%d) is int value is %d", i, arg);}}// 消息轉發[invocation invokeWithTarget:_innerObject];const char *retType = [sig methodReturnType];if(strcmp(retType, "@") == 0){NSObject *ret;[invocation getReturnValue:&ret];// 輸出的是:"return value is wang!"NSLog(@"return value is %@", ret);}NSLog(@"After calling %@", selectorName);} } @end@implementation Dog - (NSString *)barking:(NSInteger)months {return months > 3 ? @"wang!" : @"Oh!"; } @end
  • 函數的調用,如下:
Dog *dog = [MyProxy proxyWithObj:[Dog alloc]]; [dog barking:4];
  • 上面的代碼中,可以任意更改參數、調用的方法,甚至轉發給其它類型的對象,這確實達到了 Hook 對象的目的,也就是可以實現 AOP 的功能:
typedef void(^proxyBlock)(id target,SEL selector);NS_ASSUME_NONNULL_BEGIN@interface AOPProxy : NSProxy + (instancetype)proxyWithTarget:(id)target; - (void)inspectSelector:(SEL)selector preSelTask:(proxyBlock)preTask endSelTask:(proxyBlock)endTask; @end @interface AOPProxy () @property (nonatomic, strong) id target; @property (nonatomic, strong) NSMutableDictionary *preSelTaskDic; @property (nonatomic, strong) NSMutableDictionary *endSelTaskDic; @end - (void)inspect {NSMutableArray *targtArray = [AOPProxy proxyWithTarget:[NSMutableArray arrayWithCapacity:1]];[(AOPProxy *)targtArray inspectSelector:@selector(addObject:) preSelTask:^(id target, SEL selector) {[target addObject:@"Begin"];NSLog(@"%@ 加進來之前", target);} endSelTask:^(id target, SEL selector) {[target addObject:@"End"];NSLog(@"%@ 加進來之后", target);}];[targtArray addObject:@"第一個元素"]; }( "Begin" ) 加進來之前 ( "Begin", "U662f\U4e00\U4e2a\U5143\U7d20", "End" ) 加進來之后

五、NSProxy 實現延遲初始化(Lazy Initialization)

  • 在 [SomeClass lazy] 之后調用 doSomthing,首先進入 forwardingTargetForSelector,_object 為 nil 并且不是 init 開頭的方法的時候會調用 init 初始化對象,然后將消息轉發給代理對象 _object;
  • 在 [SomeClass lazy] 之后調用 initWithXXX:,首先進入 forwardingTargetForSelector 返回 nil,然后進入 methodSignatureForSelector: 和 forwardInvocation: 保存自定義初始化方法的調用,最后調用 doSomthing,進入 forwardingTargetForSelector,_object 為 nil 并且不是 init 開頭的方法的時候會調用自定義初始化方法,然后將消息轉發給代理對象 _object。
SomeClass *object = [SomeClass lazy];// other thing ...[object doSomething]; // 在這里 object 才會調用初始化方法,然后調用 doSomething

總結

以上是生活随笔為你收集整理的iOS之深入解析少见却神奇的NSProxy类的多种使用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。