Objective-C 语言是一门动态语言,编译器不需要关心接受消息的对象是何种类型,接收消息的对象问题也要在运行时处理。
pragramming 层面的 runtime 主要体现在以下几个方面:
- 关联对象 Associated Objects
- 消息发送 Messaging
- 消息转发 Message Forwarding
- 方法调配 Method Swizzling
- “类对象” NSProxy Foundation | Apple Developer Documentation
- KVC、KVO About Key-Value Coding
补充一个大家经常用的、但是很容易忽视的 runtime 应用吧:
动态获取 class 和 slector
NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");
给分类添加属性和 KVC/KVO了。
大多数情况下,我们是不会直接用 runtime 写业务代码的,所以,大部分时候,我们只会在一些平时用起来很方便的框架里面,看到有用到 runtime 的黑魔法(当然你也可以自己造轮子)。runtime 用起来的最大感受就是,底层写一堆 runtime,外面用起来超清爽!对于那些“代码艺术家”来说,简直是爽到爆。
我在项目中使用runtime比较少:
1.在category中添加属性
2.修改系统方法,如重写objectAtIndex方法阻止数组越界崩溃。
大概说说一下自己在开发中用到的一些基于 runtime 的开源框架吧,具体应用自己可以去看看源码:
1.Aspects(AOP必备,“取缔” baseVC,无侵入埋点)
2.MJExtension(JSON 转 model,一行代码实现 NSCoding 协议的自动归档和解档)
3.JSPatch(动态下发 JS 进行热修复)
4.NullSafe(防止因发 unrecognised messages 给 NSNull 导致的崩溃)
5.UITableView-FDTemplateLayoutCell(自动计算并缓存 table view 的 cell 高度)
6.UINavigationController+FDFullscreenPopGesture(全屏滑动返回)
runtime 的原理和用法可以看看官方文档:
Objective-C Runtime Programming Guide Interacting with the Runtime
20180212 更新
在引导页上使用了runtime
.h代码如下:
#import <UIKit/UIKit.h>
@interface UIViewController (KSGuid)<UICollectionViewDataSource,UICollectionViewDelegate,UIScrollViewDelegate>
//*实现引导页的控制器,外部不用调用即可实现GuidView,可以修改下面的图片*/
@end
/*这里是要展示的图片,修改即可,当然不止三个 1242 * 2208的分辨率最佳,如果在小屏手机上显示不全,最好要求UI重新设计图片*/
#define ImageArray @[@"guid01",@"guid02",@"guid03",@"guid04"]
/** pageIndicatorTintColor*/
#define pageTintColor [[UIColor whiteColor] colorWithAlphaComponent:0.5];
/** currentPageIndicatorTintColor*/
#define currentTintColor [UIColor whiteColor];
/*
如果要修改立即体验按钮的样式
重新- (UIButton*)removeBtn方法即可
*/
.m代码如下
#import "UIViewController+KSGuid.h"
#import <objc/runtime.h>
#define CollectionView_Tag 15
#define RemoveBtn_tag 16
#define Control_tag 17
#define FIRST_IN_KEY @"FIRST_IN_KEY"
@interface KSGuidViewCell : UICollectionViewCell
@property (nonatomic, copy) NSString* imageName;
@property (nonatomic, strong) UIImageView* imageView;
@end
@implementation KSGuidViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_imageView = [[UIImageView alloc] initWithFrame:self.bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.userInteractionEnabled = YES;
[self.contentView addSubview:_imageView];
}
return self;
}
- (void)setImageName:(NSString *)imageName{
if (_imageName != imageName) {
_imageName = [imageName copy];
}
_imageView.image = [UIImage imageNamed:imageName];
}
@end
/************************以上是KSGuidViewCell,以下才是UIViewController+KSGuid******************************/
@implementation UIViewController (KSGuid)
#pragma mark-
#pragma mark 这里是退出的按钮
- (UIButton*)removeBtn{
//移除按钮样式
UIButton* removeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[removeBtn addTarget:self action:@selector(removeGuidView) forControlEvents:UIControlEventTouchUpInside];
removeBtn.hidden = (self.imageArray.count != 1);
removeBtn.tag = RemoveBtn_tag; //注意这里的tag
//***********************这里面可以自定义*******************************//
CGFloat btnW = 128;
CGFloat btnH = 35;
CGFloat btnX = CGRectGetMidX(self.view.frame) - btnW / 2;
CGFloat btnY = CGRectGetMaxY(self.view.frame) * 0.83;
removeBtn.frame = CGRectMake(btnX, btnY, btnW, btnH);
removeBtn.layer.cornerRadius = 4;
removeBtn.layer.borderColor = [UIColor whiteColor].CGColor;
removeBtn.layer.borderWidth = 1.;
[removeBtn setTitle:@"进入婚匠" forState:UIControlStateNormal];
[removeBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
removeBtn.titleLabel.font = [UIFont systemFontOfSize:18.];
//********************自定义结束**********************************//
return removeBtn;
}
#pragma mark-
#pragma mark 这里填充图片的名称
- (NSArray<NSString*>*)imageArray{
return ImageArray;
}
+ (void)load{
NSString* versoin = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString* versionCache = [[NSUserDefaults standardUserDefaults] objectForKey:FIRST_IN_KEY];
//启动时候首先判断是不是第一次
if ([versoin isEqualToString:versionCache]) {
return;
}
//以下代码只在程序安装初次运行时候执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method method1 = class_getInstanceMethod(self.class, @selector(viewDidLoad));
Method method2 = class_getInstanceMethod(self.class, @selector(guidViewDidLoad));
BOOL didAddMethod =
class_addMethod(self.class,
@selector(viewDidLoad),
method_getImplementation(method2),
method_getTypeEncoding(method2));
if (didAddMethod) {
class_replaceMethod(self.class,
@selector(guidViewDidLoad),
method_getImplementation(method1),
method_getTypeEncoding(method1));
} else {
method_exchangeImplementations(method1, method2);
}
});
}
- (void)guidViewDidLoad{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//这里的代码只在程序安装初次打开,并且在第一个控制器里面执行
//初始化视图
[self setupSubViews];
});
//这是调用工程里面的viewDidLoad
[self guidViewDidLoad];
}
#pragma mark-
#pragma mark 初始化视图
- (void)setupSubViews{
//界面样式
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.itemSize = [UIScreen mainScreen].bounds.size;
flowLayout.minimumLineSpacing = 0;
flowLayout.minimumInteritemSpacing = 0;
flowLayout.sectionInset = UIEdgeInsetsZero;
flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
UICollectionView* collectionView = [[UICollectionView alloc]
initWithFrame:self.view.bounds
collectionViewLayout:flowLayout];
collectionView.dataSource = self;
collectionView.delegate = self;
collectionView.pagingEnabled = YES;
collectionView.showsVerticalScrollIndicator = NO;
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.backgroundColor = [UIColor whiteColor];
[collectionView registerClass:[KSGuidViewCell class] forCellWithReuseIdentifier:@"KSGuidViewCell"];
collectionView.tag = CollectionView_Tag;
[self.view addSubview:collectionView];
[self.view addSubview:self.removeBtn];
UIPageControl* control = [[UIPageControl alloc] init];
CGFloat controlW = 170;
CGFloat controlH = 20;
CGFloat controlX = CGRectGetMidX(self.view.frame) - controlW / 2;
CGFloat controlY = CGRectGetMaxY(self.view.frame) - 38;
control.frame = CGRectMake(controlX, controlY, controlW, controlH);
control.numberOfPages = ImageArray.count;
control.pageIndicatorTintColor = pageTintColor;
control.currentPageIndicatorTintColor = currentTintColor;
control.tag = Control_tag;
[self.view addSubview:control];
}
#pragma mark-
#pragma mark UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return self.imageArray.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
KSGuidViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"KSGuidViewCell" forIndexPath:indexPath];
cell.imageName = self.imageArray[indexPath.row];
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
NSUInteger index = scrollView.contentOffset.x / CGRectGetWidth(self.view.frame);
[self.view viewWithTag:RemoveBtn_tag].hidden = (index != self.imageArray.count - 1);
UIPageControl* control =[self.view viewWithTag:Control_tag];
control.currentPage = index;
}
- (void)removeGuidView{
NSString* versoin = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
[[NSUserDefaults standardUserDefaults] setObject:versoin forKey:FIRST_IN_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
[[self.view viewWithTag:Control_tag] removeFromSuperview];
[[self.view viewWithTag:RemoveBtn_tag] removeFromSuperview];
[[self.view viewWithTag:CollectionView_Tag] removeFromSuperview];
}
@end