Blood magic

44
BloodMagic Custom property attributes CocoaHeads, 2014

Transcript of Blood magic

Page 1: Blood magic

BloodMagicCustom property attributes

CocoaHeads, 2014

Page 2: Blood magic

AlexDenisov

1101_debian

WHOAMI

Twitter:

Github:IRC: AlexDenisov

Page 3: Blood magic

Outline

• What BloodMagic is

• How to use it

• How to extend it

• How does it work

• Q & A

Page 4: Blood magic

What BloodMagic is

Page 5: Blood magic

Problem

@interface ViewController : UIViewController

@property (nonatomic) NSMutableArray *resources;@property (nonatomic) ProgressView *progressView;@property (nonatomic) ErrorView *errorView;@property (nonatomic) DataLoader *dataLoader;

@end

Page 6: Blood magic

@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

Page 7: Blood magic

- (PropertyClass *)propertyName{ if (!_propertyName) { _propertyName = [PropertyClass new]; }

return _propertyName;}

A lot of Boilerplate Code

Page 8: Blood magic

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

Page 9: Blood magic

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; ^…

Page 10: Blood magic

BloodMagic

a mechanism for creating custom property attributes

based on your code

Page 11: Blood magic

Let’s add magic#import <BloodMagic/Lazy.h>

@interface ViewController : UIViewController<BMLazy>

@property (nonatomic) NSMutableArray *resources;@property (nonatomic) ProgressView *progressView;@property (nonatomic) ErrorView *errorView;@property (nonatomic) DataLoader *dataLoader;

@end

Page 12: Blood magic

Let’s add magic@implementation ViewController

@dynamic resources;@dynamic progressView;@dynamic errorView;@dynamic dataLoader;

- (void)actOnSomething{ [self.dataLoader loadNextPage]; // ^ new object created}

@end

Page 13: Blood magic

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

Page 14: Blood magic

How to use BloodMagic

Page 15: Blood magic

Single Custom Attribute

#import <BloodMagic/Lazy.h>

@interface ViewController : UIViewController<BMLazy>

@property (nonatomic, bm_lazy) DataLoader *dataLoader;

@end

Page 16: Blood magic

Single Custom Attribute#import "ViewController.h"

@implementation ViewController

@dynamic dataLoader;

- (void)viewDidLoad{ [super viewDidLoad]; [self.dataLoader loadNextPage];

}

@end

Page 17: Blood magic

Single Custom Attribute#import "ViewController.h"

@implementation ViewController

@dynamic dataLoader;

- (void)viewDidLoad{ [super viewDidLoad]; [self.dataLoader loadNextPage]; // ^ new object created}

@end

Page 18: Blood magic

Multiple Custom Attributes

#import <BloodMagic/Lazy.h>#import <BloodMagic/Partial.h>

@interface ViewController : UIViewController<BMLazy,BMPartial>

@property (nonatomic, bm_lazy) DataLoader *dataLoader;@property (nonatomic, bm_partial) ErrorView *errorView;

@end

Page 19: Blood magic

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

Page 20: Blood magic

How to extend it

Page 21: Blood magic

@property (nonatomic, bm_preference) NSString *cocoaHeads;

Page 22: Blood magic

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

Page 23: Blood magic

Public Protocol

@protocol BMPreference<NSObject>

@end

Page 24: Blood magic

Public Header

#import <BloodMagic/Sources/Modules/Core/Public/BMPublicCoreDefnitions.h>#import <BloodMagic/Sources/Modules/Preference/Public/BMPreference.h>

#ifndef bm_preference#define bm_preference#endif

#ifndef preference#define preference(property_name) register_module(BMPreference, property_name)#endif

Page 25: Blood magic

Public Header

#import <BloodMagic/Sources/Modules/Core/Public/BMPublicCoreDefnitions.h>#import <BloodMagic/Sources/Modules/Preference/Public/BMPreference.h>

#ifndef bm_preference#define bm_preference#endif

#ifndef preference#define preference(property_name) register_module(BMPreference, property_name)#endif

Page 26: Blood magic

Private Module Loader

#import <Foundation/Foundation.h>

@interface BMPreferenceModuleLoader : NSObject

@end

Page 27: Blood magic

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

Page 28: Blood magic

Private Hook

#import "BMHook.h"#import "BMPreference.h"

@interface BMPreferenceHook : NSObject<BMHook, BMPreference>

@end

Page 29: Blood magic

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

Page 30: Blood magic

Private HookAccessor

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];}

Page 31: Blood magic

Private HookMutator

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];}

Page 32: Blood magic

BMPreference Usage

#import <BloodMagic/Preference.h>

@interface Settings : NSObject <BMPreference>

@property (nonatomic, bm_preference) NSString *name;@property (nonatomic, bm_preference) NSUInteger age;

@end

Page 33: Blood magic

BMPreference Usage

#import "Settings.h"

@implementation Settings

@dynamic name;@dynamic age;

@end

Page 34: Blood magic

BMPreference Usage

Settings *settings = [Settings new]; settings.name = @"AlexDenisov"; settings.age = 26;

// …NSLog(@"%@", defaults);

Page 35: Blood magic

BMPreference Usage

Settings *settings = [Settings new]; settings.name = @"AlexDenisov"; settings.age = 26;

// …NSLog(@"%@", defaults);{…

age = 26; name = AlexDenisov;

…}

Page 36: Blood magic

How does it work

Page 37: Blood magic

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

Page 38: Blood magic

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

Property attributes implementation

Page 39: Blood magic

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

Page 40: Blood magic

Core Module: Hooks@protocol BMHook

<NSObject>

@optional

+ (void)mutatorHook:(id *)value withProperty:(const BMProperty *)property

sender:(id)sender;

+ (void)accessorHook:(id *)value withProperty:(const BMProperty *)property

sender:(id)sender;

@end

Page 41: Blood magic

Core Module: Injections

@interface BMBloodMagicInjector : NSObject

- (void)injectBloodMagicInto:(Protocol *)protocol;

@end

Page 42: Blood magic

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

Page 43: Blood magic

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

Page 44: Blood magic

Questions?