Slide 1

Slide 1 text

BloodMagic Custom property attributes CocoaHeads, 2014

Slide 2

Slide 2 text

AlexDenisov 1101_debian WHOAMI Twitter: Github: IRC: AlexDenisov

Slide 3

Slide 3 text

Outline • What BloodMagic is • How to use it • How to extend it • How does it work • Q & A

Slide 4

Slide 4 text

What BloodMagic is

Slide 5

Slide 5 text

Problem @interface ViewController : UIViewController @property (nonatomic) NSMutableArray *resources; @property (nonatomic) ProgressView *progressView; @property (nonatomic) ErrorView *errorView; @property (nonatomic) DataLoader *dataLoader; @end

Slide 6

Slide 6 text

@implementation ViewController - (NSMutableArray *)resources { if (!_resources) { _resources = [NSMutableArray new]; } return _resources; } - (ProgressView *)progressView { if (!_progressView) { _progressView = [ProgressView new]; } return _progressView; } - (ErrorView *)errorView { if (!_errorView) { _errorView = [ErrorView new]; } return _errorView; } - (DataLoader *)dataLoader { if (!_dataLoader) { _dataLoader = [DataLoader new]; } return _dataLoader; } @end Lazy Initialization

Slide 7

Slide 7 text

- (PropertyClass *)propertyName { if (!_propertyName) { _propertyName = [PropertyClass new]; } return _propertyName; } A lot of Boilerplate Code

Slide 8

Slide 8 text

Just add ‘lazy’ attribute @interface ViewController : UIViewController @property (nonatomic, lazy) NSMutableArray *resources; @property (nonatomic, lazy) CenterProgress *centerProgress; @property (nonatomic, lazy) BottomProgress *bottomProgress; @property (nonatomic, lazy) DataLoader *dataLoader; @end

Slide 9

Slide 9 text

Just add ‘lazy’ attribute @interface ViewController : UIViewController @property (nonatomic, lazy) NSMutableArray *resources; @property (nonatomic, lazy) CenterProgress *centerProgress; @property (nonatomic, lazy) BottomProgress *bottomProgress; @property (nonatomic, lazy) DataLoader *dataLoader; @end /tmp/lazy_test ➜ make … error: unknown property attribute 'lazy' @property (nonatomic, lazy) NSMutableArray *resources; ^ …

Slide 10

Slide 10 text

BloodMagic a mechanism for creating custom property attributes based on your code

Slide 11

Slide 11 text

Let’s add magic #import @interface ViewController : UIViewController @property (nonatomic) NSMutableArray *resources; @property (nonatomic) ProgressView *progressView; @property (nonatomic) ErrorView *errorView; @property (nonatomic) DataLoader *dataLoader; @end

Slide 12

Slide 12 text

