Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
BloodMagic
Search
AlexDenisov
April 11, 2014
Programming
1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
BloodMagic
CocoaHeads, 2014
https://github.com/railsware/BloodMagic
AlexDenisov
April 11, 2014
More Decks by AlexDenisov
See All by AlexDenisov
Getting started with LLVM using Swift
alexdenisov
0
170
Magic Behind Xcode
alexdenisov
2
510
Compilation Process
alexdenisov
5
800
Other Decks in Programming
See All in Programming
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
210
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
170
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
1.1k
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
130
吝嗇家のためのAI活用 / AI development for miser - ChatGPT + Issue Driven Development
tooppoo
0
190
【やさしく解説 設計編・中級 #1】一つの車に、運転手は一人 ~ある倉庫システムの事例から~
panda728
PRO
0
180
継続モナドとリアクティブプログラミング
yukikurage
3
600
ローカルLLMでどこまでコードが書けるか -縮小版 / How much code can be written on a local LLM Shortened
kishida
2
190
Embedded SREと共に達成した会員管理システムのAWS移行 - SRE NEXT 2026 ランチスポンサーセッション
niftycorp
PRO
1
2.6k
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
210
分散システム、なんですぐ死んでしまうん?耐障害性を高めたいあなたのためのレジリエンスパターン入門
mshibuya
7
6.2k
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
580
Featured
See All Featured
エンジニアに許された特別な時間の終わり
watany
108
250k
The Cost Of JavaScript in 2023
addyosmani
55
10k
Mobile First: as difficult as doing things right
swwweet
225
10k
What does AI have to do with Human Rights?
axbom
PRO
1
2.2k
The Cult of Friendly URLs
andyhume
79
6.9k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Tell your own story through comics
letsgokoyo
1
990
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Leo the Paperboy
mayatellez
8
1.9k
Optimizing for Happiness
mojombo
378
71k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
260
Transcript
BloodMagic Custom property attributes CocoaHeads, 2014
AlexDenisov 1101_debian WHOAMI Twitter: Github: IRC: AlexDenisov
Outline • What BloodMagic is • How to use it
• How to extend it • How does it work • Q & A
What BloodMagic is
Problem @interface ViewController : UIViewController @property (nonatomic) NSMutableArray *resources; @property
(nonatomic) ProgressView *progressView; @property (nonatomic) ErrorView *errorView; @property (nonatomic) DataLoader *dataLoader; @end
@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
- (PropertyClass *)propertyName { if (!_propertyName) { _propertyName = [PropertyClass
new]; } return _propertyName; } A lot of Boilerplate Code
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
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; ^ …
BloodMagic a mechanism for creating custom property attributes based on
your code
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
Let’s add magic @implementation ViewController @dynamic resources; @dynamic progressView; @dynamic
errorView; @dynamic dataLoader; - (void)actOnSomething { [self.dataLoader loadNextPage]; // ^ new object created } @end
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
How to use BloodMagic
Single Custom Attribute #import <BloodMagic/Lazy.h> @interface ViewController : UIViewController <BMLazy>
@property (nonatomic, bm_lazy) DataLoader *dataLoader; @end
Single Custom Attribute #import "ViewController.h" @implementation ViewController @dynamic dataLoader; -
(void)viewDidLoad { [super viewDidLoad]; [self.dataLoader loadNextPage]; } @end
Single Custom Attribute #import "ViewController.h" @implementation ViewController @dynamic dataLoader; -
(void)viewDidLoad { [super viewDidLoad]; [self.dataLoader loadNextPage]; // ^ new object created } @end
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
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
How to extend it
@property (nonatomic, bm_preference) NSString *cocoaHeads;
Module structure BloodMagic/Sources/Modules/Preference (master ✔) ➜ tree |-- Private |
|-- BMPreferenceHook.h | |-- BMPreferenceHook.m | |-- BMPreferenceModuleLoader.h | `-- BMPreferenceModuleLoader.m `-- Public `-- BMPreference.h … |-- BloodMagic/Sources `-- Preference.h
Public Protocol @protocol BMPreference <NSObject> @end
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
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
Private Module Loader #import <Foundation/Foundation.h> @interface BMPreferenceModuleLoader : NSObject @end
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
Private Hook #import "BMHook.h" #import "BMPreference.h" @interface BMPreferenceHook : NSObject
<BMHook, BMPreference> @end
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
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]; }
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]; }
BMPreference Usage #import <BloodMagic/Preference.h> @interface Settings : NSObject <BMPreference> @property
(nonatomic, bm_preference) NSString *name; @property (nonatomic, bm_preference) NSUInteger age; @end
BMPreference Usage #import "Settings.h" @implementation Settings @dynamic name; @dynamic age;
@end
BMPreference Usage Settings *settings = [Settings new]; settings.name = @"AlexDenisov";
settings.age = 26; // … NSLog(@"%@", defaults);
BMPreference Usage Settings *settings = [Settings new]; settings.name = @"AlexDenisov";
settings.age = 26; // … NSLog(@"%@", defaults); { … age = 26; name = AlexDenisov; … }
How does it work
BloodMagic is modular ~/Projects/BloodMagic/Sources (master ✔) ➜ tree -L 2
. `-- Modules |-- Core |-- Final |-- Injectable |-- Initializers |-- Lazy |-- Partial `-- Preference
BloodMagic is modular ~/Projects/BloodMagic/Sources (master ✔) ➜ tree -L 2
. `-- Modules |-- Core |-- Final |-- Injectable |-- Initializers |-- Lazy |-- Partial `-- Preference Property attributes implementation
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
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
Core Module: Injections @interface BMBloodMagicInjector : NSObject - (void)injectBloodMagicInto:(Protocol *)protocol;
@end
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
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
Questions?