https://github.com/railsware/bloodmagic
BloodMagic is a framework, which gives you a way to create custom property attributes.
https://github.com/railsware/bloodmagic
Last synced: about 1 year ago
JSON representation
BloodMagic is a framework, which gives you a way to create custom property attributes.
- Host: GitHub
- URL: https://github.com/railsware/bloodmagic
- Owner: railsware
- License: mit
- Created: 2013-10-13T16:42:22.000Z (over 12 years ago)
- Default Branch: master
- Last Pushed: 2020-02-16T21:47:14.000Z (over 6 years ago)
- Last Synced: 2025-03-29T22:06:42.850Z (about 1 year ago)
- Language: Objective-C
- Homepage:
- Size: 3.47 MB
- Stars: 316
- Watchers: 30
- Forks: 36
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
## BloodMagic

  
Objective-C is a powerful language, but sometimes it lacks of custom property attributes, like these:
```objectivec
@property (nonatomic, strong, lazy) ProgressViewService *progressView;
@property (nonatomic, strong, partial) HeaderView *headerView;
@property (nonatomic, strong, final) NSString *almostImmutable;
@property (nonatomic, strong, preference) NSString *authToken;
@property (nonatomic, strong, injectable) id client;
@property (nonatomic, strong, anything_you_want) AwesomeView *someAwesomeView;
```
We can't implement these attributes without hacking on `clang`, but fortunately, we're able to achieve these effects by means of BloodMagic' spells
[FAQ](https://github.com/railsware/BloodMagic/blob/master/FAQ.md)
[Blog-post](http://l.rw.rw/dibm)
[Presentation](https://speakerdeck.com/alexdenisov/bloodmagic) by [AlexDenisov](https://github.com/AlexDenisov)
[Presentation](https://speakerdeck.com/0xc010d/dependency-injection-ftw) by [Ievgen Solodovnykov](https://github.com/0xc010d)
### Embark on the Dark
#### [CocoaPods](http://cocoapods.org/):
```ruby
pod 'BloodMagic', :git => 'https://github.com/railsware/BloodMagic.git'
```
#### [Components](https://github.com/AlexDenisov/Components)
```bash
$ mkdir -p ./Components.make
# iOS
wget https://raw.githubusercontent.com/AlexDenisov/Components/master/Components.make/BloodMagic/1.0.0/BloodMagic-iOS.make -O ./Components.make/BloodMagic-iOS.make
# OSX
wget https://raw.githubusercontent.com/AlexDenisov/Components/master/Components.make/BloodMagic/1.0.0/BloodMagic-OSX.make -O ./Components.make/BloodMagic-OSX.make
```
#### Manually
Alternatively you can use built frameworks for [iOS](https://github.com/railsware/BloodMagic/releases/download/1.0.0/BloodMagic-iOS-1.0.0.zip) and [OSX](https://github.com/railsware/BloodMagic/releases/download/1.0.0/BloodMagic-OSX-1.0.0.zip).
Just drag&drop framework into your project and don't forget to add `-all_load`, `-ObjC` and `-lc++` or `-lstdc++` to `OTHER_LINKER_FLAGS`
### Available Spells
[Lazy Initialization](https://github.com/railsware/BloodMagic#lazy-initialization)
[Dependency Injection](https://github.com/railsware/BloodMagic#dependency-injection)
[Partial Views](https://github.com/railsware/BloodMagic#partial-views)
[Assign-once properties](https://github.com/railsware/BloodMagic#assign-once-properties)
[Preferences (NSUserDefaults wrapper)](https://github.com/railsware/BloodMagic#preferences)
BloodMagic has been designed to be extensible, so few more spells will be available soon.
====
#### Lazy initialization
```ruby
pod 'BloodMagic/Lazy', :git => 'https://github.com/railsware/BloodMagic.git'
```
Initializes object on demand.
If you use Objective-C, then you should be familiar with this code:
```objectivec
@interface ViewController : UIViewController
@property (nonatomic, strong) ProgressViewService *progressViewService;
@end
```
```objectivec
- (ProgressViewService *)progressViewService
{
if (_progressViewService == nil) {
_progressViewService = [ProgressViewService new];
}
return _progressViewService;
}
```
But we are able to automate this routine!
Just add `BMLazy` protocol to your class:
```objectivec
@interface ViewController : NSObject
@property (nonatomic, strong, bm_lazy) ProgressViewService *progressViewService;
@end
```
and mark any property as `@dynamic`:
```objetivec
@implementation ViewController
@dynamic progressViewService;
@end
```
Object `progressViewService` will be initialized on the first call
```objectivec
self.progressViewService
// or
yourViewController.progressViewService
```
or when you try to get value for key
```objectivec
[self valueForKey:@"progressViewService"]
// or
[yourViewController valueForKey:@"progressViewService"]
```
By default it creates an instance with the `+new` class' method.
In this case `progressViewService` will be deallocated as a usual property.
#### Dependency Injection
```ruby
pod 'BloodMagic/Injectable', :git => 'https://github.com/railsware/BloodMagic.git'
```
During the creation of `Lazy Initialization` spell an interesting side effect was found - Dependency Injection.
It behaves the same way as `BMLazy`, but uses another approach to instantiate object.
For example, if you need to initialize `progressViewService` in a special way, you should provide initializer:
```objectivec
BMInitializer *initializer = [BMInitializer injectableInitializer];
initializer.propertyClass = [ProgressViewService class]; // optional, uses NSObject by default
initializer.containerClass = [ViewController class]; // optional, uses NSObject by default
initializer.initializer = ^id (id sender){
return [[ProgressViewService alloc] initWithViewController:sender];
};
[initializer registerInitializer];
```
_Note:_ `containerClass` doesn't apply on derived classes, to achieve such behavior you should specify `containerClass` explicitly.
This spell is very useful when dealing with the singleton
```objectivec
BMInitializer *initializer = [BMInitializer injectableInitializer];
initializer.propertyClass = [RequestManager class];
initializer.initializer = ^id (id sender){
static id singleInstance = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
singleInstance = [RequestManager new];
});
return singleInstance;
};
[initializer registerInitializer];
```
Thus, neither the `RequestManager` nor the class that uses it, will not be aware about his singleton nature.
Adepts of [SRP](http://en.wikipedia.org/wiki/Single_responsibility_principle) school must approve ;)
Also, you're able to use `@protocol`s as well
```objectivec
BMInitializer *initializer = [BMInitializer injectableInitializer];
initializer.protocols = @[ @protocol(ProgressViewServiceProtocol) ];
initializer.initializer = ^id (id sender){
return [[ProgressViewService alloc] initWithViewController:sender];
};
[initializer registerInitializer];
```
##### Injection hooks
`BMInjectable` module provides a hook system to catch the object creation.
To enable these hooks just create instance method named `propertyNameInjected:`.
For example:
```objectivec
@implementation ViewController
@injectable(progressViewService)
- (void)progressViewServiceInjected:(ProgressViewService *service)
{
service.title = self.title;
}
@end
```
#### Partial Views
```ruby
pod 'BloodMagic/Partial', :git => 'https://github.com/railsware/BloodMagic.git'
```
Instantiates view from `xib` on demand, similar to `Lazy` module.
This spell might be helpful if you have reusable views.
For example:
You need to show the same user info in table cells (`UsersListViewController`) and in some header view (`UserProfileViewController`).
It makes sense to create one `UserView.xib` associated with `UserView` class and use it through the whole app.
So it may looks like this:
```objectivec
// Cell used from UsersListViewController
// Created manually
@implementation UserViewCell
{
UserView *_userView;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
NSString *nibName = NSStringFromClass([UserView class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
_userView = [[nib instantiateWithOwner:nil options:nil] lastObject];
[self addSubview:_userView];
}
return self;
}
@end
// View used from UserProfileViewController
// Created from xib
@implementation UserHeaderView
{
UserView *_userView;
}
- (void)awakeFromNib
{
[super awakeFromNib];
NSString *nibName = NSStringFromClass([UserView class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
_userView = [[nib instantiateWithOwner:nil options:nil] lastObject];
[self addSubview:_userView];
}
@end
```
Both cases use the same, similar code.
So, BloodMagic does nothing special, just hides this boilerplate:
```objectivec
#import
@interface UserViewCell ()
@property (nonatomic, strong, bm_partial) UserView *userView;
@end
@implementation UserViewCell
@dynamic userView;
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self addSubview:self.userView];
}
return self;
}
@end
// ...
@interface UserHeaderView ()
@property (nonatomic, strong, bm_partial) UserView *userView;
@end
@implementation UserHeaderView
@dynamic userView;
- (void)awakeFromNib
{
[super awakeFromNib];
[self addSubview:self.userView];
}
@end
```
#### Assign-once properties
```ruby
pod 'BloodMagic/Final', :git => 'https://github.com/railsware/BloodMagic.git'
```
Java provides [final](http://en.wikipedia.org/wiki/Final_(Java)) keyword, which determines (at least) that value can't be changed after initialization.
From now this feature available in Objective-C, via BloodMagic.
```objectivec
#import
@interface FinalizedObject : NSObject
@property (nonatomic, strong, bm_final) NSString *almostImmutableProperty;
@end
@implementation FinalizedObject
@dynamic almostImmutableProperty;
@end
// ...
FinalizedObject *object = [FinalizedObject new];
object.almostImmutableProperty = @"Initial value"; // everything is fine
object.almostImmutableProperty = @"Another value"; // exception will be thrown
```
#### Preferences
```ruby
pod 'BloodMagic/Preference', :git => 'https://github.com/railsware/BloodMagic.git'
```
Enjoy the simplest way to deal with `NSUserDefaults`
```objectivec
#import
@interface Settings : NSObject
@property (nonatomic, strong, bm_preference) NSString *nickname;
@end
@implementation Settings
@dynamic nickname;
@end
// ...
Settings *settings = [Settings new];
settings.nickname = @"AlexDenisov"; // @"AlexDenisov" goes to [NSUserDefaults standardUserDefaults] with key "nickname"
NSLog(@"My name is: %@", settings.nickname); // reads object for key "nickname" from [NSUserDefaults standardUserDefaults]
```
### Side effects (aka bugs)
BloodMagic may have side effects, if you find one, please, open issue or send us a pull request.
Those actions will help us to protect you from mutilation.