Clang Attributes黒魔術小記

5377 ワード

孫源sunnyxxの技術ブログシリーズhttp://blog.sunnyxx.com/2016/05/14/clang-attributes/
2016年5月14日
Clang AttributesはClangで提供されたソースの注釈であり、開発者がコンパイラにある要求を表現するのに便利であり、Static Analyzer、Name Mangling、Code Generationなどの制御に参加し、一般的に__attribute__(xxx)の形でコードに現れる。使用を容易にするために、いくつかの一般的な属性はまた、Cocoaによってマクロとして定義されています。例えば、システムヘッダファイルによく登場するNS_CLASS_AVAILABLE_IOS(9_0)は、__attribute__(availability(...))という属性の簡単な書き方です。
一般的な属性の紹介は、NSHisterの紹介文とtwitterの紹介文を見ることができます。いくつかの面白い「黒の魔法」を紹介します。いくつかの場面で思わぬ効果があるかもしれません。
以下のテストはXcode 7.3(Clang 3.8)を基準とします。
OBsubclassing_resticted
この属性を使用すると、Final Classという名前のクラスがあると仮定して、つまり引き継がれてはいけないクラスを定義することができます。
@interface Eunuch : NSObject
@end
@interface Child : Eunuch //         
@end
@interfaceの前にEunuch( )という属性をつければいいです。
__attribute__((objc_subclassing_restricted))
@interface Eunuch : NSObject
@end
@interface Child : Eunuch // 
OBrequires_スーパー
aka:objc_subclassing_restricted、フラグサブクラスはこの方法を継承する時にNS_REQUIRES_SUPERを呼び出す必要があります。そうでなければ、コンパイルの警告が与えられます。
@interface Father : NSObject
- (void)hailHydra __attribute__((objc_requires_super));
@end
@implementation Father
- (void)hailHydra {
 NSLog(@"hail hydra!");
}
@end
@interface Son : Father
@end
@implementation Son
- (void)hailHydra {
} // 
OBbox able
Objective-Cのsuperシンタックス飴は、基本データタイプボックスを@(...)オブジェクトにすることができ、もしbox 1つのNSNumberタイプまたはstructタイプがunionオブジェクトになるなら、この属性を使用することができます。
typedef struct __attribute__((objc_boxable)) {
 CGFloat x, y, width, height;
} XXRect;
これにより、NSValueは、boxによって能力を備えている:
CGRect rect1 = {1, 2, 3, 4};
NSValue *value1 = @(rect1); // 
constructor/destructor
名前の通り、コンストラクタとデストラクタ、この2つの属性の関数は、それぞれ実行可能ファイル(またはshared library)loadとunloadで呼び出され、XXRectの関数が呼び出される前とreturn後に実行されると理解できる。
__attribute__((constructor))
static void beforeMain(void) {
 NSLog(@"beforeMain");
}
__attribute__((destructor))
static void afterMain(void) {
 NSLog(@"afterMain");
}
int main(int argc, const char * argv[]) {
 NSLog(@"main");
 return 0;
}

// Console:
// "beforeMain" -> "main" -> "afterMain"
constructorとmain()は全部メーン関数の実行前に呼び出されましたが、+loadはconstructorよりもっと前になくしました。dyld(動的リンク器、プログラムの最初の起点)はイメージをロードする時に、+loadにすべての種類をロードするように通知します。全部のロードが完了したら、dyldはこのイメージの中のすべてのconstructor方法を呼び出すことができます。
だからconstructorは悪いことをする絶好のチャンスです。
  • すべてのクラスはすでにロード済みです。
  • main関数はまだ実行されていません。
  • は+ロードのような必要がなくても、一つのクラスに
  • をマウントしなければなりません。objc runtimeのFDStockViewPatch Entry方法は、このタイミングで奇日を実現するための手段である。
    PS:複数のconstructorがあって、優先度を制御したいなら、+loadと書いてもいいです。中の数字は小さいほど優先度が高く、1~100はシステムのために保留されます。
    enabale_if
    この属性はC関数だけで使用できます。パラメータの静的検査を行うために使用できます。
    static void printValidAge(int age)
    __attribute__((enable_if(age > 0 && age < 120, "     ?"))) {
     printf("%d", age);
    }
    
    この関数の呼び出しはFDStackViewを満たす必要があるという意味で許可されます。
    printValidAge(26); // √
    printValidAge(150); // 
    cleanup
    変数に宣言します。この変数のスコープが終了すると、指定された関数を呼び出します。Reactive Cocoaはこの特性で不思議な__attribute__((constructor(101)))を実現しました。これについてatributeは前の記事で紹介されています。
    overloadable
    C関数では、いくつかの関数名が同じであることが定義されますが、パラメータが異なる方法では、コンパイラを呼び出すと自動的にパラメータに従って関数のプロトタイプを選択します。
    __attribute__((overloadable)) void logAnything(id obj) {
     NSLog(@"%@", obj);
    }
    __attribute__((overloadable)) void logAnything(int number) {
     NSLog(@"%@", @(number));
    }
    __attribute__((overloadable)) void logAnything(CGRect rect) {
     NSLog(@"%@", NSStringFromCGRect(rect));
    }
    // Tests
    logAnything(@[@"1", @"2"]);
    logAnything(233);
    logAnything(CGRectMake(1, 2, 3, 4));
    
    OBruntime_nameage > 0 && age < 120または@onExitのためにクラスまたはプロトコルの名前をコンパイルする時に別のものに指定します。
    __attribute__((objc_runtime_name("SarkGay")))
    @interface Sark : NSObject
    @end
    
    NSLog(@"%@", NSStringFromClass([Sark class])); // "SarkGay"
    
    このクラスの名前を直接使うところは全部入れ替えられます。一番簡単で乱暴な使い方はクラス名の混淆をすることです。
    __attribute__((objc_runtime_name("40ea43d7629d01e4b8d6289a132482d0dd5df4fa")))
    @interface SecretClass : NSObject
    @end
    
    数字で始まることもできます。心配しないでください。もしスクリプトを書いたら、各クラスの前にランダムに生成された@interfaceを入れて、一番簡単なコードを混同して完成します。
    これは私が知っている唯一のobjcの運行時のクラス構造に影響があるatributeです。コードクラス名を通じて、コンパイル時にいくつかの情報を注入して、運行時に連れてこられた後、逆解凍します。これは秘密通路を開設して、コードを書く時と運送時に相当します。頭の穴を開けてみてください。このatributeをマクロとして定義したら、@protocolという形でいくつかの機能を完成します。
    // @singleton     __attribute__((objc_runtime_name(...)))
    //        "SINGLETON_Sark_sharedInstance"
    @singleton(Sark, sharedInstance)
    @interface Sark : NSObject
    + (instancetype)sharedInstance;
    @end
    
    運転時にobjc_runtime_nameで入口タイミングを取得し、このクラスをルンムで見つけ、「sharedInstance」というselector情報を逆解釈し、動的にannotation__attribute__((constructor))などの方法で置き換え、+ allocの単例に戻る。
    References
    http://llvm.org/releases/3.8.0/tools/clang/docs/AttributeReference.html http://clang-analyzer.llvm.org/annotations.html