Giter VIP home page Giter VIP 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

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

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bloodmagic's Issues

Warning: "Protocol hasn't been found" on a delegate class that does not rely on BloodMagic

Hello,

Currently I am having

2015-10-12 14:00:45.979 Prototype[17600:31945410] BloodMagic: Protocol MaintenanceViewControllerDelegate hasn't been found. Please check if you include the header with that protocol description in your .m file
#import <BloodMagic/Injectable.h>

@class MaintenanceViewController;

@protocol AnalyticsProtocol;

@protocol MaintenanceViewControllerDelegate <NSObject>
- (void)maintenanceViewControllerRetryButtonWasCalled;
@end

@interface MaintenanceViewController : UIViewController
    <BMInjectable>

@property (nonatomic, strong) id<AnalyticsProtocol> analytics;
@property (weak, nonatomic) id <MaintenanceViewControllerDelegate> delegate;

@end

@analytics is injected by BM and delegate is just a regular property, however I get warning on that delegate property.

Thanks.

Initializer

@implementation APIClient

- (instancetype)init {
    self = [super initWithBaseURL:[NSURL URLWithString:BASE_URL]];
    if (!self) {
        return nil;
    }

    self.responseSerializer = [AFJSONResponseSerializer serializer];
    self.requestSerializer = [AFJSONRequestSerializer serializer];

    return self;
}

@end

//this method is executed before main.m
__attribute__((constructor)) static void APIClientInitialazer() {
   BMInitializer *initializer = [BMInitializer injectableInitializer];
    initializer.propertyClass = [APIClient class];
    initializer.initializer = ^id (id sender) {
       static id singleInstance = nil;
        static dispatch_once_t once;
        dispatch_once(&once, ^{
            NSLog(@"%@", BASE_URL);
            singleInstance = [[APIClient alloc] initWithBaseURL:[NSURL URLWithString:BASE_URL]];
        });
       return singleInstance; __
    };
    [initializer registerInitializer];
}

block is never exected. Am I wrong at some step?

Poor errors handling and memory management

For example :

- (NSSet *)protocols {
    if (!_protocols) {
        Protocol *__unsafe_unretained *protocols;
        uint protocolsCount = 0;
        protocols = class_copyProtocolList(_objcClass, &protocolsCount);
        NSMutableSet *mutableProtocols = [NSMutableSet setWithCapacity:protocolsCount];
        for (uint protocolIndex = 0; protocolIndex != protocolsCount; ++protocolIndex) {
            Protocol *protocol = protocols[protocolIndex];
            [mutableProtocols addObject:protocol];
        }

// What makes you sure "protocols" variable is not NULL ?
free(protocols);

        _protocols = [NSSet setWithSet:mutableProtocols];
    }
    return _protocols;
}

NSObject protocol is forward declared in BMPreference.h, why?

Hello, why don't you import #import <Foundation/NSObject.h> here?

If someone uses this header he will have to import Foundation/NSObject.h anyway, so why don't you import it yourself? Probably I miss some point.

You oblige developers to import NSObject.h before #import <BloodMagic/Preference.h>, otherwise, they get a compiler error.

Unregistered classes/protocols should be nil

I am building an app that will have multiple modules. Modules can be plugged in/out easily by using BM as DI framework. However, I am finding that when a module has not been registered in BM, it stills creates an instance object on first use.

The behaviour is even worse when dealing with protocols, because in that case an NSObject is inflated and its later use will break.

Is this expected behaviour? I would expect that any injection that has not been defined should return nil.

Example:

@interface MOAppDelegate () <BMLazy>
@property (nonatomic, strong) MOCModuleManager *moduleManager;
@property (nonatomic, strong) id<MOFMerchantsDAO> merchantsDAO;
@end

@implementation MOAppDelegate
@dynamic moduleManager;
@dynamic merchantsDAO;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSAssert(self.moduleManager == nil, @"Should have returned nil");
    NSAssert(self.merchantsDAO == nil || [self.merchantsDAO conformsToProtocol:@protocol(MOFMerchantsDAO)], @"Should have returned nil or at least conform to desired protocol");
    return YES;
}

bm_lazy

Why you use "bm_lazy" define? As I see library don't actually use itl. Library adds hook to all dynamic properties for objects that conforms to .

For now I expect that lazy initializer will be added only to the properties marked with bm_lazy. But not for all.

Bloodmagic swaps all @dynamic properties' implementatoins

Hi, I don't have a clear understanding of framework, because of lack of time, but as far as I understood,
BloodMagic implements on my own all properties that are marked as @dynamic, no matter if I added bm_injectable property attribute or not. This means that I can't use @dynamics for my own purposes and at the same time use BloodMagic. I wish I could.

Is there a way to fix it, or the core idea of framework realization is not compatible with my wish? Sorry, once again, I tried to answer my own question, but It would be less time consuming if you give me a hint.

Incorrect podfile

using

pod 'BloodMagic/Lazy', :git => 'https://github.com/railsware/BloodMagic.git', :branch => 'stable'

missing files

Core.h
Initializers.h
Lazy.h

Worked fine ~2 weeks ago

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

Not working for me.

My source:
`
*.h file

import <Foundation/Foundation.h>

import "Presenter.h"

import "RubricMenuView.h"

import <BloodMagic/Lazy.h>

import "ProductCategoryIteractor.h"

@protocol RubricMenuView;

@interface RubricMenuPresenter : NSObject <Presenter, BMLazy>

@Property (nonatomic, strong, bm_lazy) ProductCategoryIteractor* productCategoryIteractor;

  • (instancetype) initWith: (id) view;

@EnD

*.m file

import "RubricMenuPresenter.h"

@interface RubricMenuPresenter()

@Property (weak) id view;

@EnD

@implementation RubricMenuPresenter

@dynamic productCategoryIteractor;

pragma mark - RubricMenuPresenter methods

  • (instancetype) initWith: (id) view {
    self = [super init];
    if (self) {
    self.view = view;
    }
    return self;
    }

pragma mark - Presenter methods

  • (void) viewDidLoad {
    ProductCategoryIteractor* sdf = self.productCategoryIteractor;
    [[[sdf execute]
    subscribeOn: [RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
    NSLog(@"Log result");
    } error:^(NSError *error) {
    NSLog(@"Log error");
    }];
    }
    `

When called line with source "ProductCategoryIteractor* sdf = self.productCategoryIteractor;"
application is crashing with error "*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RubricMenuPresenter productCategoryIteractor]: unrecognized selector sent to instance 0x7fdfd51985a0'"

What I am doing wrong?

CocoaPods 'use_frameworks!' option breaks dependency injection

I have created a simple iOS project with CocoaPods and dependency injection using BloodMagic. It works perfectly. However, if I add the 'use_frameworks!' option in the Podfile, the app breaks. An error is thrown, which complains that the getter method of the dynamic property does not exist.

Status of the library

Hi there, thanks first of all for such a framework. I see that the last changes were done for a while ago, so is this still production usable or not? 😄

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.