Objective-C 是一种强大的语言,但有时它缺乏自定义属性,如下所示
@property (nonatomic, strong, bm_lazy) ProgressViewService *progressView;
@property (nonatomic, strong, bm_partial) HeaderView *headerView;
@property (nonatomic, strong, bm_final) NSString *almostImmutable;
@property (nonatomic, strong, storable) NSString *authToken;
@property (nonatomic, strong, copyable) NSString *username;
@property (nonatomic, strong, copyable) NSString *email;
@property (nonatomic, strong, anything_you_want) AwesomeView *someAwesomeView;
我们不通过在 clang
上进行破解来实现这些属性,但幸运的是,我们可以通过 BloodMagic 的咒语来实现这些效果
通过 CocoaPods 进行简单安装
pod 'BloodMagic', :git => 'https://github.com/railsware/BloodMagic.git'
只需将框架拖放到您的项目中,并别忘了将 -all_load
和 -ObjC
添加到 OTHER_LINKER_FLAGS
。
BloodMagic 被设计为可扩展的,因此很快还将提供更多咒语。
pod 'BloodMagic/Lazy', :git => 'https://github.com/railsware/BloodMagic.git'
按需初始化对象。
如果您使用 Objective-C,那么您应该熟悉以下代码
@interface ViewController : UIViewController
@property (nonatomic, strong) ProgressViewService *progressViewService;
@end
- (ProgressViewService *)progressViewService
{
if (_progressViewService == nil) {
_progressViewService = [ProgressViewService new];
}
return _progressViewService;
}
但我们能够自动执行此例程
只需将 BMLazy
协议添加到您的类中
@interface ViewController : NSObject
<BMLazy>
@property (nonatomic, strong, bm_lazy) ProgressViewService *progressViewService;
@end
并将任何属性标记为 @dynamic
@implementation ViewController
@dynamic progressViewService;
@end
对象 progressViewService
将在第一次调用时初始化
self.progressViewService
// or
yourViewController.progressViewService
或者当您尝试获取密钥的值时
[self valueForKey:@"progressViewService"]
// or
[yourViewController valueForKey:@"progressViewService"]
默认情况下,它将使用类的方法的 +new
创建实例。
在这种情况下,progressViewService
将像通常属性一样被释放。
pod 'BloodMagic/Lazy', :git => 'https://github.com/railsware/BloodMagic.git'
在创建 Lazy Initialization
咒语时发现了一个有趣的副作用 - 一种依赖注入。
例如,如果您需要以特殊方式初始化 progressViewService
,可以创建一个特殊的初始化器。
BMInitializer *initializer = [BMInitializer lazyInitializer];
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];
注意: containerClass
在子类中不适用。要实现类似行为,您应该显式指定 containerClass
。
这个魔法在处理单例容器时非常有用。
BMInitializer *initializer = [BMInitializer lazyInitializer];
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];
因此,无论是 RequestManager
还是使用它的类,都不会意识到它是单例。
SRP(单一责任原则)的拥护者一定会赞同 ;)
此外,您还可以使用 @protocol
。
BMInitializer *initializer = [BMInitializer lazyInitializer];
initializer.protocols = @[ @protocol(ProgressViewServiceProtocol) ];
initializer.initializer = ^id (id sender){
return [[ProgressViewService alloc] initWithViewController:sender];
};
[initializer registerInitializer];
BMLazy
模块提供了一个钩子系统来捕获对象创建。要启用这些钩子,只需创建一个名为 propertyNameInjected:
的实例方法即可。
例如:
@implementation ViewController
@lazy(progressViewService)
- (void)progressViewServiceInjected:(ProgressViewService *service)
{
service.title = self.title;
}
@end
pod 'BloodMagic/Partial', :git => 'https://github.com/railsware/BloodMagic.git'
按需实例化 xib
视图,类似于 Lazy
模块。如果您有可重用的视图,此魔法可能非常有用。
例如:
您需要在不同部件中显示相同的用户信息(例如:在表格单元格中(UsersListViewController
)和在某个标题视图中(UserProfileViewController
))。在这种情况下,创建一个与 UserView
类关联的 UserView.xib
,并在整个应用程序中使用它,是非常合理的。
所以这可能会看起来像这样:
// 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
这两种情况使用了相同的代码,BloodMagic 什么都不做,只是隐藏了这些样板代码。
#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
pod 'BloodMagic/Final', :git => 'https://github.com/railsware/BloodMagic.git'
Java 提供了 final 关键字,它确定了一个值不能被改变。
从现在起,BloodMagic 通过BloodMagic使这一功能在 Objective-C 中也变得可用。
#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"; // all is OK
object.almostImmutableProperty = @"Another value"; // exception will be thrown
pod 'BloodMagic/Preference', :git => 'https://github.com/railsware/BloodMagic.git'
享受处理 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]
BloodMagic 可能会有副作用。如果您发现了,请创建一个问题或给我们发送拉取请求。
这些行动将帮助我们保护您免受破坏。