Objective-Cは、クロージャーの実装に役立つブロックを最近追加しました。 しかし、もっと何かが欲しい。 たとえば、パターンマッチングとパラメーター化されたメソッド。
楽しみのためだけに、コンパイラにパッチをあてたり、プリプロセッサで踊ったりせずに、言語自体を使用して、言語に追加しようとします。
何から来たの?
以下のすべての例は、アイデアを説明し、プロジェクトでそれを使用して、あなた自身の危険とリスクであなたがすることができます。
画像との比較により、似たようなものを書くことができます。
factorial( 0 ) -> 1
factorial( 1 ) -> 1
factorial( n ) -> n * factorial( n - 1 )
まず、画像との比較を試みます。 したがって、NSObject拡張を作成し、追加の実装をラップするブロックのタイプを宣言します。
typedef id ( ^ PatternMatchingBlock ) ( id obj ) ;
@interface NSObject ( PatternMatching )
- ( void )メソッド: ( SEL ) sel_ withParameter : ( id ) object_ useBlock : ( PatternMatchingBlock ) block_;
@end
渡された実際のパラメーターに応じて必要な実装を選択できるように、セレクターの実装を置き換える必要があります。 メソッド置換メソッド( Method Swizzling )はこれに役立ちます。 各メソッドは
objc_method
型の構造体であり、
objc_method
から
IMP
型の
method_imp
フィールドに関心があります。
IMP
は、メソッド実装のC関数へのポインターです。 メソッド置換の意味は、これらのポインターを2つのメソッドに置き換えることです。 メソッドと辞書の初期実装へのポインターを格納するクラスを作成します。そのキーは「イメージ」のオブジェクトになり、値は実装のブロックになります。
@interface PMImplementation : NSObject
@property ( nonatomic、retain ) NSMutableDictionary * impls;
@property ( nonatomic、retain ) NSValue * defaultImpl;
+ ( id ) implementationWithDefaultImpl : ( IMP ) impl_;
- ( id ) forObject : ( id ) object_ invokeWithParameter : ( id ) parameter_;
@end
static char * PMimplsKey = nil ;
@implementation PMI実装
@synthesize impls = _impls;
@synthesize defaultImpl = _default_impl;
- ( void ) dealloc {
[ _impls release ] ;
[ _default_impl release ] ;
[スーパーdealloc ] ;
}
- ( id ) initWithDefaultImpl : ( IMP ) impl_ {
if ( ! ( self = [ super init ] ) ))
nilを 返します。
self.defaultImpl = [ NSValue valueWithPointer : impl_ ] ;
self.impls = [ NSMutableDictionary辞書] ;
自己を返す ;
}
+ ( id ) implementationWithDefaultImpl : ( IMP ) impl_ {
return [ [ [ self alloc ] initWithDefaultImpl : impl_ ] autorelease ] ;
}
- ( id ) forObject : ( id ) object_ invokeWithParameter : ( id ) parameter_ {
for ( id key_ in [ self.impls allKeys ] )
if ( [ key_ isEqual : parameter_ ] ) {
PatternMatchingBlock block_ = [ self.impls objectForKey : key_ ] ;
return block_ ( parameter_ ) ;
}
IMP impl_ = [ self.defaultImpl pointerValue ] ;
return impl_ ( object_、_cmd、parameter_ ) ;
}
@end
NSObject拡張の実際の実装:
@implementation NSObject ( PatternMatching )
- ( NSMutableDictionary * ) impls {
NSMutableDictionary * impls_ = objc_getAssociatedObject ( self、 & PMImplsKey ) ;
if ( ! impls_ ) {
impls_ = [ NSMutableDictionary辞書] ;
objc_setAssociatedObject ( self、 および PMImplsKey、impls_
、OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}
return impls_;
}
- ( void )メソッド: ( SEL ) sel_ withParameter : ( id ) object_
useBlock : ( PatternMatchingBlock ) block_ {
NSString * selector_key_ = NSStringFromSelector ( sel_ ) ;
PMImplementation * impl_ = [ self.impls objectForKey : selector_key_ ] ;
if ( ! impl_ ) {
メソッドdefault_ = class_getInstanceMethod ( [ self class ] 、sel_ ) ;
IMP default_impl_ = method_getImplementation ( default_ ) ;
impl_ = [ PMImplementation implementationWithDefaultImpl : default_impl_ ] ;
[ self.impls setObject : impl_ forKey : selector_key_ ] ;
メソッドswizzed_method_ = class_getInstanceMethod ( [ self class ]
、 @ selector ( swizzledMethod :) ) ;
method_setImplementation ( default_、method_getImplementation ( swizzed_method_ ) ) ;
}
[ impl_.impls setObject : block_ forKey : object_ ] ;
}
- ( id ) swizzledMethod : ( id ) obj_ {
PMImplementation * impl_ = [ self.impls objectForKey : NSStringFromSelector ( _cmd ) ] ;
return [ impl_ forObject : self invokeWithParameter : obj_ ] ;
}
@end
すべての魔法は[-NSObjectメソッド:withParameter:useBlock:]メソッドにあります。 渡されたセレクターに対して、実装へのポインターを格納するPMImplementationオブジェクトを作成します。 次に、それを「swizzledMethod:」メソッドに置き換えます。 トリックは、どのセレクターが呼び出されたときに暗黙的に渡される_cmdパラメーターを介して実際にどのセレクターが呼び出されたかを見つけることができることです。 さて、swizzledMethodを呼び出すと、[-PMImplementation forObject:invokeWithParameter:]が呼び出され、そこでオブジェクト上のブロックを見つけて実行するか、デフォルトの実装を使用します。
[-NSObject impls]メソッドは、さまざまなセレクターのPMImplementationオブジェクトを保存するselfのランタイムに辞書を追加します。
今、すべてのために、それはすべて開始しようとしています-例えば、階乗を見つけるためのクラス:
@interface Factorial : NSObject
- ( NSDecimalNumber * )階乗: ( NSDecimalNumber * ) number_;
@end
@implementation階乗
- ( id ) init {
if ( ! ( self = [ super init ] ) ))
nilを 返します。
NSDecimalNumber * zero_ = [ NSDecimalNumber numberWithInteger : 0 ] ;
[ selfメソッド: @selector (階乗:) withParameter : zero_ useBlock : ^ ( id obj_ ) {
return ( id ) [ NSDecimalNumber numberWithInteger : 1 ] ;
} ] ;
NSDecimalNumber * one_ = [ NSDecimalNumber numberWithInteger : 1 ] ;
[ selfメソッド: @selector (階乗:) withParameter : one_ useBlock : ^ ( id obj_ ) {
return ( id ) [ NSDecimalNumber numberWithInteger : 1 ] ;
} ] ;
自己を返す ;
}
- ( NSDecimalNumber * )階乗: ( NSDecimalNumber * )数値_ {
return [ number_ decimalNumberByMultiplyingBy :
[自己階乗: [ number_ decimalNumberBySubtracting :
[ NSDecimalNumber numberWithInteger : 1 ] ] ] ] ;
}
期待通りに動作します:
Factorial * factorial_ = [ [ factorial new ] autorelease ] ;
NSNumber * number_ = [ NSDecimalNumber numberWithInteger : 10 ] ;
NSLog ( @ "factorial%@ =%@" 、number_、 [ factorial_ factorial : number_ ] ) ;
factorial 10 = 3628800
メソッドをパラメーター化するために、「isEqual:」メソッドと便利なマクロを再定義する小さなラッパークラスを作成します。
#define PMCLASS(x)[[[PMClass alloc] initWith:[x class]] autorealese]
@interface PMClass : NSObject <NSCopying>
@property ( nonatomic、retain ) クラスクラス;
- ( id ) initWith : ( Class ) class_;
@end
@implementation PMClass
@synthesize class = _class;
- ( void ) dealloc {
[ _class release ] ;
[スーパーdealloc ] ;
}
- ( id ) initWith : ( Class ) class_ {
if ( ! ( self = [ super init ] ) ))
nilを 返します。
self.class = class_;
自己を返す ;
}
- ( BOOL ) isEqual : ( id ) object_ {
return [ self.class isEqual : [ object_class ] ] ;
}
- ( id ) copyWithZone : ( NSZone * ) zone_ {
return [ [ PMClass alloc ] initWith : self.class ] ;
}
@end
これで、引数のタイプに応じて実装を選択できます。
@interfaceテスト: NSObject
- ( void ) test : ( id ) obj_;
@end
@実装テスト
- ( id ) init {
if ( ! ( self = [ super init ] ) ))
nilを 返します。
[ selfメソッド: @selector ( test :) withParameter : PMCLASS ( NSNull ) useBlock :^ ( id obj_ ) {
NSLog ( @ "Null:%@の実装" 、 [ obj_ class ] ) ;
return ( id ) nil ;
} ] ;
自己を返す ;
}
- ( void )テスト: ( id ) obj_ {
NSLog ( @ "Object:%@のデフォルト実装" 、 [ obj_ class ] ) ;
}
@end
私達は試みます:
Test * test_ = [ [ Test new ] autorealese ] ;
[ test_ test : @ "String" ] ;
[ test_test : [ NSNull null ] ] ;
default impl for Object: NSCFString
implementation for Null: NSNull
他に何ができますか? 少なくとも複数の引数を持つメソッドを使用すると、記事が大幅に増えます。
その結果、Objective-Cでイメージマッチングとパラメーター化されたメソッドの実装を取得しました(ただし、不気味な構文を使用しています)。
明らかなマイナス-オブジェクトのみがパラメーターおよび「画像」として使用できます。
この記事が少なくとも誰かにとって有益であり、読んだ後、Objective-Cランタイムの魔法についてもっと学びたいという願望があったことを願っています。 もしそうなら、その夜の魅力的な読書問題:
Objective-Cランタイムプログラミングガイド
Objective-Cランタイムリファレンス