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