XSpect, a lightweight library to make your code reusable and maintainable.
Upcoming SlideShare
Loading in...5
×
 

XSpect, a lightweight library to make your code reusable and maintainable.

on

  • 1,054 views

 

Statistics

Views

Total Views
1,054
Views on SlideShare
1,054
Embed Views
0

Actions

Likes
11
Downloads
21
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

XSpect, a lightweight library to make your code reusable and maintainable. XSpect, a lightweight library to make your code reusable and maintainable. Presentation Transcript

  • XSpect Makes code reusable and maintainable 2013/10/17 李岡諭 Xaree Lee (leondemon) xareelee@gmail.com
  • Previously on Cocoaheads@Taipei Zonble's talk (about AOP) http://goo.gl/4Rlbb2
  • 羅馬不是一天造成的 Unmaintainable 的 code 也是
  • Life should be simple, but... 1| 2| 3| - (void)appendData:(NSData*)inData{ [_data appendData:inData]; }
  • Welcome to real life 1| - (void)appendData:(NSData*)inData{ 2| NSParameterAssert(inData != nil); 3| NSParameterAssert([inData length]); 4| if (!inData) { 5| NSLog(@"inData is nil!"); for guarding and checking 6| return; 7| } 8| if (![inData length]) { 9| return; 10| } 11| NSInteger length = [_data length]; 12| [_lock lock]; for thread safe 13| [_data appendData:inData]; main task 14| [_lock unlock]; 15| NSParameterAssert(length != [_data length]); 16| #if DEBUG for monitoring and analyzing 17| [TestFlight passCheckpoint:@"APPEND_DATA"]; 18| #else 19| [Flurry logEvent:@"APPEND_DATA"]; 20| id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; 21| [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"data" withAction:@"append" withLabel:@"user_data" withValue:nil] build]]; 22| #endif 23| }
  • You add lots of code for •確定傳入參數 •確定有插入資料 •加 lock •加 Debug Log •加 TestFlight Log、Flurry Log... ...and everywhere!
  • XSpect Xaree's Spect library spect: look, see http://www.prefixsuffix.com
  • XSpect 包含了 2 個獨立的套件 • XAspect: 以 Aspect-Oriented 的方式,讓 程式碼 reusable 及 maintainable。 • XIntrospect: 把小程式碼包裝成更容易重複 使用的 block (使用 Block-in-Block)。
  • advice Traditional Obj-C message Using XSpect Obj-C message advice method advice subtask subtask subtask subtask Main Task subtask subtask XAspect XIntrospec subtask Main Task subtask subtask subtask Obj-C message advice advice method advice advice Obj-C message
  • Live Demo
  • advice subtask Main Task subtask advice Obj-C message • 程式是由許多 tasks 組成 Main task: 為程式的主架構 Subtasks 應該要很容易的加入或移除 • 我把 subtask 分為兩類: introspective subtasks aspect subtasks (advices) advice subtask Main Task subtask advice Introspective tasks: Aspect advices: From introspection (inner-spect) From aspects (outer-spect) Inside a method Outside a method Depending on the main task Independent jobs (usually) reusable and maintainable introspective blocks reusable and maintainable aspect categories
  • XAspect Using Method Swizzling (Aspect-Oriented Programming) 利用 Obj-C runtime 的特性, 截 並重新導向 message,以獲得執 行額外程式碼的機會。
  • Procedure-Oriented Procedures (C function calls) Event A Event B Event C Func Func Func Func Func Func Func Func Func Func Func Func Func Func Func
  • Object-Oriented Object-Oriented Class Class Class Class IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . Procedures (Obj-C messages) Event A Event B Event C IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP IMP Obj-C message
  • Aspect-Oriented Object-Oriented Class Class Class Class IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . IMP1 IMP2 IMP3 . Procedures (Obj-C messages) Event A Event B Event C IMP IMP IMP IMP Cross-Cutting concerns Aspect-Oriented Aspect B1 B2 B3 . . A1 IMP logging IMP A2 IMP IMP A3 IMP security IMP Aspect A1 A2 A3 . . Obj-C message IMP B1 IMP IMP B2 IMP IMP B3
  • Method Swizzling Before Swizzling After Swizzling Class Class SEL A SEL B SEL A SEL B IMP A IMP B IMP A IMP B
  • XAspect Create a recursive invocation before Method Swizzling Before Swizzling After Swizzling 2 Class SEL A Class SEL B SEL A SEL B IMP A IMP B 1 IMP A IMP B
  • Live Demo // In –viewDidLoad or –application:didFinishLaunchingWithOptions: User *user = [User new]; NSLog(@"The user is: %@", [user userName]); @implementation User - (NSString*)userName{ NSString *userName = @"Xaree Lee"; NSLog(@"I'm %@", userName); return userName; } @end 2013-10-14 23:04:16.454 XSpect[9199:a0b] I'm Xaree Lee 2013-10-14 23:04:16.559 XSpect[9199:a0b] The user is: Xaree Lee
  • Adding Aspect // In User+Greeting.m, the Greeting category of User @implementation User (Greeting) + (void)load{ SwapInstanceMethod([self class], @selector(userName), @selector(Greeting_userName)); } - (NSString *) Greeting_userName{ // Add before advice here NSLog(@"==> Hello, what's your name?"); // Invoke recursively NSString *userName = [self Greeting_userName]; // After advice NSLog(@"==> Greeting, %@", userName); return userName; } @end 2013-10-14 2013-10-14 2013-10-14 2013-10-14 23:04:16.451 23:04:16.454 23:04:16.558 23:04:16.559 XSpect[9199:a0b] XSpect[9199:a0b] XSpect[9199:a0b] XSpect[9199:a0b] ==> I'm ==> The Hello, what's your name? Xaree Lee Greeting, Xaree Lee user is: Xaree Lee
  • @implementation User (Greeting) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SwapInstanceMethod([self class], @selector(userName), @selector(Greeting_userName)); }); } - (NSString *) Greeting_userName{ // Add before advice here NSLog(@"==> Hello, what's your name?"); // Invoke recursively NSString *userName = [self Greeting_userName]; // After advice NSLog(@"==> Greeting, %@", userName); return userName; } @end #undef AspectName #define AspectName Greeting XAs p e c t E x te n s i o n s t y le AspectClass(User) WeaveAspectInstanceMethods(@selector(userName)); AspectImplementation - (NSString *) Aspect(userName){ // Add before advice here NSLog(@"==> Hello, what's your name?"); // Invoke recursively NSString *userName = [self Aspect(userName)]; // After advice NSLog(@"==> Greeting, %@", userName); return userName; } EndAspect
  • Class More Aspects SEL A SEL B SEL C IMP A IMP B IMP C IMP B SEL A SEL B SEL C SEL D C IMP A Add 2nd Aspect SEL B IMP B IMP C IMP D C Class Add 3rd Aspect Class results SEL A SEL B SEL C SEL D C @"A" IMP B @"B1" @SEL B @"B2" IMP C @"C1" @SEL C @"C2" IMP C @"D1" @SEL D @"D2" > > > > > > > D1 C1 B1 A B2 C2 D2
  • Advantages • Keep OCP principle (open for extension; closed for modification) • Encapsulate changes (write all changes in a aspect file) • Reusable and maintainable (you can find all the code in one place)
  • Disadvantages • Hard to understand (if you aren't familiar with AOP) • Hard to debug (I might add some code to deal with it) • Unpredictable loading sequence (you should use XAspect for the independent purpose)
  • XIntrospect Using Block-in-Block technique 把小片段的程式碼包裝成可重複使 用的 Block,然後 runtime 的時候 再把它們組合起來,並執行。
  • Why Call it "Introspect" 並非所有 subtasks 都 是來自獨立的外部觀點
  • Low Coupling vs High Coupling subtasks to the main task 1| - (void)appendData:(NSData*)inData{ 2| NSParameterAssert(inData != nil); 3| NSParameterAssert([inData length]); 4| if (!inData) { 5| NSLog(@"inData is nil!"); for guarding and checking 6| return; 7| } 8| if (![inData length]) { 9| return; 10| } 11| NSInteger length = [_data length]; 12| [_lock lock]; for thread safe 13| [_data appendData:inData]; main task 14| [_lock unlock]; 15| NSParameterAssert(length != [_data length]); 16| #if DEBUG for monitoring and analyzing 17| [TestFlight passCheckpoint:@"APPEND_DATA"]; 18| #else 19| [Flurry logEvent:@"APPEND_DATA"]; 20| id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; 21| [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"data" withAction:@"append" withLabel:@"user_data" withValue:nil] build]]; 22| #endif 23| } high ? low
  • XIntrospect 的要點在於 在 main task 的前後,總是有一 些瑣碎的程式碼是必要的。它們是 完整的程式碼中的一部分。
  • Blo ck就像 in是俄 Blo 羅斯 ck 娃娃
  • Core Block Types typedef void (^Matryoshka)(); typedef Matryoshka (^IntrospectBlock)(Matryoshka innerBlock); • Matryoshka:它包含了小片段的程式碼。並可能會在內 部包裝另一個 Matryoshka。在被執行之後,會從最外 層開始一層一層執行程式碼。 (Matryoshka 必須在 IntrospectBlock 內產生) • IntrospectBlock:實際產生及包裝 Matryoshka 的 Block。它會決定此層的 Matryoshka 的程式碼,並呼叫 更內一層的 Matryoshka。 (IntrospectBlock 是實際上決定程式碼的地方)
  • Define IntrospectBlock /** XIntrospectCore Definition **/ typedef void (^Matryoshka)(); typedef Matryoshka (^IntrospectBlock)(Matryoshka innerBlock); Matryoshka assembleMatryoshka(IntrospectBlock introspection, ... ); /** Declare an IntrospectBlock **/ IntrospectionBlock introspect = ^ Matryoshka(Matryoshka innerBlock){ return ^(){ NSLog(@"before advice"); innerBlock(); NSLog(@"after advice"); }; }; /** Declare another IntrospectBlock **/ IntrospectBlock mainTask = ^ Matryoshka(Matryoshka innerBlock){ return ^(){ NSLog(@"Here's the main task"); }; };
  • /** XIntrospectCore Definition **/ typedef void (^Matryoshka)(); typedef Matryoshka (^IntrospectBlock)(Matryoshka innerBlock); Matryoshka assembleMatryoshka(IntrospectBlock introspection, ... ); /** Assemble and invoke the whole matryoshka **/ NSLog(@"Start to assemble a matryoshka"); Matryoshka matryoshka = assembleMatryoshka(introspect, introspect, introspect, mainTask, nil); NSLog(@"Prepare to invoke matryoshka"); matryoshka(); NSLog(@"Did invoke matryoshka"); 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 2013-10-15 23:17:09.444 23:17:09.451 23:17:09.452 23:17:09.452 23:17:09.453 23:17:09.453 23:17:09.454 23:17:09.454 23:17:09.455 23:17:09.456 XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] XSpect[10654:a0b] Start to assemble a matryoshka Prepare to invoke matryoshka before advice before advice before advice Here's the main task after advice after advice after advice Did invoke matryoshka
  • Advantages • Keep SRP principle (single responsibility principle) • Intuitive coding style (using the extension macros) • Reusable and readable (encapsulate all code in a block)
  • Disadvantages • Hard to understand (if you aren't familiar with Block-in-Block) • Hard to debug (I might add some code to deal with it) • Unpredictable loading sequence (you should use XAspect for the independent purpose)
  • Using XSpect library to Keep those code cleaner 1| - (void)appendData:(NSData*)inData{ 2| NSParameterAssert(inData != nil); 3| NSParameterAssert([inData length]); 4| if (!inData) { Using XIntrospect 5| NSLog(@"inData is nil!"); for guarding and checking 6| return; 7| } 8| if (![inData length]) { 9| return; Using XIntrospect 10| } Or 11| NSInteger length = [_data length]; Using XAspect 12| [_lock lock]; for thread safe 13| [_data appendData:inData]; main task 14| [_lock unlock]; 15| NSParameterAssert(length != [_data length]); Using XAspect 16| #if DEBUG for monitoring and analyzing 17| [TestFlight passCheckpoint:@"APPEND_DATA"]; 18| #else 19| [Flurry logEvent:@"APPEND_DATA"]; 20| id<GAITracker> tracker = [[GAI sharedInstance] defaultTracker]; 21| [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"data" withAction:@"append" withLabel:@"user_data" withValue:nil] build]]; 22| #endif 23| }
  • XSpect Github: xareelee/XSpect CocoaPods: preparing
  • Thanks