Objective-C 語(yǔ)言是一門(mén)動(dòng)態(tài)語(yǔ)言,編譯器不需要關(guān)心接受消息的對(duì)象是何種類(lèi)型,接收消息的對(duì)象問(wèn)題也要在運(yùn)行時(shí)處理间雀。
pragramming 層面的 runtime 主要體現(xiàn)在以下幾個(gè)方面:
- 關(guān)聯(lián)對(duì)象 Associated Objects
- 消息發(fā)送 Messaging
- 消息轉(zhuǎn)發(fā) Message Forwarding
- 方法調(diào)配 Method Swizzling
- “類(lèi)對(duì)象” NSProxy Foundation | Apple Developer Documentation
- KVC悔详、KVO About Key-Value Coding
補(bǔ)充一個(gè)大家經(jīng)常用的、但是很容易忽視的 runtime 應(yīng)用吧:
動(dòng)態(tài)獲取 class 和 slector
NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");
給分類(lèi)添加屬性和 KVC/KVO了惹挟。
大多數(shù)情況下茄螃,我們是不會(huì)直接用 runtime 寫(xiě)業(yè)務(wù)代碼的,所以连锯,大部分時(shí)候责蝠,我們只會(huì)在一些平時(shí)用起來(lái)很方便的框架里面,看到有用到 runtime 的黑魔法(當(dāng)然你也可以自己造輪子)萎庭。runtime 用起來(lái)的最大感受就是霜医,底層寫(xiě)一堆 runtime,外面用起來(lái)超清爽驳规!對(duì)于那些“代碼藝術(shù)家”來(lái)說(shuō)肴敛,簡(jiǎn)直是爽到爆。
我在項(xiàng)目中使用runtime比較少:
1.在category中添加屬性
2.修改系統(tǒng)方法吗购,如重寫(xiě)objectAtIndex方法阻止數(shù)組越界崩潰医男。
大概說(shuō)說(shuō)一下自己在開(kāi)發(fā)中用到的一些基于 runtime 的開(kāi)源框架吧,具體應(yīng)用自己可以去看看源碼:
1.Aspects(AOP必備捻勉,“取締” baseVC镀梭,無(wú)侵入埋點(diǎn))
2.MJExtension(JSON 轉(zhuǎn) model,一行代碼實(shí)現(xiàn) NSCoding 協(xié)議的自動(dòng)歸檔和解檔)
3.JSPatch(動(dòng)態(tài)下發(fā) JS 進(jìn)行熱修復(fù))
4.NullSafe(防止因發(fā) unrecognised messages 給 NSNull 導(dǎo)致的崩潰)
5.UITableView-FDTemplateLayoutCell(自動(dòng)計(jì)算并緩存 table view 的 cell 高度)
6.UINavigationController+FDFullscreenPopGesture(全屏滑動(dòng)返回)
runtime 的原理和用法可以看看官方文檔:
Objective-C Runtime Programming Guide Interacting with the Runtime
20180212 更新
在引導(dǎo)頁(yè)上使用了runtime
.h代碼如下:
#import <UIKit/UIKit.h>
@interface UIViewController (KSGuid)<UICollectionViewDataSource,UICollectionViewDelegate,UIScrollViewDelegate>
//*實(shí)現(xiàn)引導(dǎo)頁(yè)的控制器踱启,外部不用調(diào)用即可實(shí)現(xiàn)GuidView,可以修改下面的圖片*/
@end
/*這里是要展示的圖片报账,修改即可,當(dāng)然不止三個(gè) 1242 * 2208的分辨率最佳,如果在小屏手機(jī)上顯示不全,最好要求UI重新設(shè)計(jì)圖片*/
#define ImageArray @[@"guid01",@"guid02",@"guid03",@"guid04"]
/** pageIndicatorTintColor*/
#define pageTintColor [[UIColor whiteColor] colorWithAlphaComponent:0.5];
/** currentPageIndicatorTintColor*/
#define currentTintColor [UIColor whiteColor];
/*
如果要修改立即體驗(yàn)按鈕的樣式
重新- (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:@"進(jìn)入婚匠" forState:UIControlStateNormal];
[removeBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
removeBtn.titleLabel.font = [UIFont systemFontOfSize:18.];
//********************自定義結(jié)束**********************************//
return removeBtn;
}
#pragma mark-
#pragma mark 這里填充圖片的名稱(chēng)
- (NSArray<NSString*>*)imageArray{
return ImageArray;
}
+ (void)load{
NSString* versoin = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString* versionCache = [[NSUserDefaults standardUserDefaults] objectForKey:FIRST_IN_KEY];
//啟動(dòng)時(shí)候首先判斷是不是第一次
if ([versoin isEqualToString:versionCache]) {
return;
}
//以下代碼只在程序安裝初次運(yùn)行時(shí)候執(zhí)行
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, ^{
//這里的代碼只在程序安裝初次打開(kāi)埠偿,并且在第一個(gè)控制器里面執(zhí)行
//初始化視圖
[self setupSubViews];
});
//這是調(diào)用工程里面的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