Code Monkey home page Code Monkey logo

bloodmagic's Introduction

BloodMagic

License MIT   Build status   Version   Platform

Objective-C is a powerful language, but sometimes it lacks of custom property attributes, like these:

@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<NetworkClient> 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

Blog-post

Presentation by AlexDenisov

Presentation by Ievgen Solodovnykov

Embark on the Dark

  pod 'BloodMagic', :git => 'https://github.com/railsware/BloodMagic.git'
$ 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 and OSX.

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

Dependency Injection

Partial Views

Assign-once properties

Preferences (NSUserDefaults wrapper)

BloodMagic has been designed to be extensible, so few more spells will be available soon.

====

Lazy initialization

  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:

@interface ViewController : UIViewController

@property (nonatomic, strong) ProgressViewService *progressViewService;

@end
- (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:

@interface ViewController : NSObject
  <BMLazy>

@property (nonatomic, strong, bm_lazy) ProgressViewService *progressViewService;

@end

and mark any property as @dynamic:

@implementation ViewController

@dynamic progressViewService;

@end

Object progressViewService will be initialized on the first call

self.progressViewService
// or
yourViewController.progressViewService

or when you try to get value for key

[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

  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:

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

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 school must approve ;)

Also, you're able to use @protocols as well

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:

@implementation ViewController

@injectable(progressViewService)

- (void)progressViewServiceInjected:(ProgressViewService *service)
{
    service.title = self.title;
}

@end

Partial Views

  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:

// 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:

#import <BloodMagic/Partial.h>

@interface UserViewCell ()
    <BMPartial>

@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 ()
    <BMPartial>

@property (nonatomic, strong, bm_partial) UserView *userView;

@end

@implementation UserHeaderView

@dynamic userView;

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self addSubview:self.userView];
}

@end

Assign-once properties

  pod 'BloodMagic/Final', :git => 'https://github.com/railsware/BloodMagic.git'

Java provides final keyword, which determines (at least) that value can't be changed after initialization.

From now this feature available in Objective-C, via BloodMagic.

#import <BloodMagic/Final.h>

@interface FinalizedObject : NSObject
    <BMFinal>

@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

pod 'BloodMagic/Preference', :git => 'https://github.com/railsware/BloodMagic.git'

Enjoy the simplest way to deal with NSUserDefaults

#import <BloodMagic/Preference.h>

@interface Settings : NSObject
    <BMPreference>

@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.

bloodmagic's People

Contributors

alexdenisov avatar dodikk avatar 0xc010d avatar kkazuo avatar artfeel avatar atermenji avatar bitdeli-chef avatar danskeel avatar stanislaw avatar vdugnist avatar

Watchers

James Cloos avatar hadi avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.