Objective-Cにパターンマッチングとパラメーター化されたメソッドを追加する

「お気に入りの命令型プログラミング言語で機能的な骨をパンに追加する」というトピックに関する記事が増えています。 Javaの最近の例を次に示します 。



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ランタイムリファレンス



All Articles