Let’s add magic @implementation ViewController @dynamic resources; @dynamic progressView; @dynamic errorView; @dynamic dataLoader; - (void)actOnSomething { [self.dataLoader loadNextPage]; // ^ new object created } @end

Slide 13

Slide 13 text

Magic Happened @implementation ViewController @dynamic resources; @dynamic progressView; @dynamic errorView; @dynamic dataLoader; @end BloodMagic @implementation ViewController - (NSMutableArray *)resources { if (!_resources) { _resources = [NSMutableArray new]; } return _resources; } - (ProgressView *)progressView { if (!_progressView) { _progressView = [ProgressView new]; } return _progressView; } - (ErrorView *)errorView { if (!_errorView) { _errorView = [ErrorView new]; } return _errorView; } - (DataLoader *)dataLoader { if (!_dataLoader) { _dataLoader = [DataLoader new]; } return _dataLoader; } @end

Slide 14

Slide 14 text

How to use BloodMagic

Slide 15

Slide 15 text

Single Custom Attribute #import @interface ViewController : UIViewController @property (nonatomic, bm_lazy) DataLoader *dataLoader; @end

Slide 16

Slide 16 text

Single Custom Attribute #import "ViewController.h" @implementation ViewController @dynamic dataLoader; - (void)viewDidLoad { [super viewDidLoad]; [self.dataLoader loadNextPage]; } @end

Slide 17

Slide 17 text

Single Custom Attribute #import "ViewController.h" @implementation ViewController @dynamic dataLoader; - (void)viewDidLoad { [super viewDidLoad]; [self.dataLoader loadNextPage]; // ^ new object created } @end

Slide 18

Slide 18 text

Multiple Custom Attributes #import #import @interface ViewController : UIViewController @property (nonatomic, bm_lazy) DataLoader *dataLoader; @property (nonatomic, bm_partial) ErrorView *errorView; @end

Slide 19

Slide 19 text

Multiple Custom Attributes #import "ViewController.h" @implementation ViewController @lazy(dataLoader) @partial(errorView) - (void)viewDidLoad { [super viewDidLoaded]; [self.dataLoader loadNextPage]; [self.view addSubview:self.errorView]; } @end

Slide 20

Slide 20 text

How to extend it

Slide 21

Slide 21 text

@property (nonatomic, bm_preference) NSString *cocoaHeads;

Slide 22

Slide 22 text

Module structure BloodMagic/Sources/Modules/Preference (master ✔) ➜ tree |-- Private | |-- BMPreferenceHook.h | |-- BMPreferenceHook.m | |-- BMPreferenceModuleLoader.h | `-- BMPreferenceModuleLoader.m `-- Public `-- BMPreference.h … |-- BloodMagic/Sources `-- Preference.h

Slide 23

Slide 23 text

Public Protocol @protocol BMPreference @end

Slide 24

Slide 24 text

Public Header #import #import #ifndef bm_preference #define bm_preference #endif #ifndef preference #define preference(property_name) register_module(BMPreference, property_name) #endif

Slide 25

Slide 25 text

Public Header #import #import #ifndef bm_preference #define bm_preference #endif #ifndef preference #define preference(property_name) register_module(BMPreference, property_name) #endif

Slide 26

Slide 26 text

Private Module Loader #import @interface BMPreferenceModuleLoader : NSObject @end

Slide 27

Slide 27 text

Private Module Loader #import "BMPreferenceModuleLoader.h" #import "BMPreference.h" #import "BMBloodMagicInjector.h" @implementation BMPreferenceModuleLoader + (void)load { @autoreleasepool { BMBloodMagicInjector *injector = [BMBloodMagicInjector new]; [injector injectBloodMagicInto:@protocol(BMPreference)]; } } @end

Slide 28

Slide 28 text

Private Hook #import "BMHook.h" #import "BMPreference.h" @interface BMPreferenceHook : NSObject @end

Slide 29

Slide 29 text

Private Hook #import "BMPreferenceHook.h" #import "BMProperty.h" @implementation BMPreferenceHook static inline NSUserDefaults *bm_defaults() { return [NSUserDefaults standardUserDefaults]; } + (void)accessorHook:(id *)value withProperty:(const BMProperty *)property sender:(__unused id)sender { *value = [bm_defaults() objectForKey:property.name]; } + (void)mutatorHook:(id *)value withProperty:(const BMProperty *)property sender:(__unused id)sender { [bm_defaults() setObject:*value forKey:property.name]; } @end

Slide 30

Slide 30 text

Private Hook Accessor static inline NSUserDefaults *bm_defaults() { return [NSUserDefaults standardUserDefaults]; } + (void)accessorHook:(id *)value withProperty:(const BMProperty *)property sender:(__unused id)sender { *value = [bm_defaults() objectForKey:property.name]; }

Slide 31

Slide 31 text

Private Hook Mutator static inline NSUserDefaults *bm_defaults() { return [NSUserDefaults standardUserDefaults]; } + (void)mutatorHook:(id *)value withProperty:(const BMProperty *)property sender:(__unused id)sender { [bm_defaults() setObject:*value forKey:property.name]; }

Slide 32

Slide 32 text

BMPreference Usage #import @interface Settings : NSObject @property (nonatomic, bm_preference) NSString *name; @property (nonatomic, bm_preference) NSUInteger age; @end

Slide 33

Slide 33 text

BMPreference Usage #import "Settings.h" @implementation Settings @dynamic name; @dynamic age; @end

Slide 34

Slide 34 text

BMPreference Usage Settings *settings = [Settings new]; settings.name = @"AlexDenisov"; settings.age = 26; // … NSLog(@"%@", defaults);

Slide 35

Slide 35 text

BMPreference Usage Settings *settings = [Settings new]; settings.name = @"AlexDenisov"; settings.age = 26; // … NSLog(@"%@", defaults); { … age = 26; name = AlexDenisov; … }

Slide 36

Slide 36 text

How does it work

Slide 37

Slide 37 text

BloodMagic is modular ~/Projects/BloodMagic/Sources (master ✔) ➜ tree -L 2 . `-- Modules |-- Core |-- Final |-- Injectable |-- Initializers |-- Lazy |-- Partial `-- Preference

Slide 38

Slide 38 text

BloodMagic is modular ~/Projects/BloodMagic/Sources (master ✔) ➜ tree -L 2 . `-- Modules |-- Core |-- Final |-- Injectable |-- Initializers |-- Lazy |-- Partial `-- Preference Property attributes implementation

Slide 39

Slide 39 text

BloodMagic is modular ~/Projects/BloodMagic/Sources (master ✔) ➜ tree -L 2 . `-- Modules |-- Core |-- Final |-- Injectable |-- Initializers |-- Lazy |-- Partial `-- Preference Low level logic • hooks • injections • runtime routines

Slide 40

Slide 40 text

Core Module: Hooks @protocol BMHook @optional + (void)mutatorHook:(id *)value withProperty:(const BMProperty *)property sender:(id)sender; + (void)accessorHook:(id *)value withProperty:(const BMProperty *)property sender:(id)sender; @end

Slide 41

Slide 41 text

Core Module: Injections @interface BMBloodMagicInjector : NSObject - (void)injectBloodMagicInto:(Protocol *)protocol; @end

Slide 42

Slide 42 text

Core Module: Injections @implementation BMBloodMagicInjector // pseudocode - (void)injectBloodMagicInto:(Protocol *)protocol { class_list_t classes = collectClasses(protocol); property_list_t properties = collectDynamicProperties(classes); for (Property *property in properties) { Hook *hook = hookForProperty(property); property.accessor = hook.accessor; property.mutator = hook.mutator; } } @end

Slide 43

Slide 43 text

Sources: https://github.com/railsware/BloodMagic Slides: https://speakerdeck.com/alexdenisov/bloodmagic http://l.rw.rw/dibm Blog-post: https://speakerdeck.com/0xc010d/dependency-injection-ftw

Slide 44

Slide 44 text

Questions?