使用Xtrace分析MJRefresh技术实现细节,可能碰到的

2019-09-11 16:07栏目:专项工作
TAG:

关系:

最近在搭建新项目,为了方便开发,常会用到一些宏定义,梳理了之前项目中用到,又查漏补缺挑选了一些网络上比较不错的,总结了一份分享给大家。

把简单留给别人,把复杂留给自己。

①:先搞清两者的关系,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象!

////define.h//MiAiApp////Created by徐阳on 2017/5/18.//Copyright © 2017年徐阳. All rights reserved.////通用宏定义#ifndef define_h#define define_h//获取系统对象#define kApplication[UIApplication sharedApplication]#define kAppWindow[UIApplication sharedApplication].delegate.window#define kAppDelegate[AppDelegate shareAppDelegate]#define kRootViewController[UIApplication sharedApplication].delegate.window.rootViewController#define kUserDefaults[NSUserDefaults standardUserDefaults]#define kNotificationCenter[NSNotificationCenter defaultCenter]//获取屏幕宽高#define KScreenWidth([[UIScreen mainScreen]bounds].size.width)#define KScreenHeight[[UIScreen mainScreen]bounds].size.height#define kScreen_Bounds[UIScreen mainScreen].bounds#define Iphone6ScaleWidth KScreenWidth/375.0#define Iphone6ScaleHeight KScreenHeight/667.0//根据ip6的屏幕来拉伸#define kRealValue*(KScreenWidth/375.0f))//强弱引用#define kWeakSelf__weak typeofweak##type = type;#define kStrongSelf__strong typeoftype = weak##type;//View圆角和加边框#define ViewBorderRadius(View,Radius,Width,Color)\[View.layer setCornerRadius:];[View.layer setMasksToBounds:YES];[View.layer setBorderWidth:];[View.layer setBorderColor:[Color CGColor]]// View圆角#define ViewRadius(View,Radius)\[View.layer setCornerRadius:];[View.layer setMasksToBounds:YES]//property属性快速声明#define PropertyString@property(nonatomic,copy)NSString * s#define PropertyNSInteger@property(nonatomic,assign)NSIntegers#define PropertyFloat@property(nonatomic,assign)floats#define PropertyLongLong@property(nonatomic,assign)long long s#define PropertyNSDictionary@property(nonatomic,strong)NSDictionary * s#define PropertyNSArray@property(nonatomic,strong)NSArray * s#define PropertyNSMutableArray@property(nonatomic,strong)NSMutableArray * s///IOS版本判断#define IOSAVAILABLEVERSION([[UIDevice currentDevice]availableVersion:version]< 0)//当前系统版本#define CurrentSystemVersion[[UIDevice currentDevice].systemVersion doubleValue]//当前语言#define CurrentLanguage([NSLocale preferredLanguages]objectAtIndex:0])//-------------------打印日志-------------------------//DEBUG模式下打印日志,当前行#ifdef DEBUG#define DLogNSLog((@"%s[Line %d]" fmt),__PRETTY_FUNCTION__,__LINE__,##__VA_ARGS__);#else#define DLog#endif//拼接字符串#define NSStringFormat(format,...)[NSString stringWithFormat:format,##__VA_ARGS__]//颜色#define KClearColor[UIColor clearColor]#define KWhiteColor[UIColor whiteColor]#define KBlackColor[UIColor blackColor]#define KGrayColor[UIColor grayColor]#define KGray2Color[UIColor lightGrayColor]#define KBlueColor[UIColor blueColor]#define KRedColor[UIColor redColor]#define kRandomColorKRGBColor(arc4random_uniform/255.0,arc4random_uniform/255.0,arc4random_uniform/255.0)//随机色生成//字体#define BOLDSYSTEMFONT[UIFont boldSystemFontOfSize:FONTSIZE]#define SYSTEMFONT[UIFont systemFontOfSize:FONTSIZE]#define FONT(NAME,FONTSIZE)[UIFont fontWithName:size:]//定义UIImage对象#define ImageWithFile[UIImage imageWithContentsOfFile:([[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:@"%@@%dx",_pointer,[UIScreen mainScreen].nativeScale]ofType:@"png"])]#define IMAGE_NAMED[UIImage imageNamed:name]//数据验证#define StrValid(f!=nil &&[f isKindOfClass:[NSString class]]&& ![f isEqualToString:@""])#define SafeStr(StrValid#define HasString([str rangeOfString:key].location!=NSNotFound)#define ValidStrStrValid#define ValidDict(f!=nil &&[f isKindOfClass:[NSDictionary class]])#define ValidArray(f!=nil &&[f isKindOfClass:[NSArray class]]&&[f count]>0)#define ValidNum(f!=nil &&[f isKindOfClass:[NSNumber class]])#define ValidClass(f!=nil &&[f isKindOfClass:[cls class]])#define ValidData(f!=nil &&[f isKindOfClass:[NSData class]])//获取一段时间间隔#define kStartTime CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();#define kEndTimeNSLog(@"Time: %f",CFAbsoluteTimeGetCurrent//打印当前方法名#define ITTDPRINTMETHODNAME()ITTDPRINT(@"%s",__PRETTY_FUNCTION__)//GCD#define kDISPATCH_ASYNC_BLOCKdispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),block)#define kDISPATCH_MAIN_BLOCKdispatch_async(dispatch_get_main_queue//GCD -一次性执行#define kDISPATCH_ONCE_BLOCK(onceBlock)static dispatch_once_t onceToken;dispatch_once(&onceToken,onceBlock);//单例化一个类#define SINGLETON_FOR_HEADER(className)\+(className *)shared##className;#define SINGLETON_FOR_CLASS(className)\+(className *)shared##className { static className *shared##className = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken,^{ shared##className =[[self alloc]init];});return shared##className;}#endif /* define_h */

作为优秀的第三方库,MJRefresh充分贯彻了这句话。但我们不光是用户,我们还是创作者。所以,深入了解其背后的实现细节,既能学习优秀的编程思维,还能为我们将来自定义提供方便。要说分析别人的代码的话,光看源码,切来切去既影响效率还容易出错。好在,有Xtrace这款神器。

②:GCD仅仅支持FIFO队列,而NSOperationQueue中的队列可以被重新设置优先级,从而实现不同操作的执行顺序调整。GCD不支持异步操作之间的依赖关系设置。如果某个操作的依赖另一个操作的数据(生产者-消费者模型是其中之一),使用NSOperationQueue能够按照正确的顺序执行操作。GCD则没有内建的依赖关系支持。

以上属于臭码农原创,若有雷同属巧合,如有错误望指正,转载请标明来源和作者。by:臭码农

如果觉得一个东西太复杂,那是因为还没有抽象到一定高度去分析,然后,针对每一个子模块,肢解到最简单去分析。--大象:Thinking in UML

③:NSOperationQueue支持KVO,意味着我们可以观察任务的执行状态。

我对上面这句话的理解:抽象:抛开具体实现细节,将目标概括提取。高度:决定你分析的层级,也就是你准备从多大的粒度开始分析。

了解以上不同,我们可以从以下角度来回答

先分析一下MJRefresh的总体构成。

性能:①:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话

第一层,MJRefresh

图片 1

这是对MJRefresh最高层级的抽象了,它就是它,我知道它是做什么的就行。简单点来说,就是当我们在目录里看到这个词的时候,知道它是刷新控件。

②:从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持

第二层,MJRefreshHeader & MJRefreshFooter

图片 2

这时候,我们知道,MJRefresh中包含了下拉刷新和上拉加载两个子控件,我们日常使用是这样子的:

 self.tableView.mj_header = [MJRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(refreshData)]; self.tableView.mj_footer = [MJRefreshFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];

③:如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势

第三层,MJRefreshCustomView

图片 3

顾名思义,CustomView可以让我们根据自己的需求,自定义控件。MJ也为我们提供了基础的CustomView供我们使用,基本能满足大部分日常需求。

至此,MJRefresh的总体结构已经抽象完毕了,可以看到,仅仅只有三层而已。

面向对象编程中,绝大部分对象,我都偏向于抽象成两个部分:

  • 初始化:这部分的代码跟运行时没有关系,或者关系轻微(比如在布局时,根据SuperView的相关参数对自身进行设置)
  • 运行时:只有发生事件(比如KVO、Gesture等)时,才会调用的部分。

当然,不是说所有的代码非此即彼,肯定会存在一些模棱两可的部分,这时候的处理完全看个人喜好,毕竟我们所做的一切都是以理清思路为目的的。

同样需要说明的是:我们这里先不进行代码的具体功能分析,因为这属于比较低层次的抽象部分,我们这里的主要目的是搞清楚MJRefresh或者说UIView的加载过程。

最后的一句话:别忘了高德纳的教诲:“在大概97%的时间里,我们应该忘记微小的性能提升。过早优化是万恶之源。”只有Instruments显示有真正的性能提升时才有必要用低级的GCD。

MJRefrsh类结构

图片 4

MJ本人提供的类图结构,在第三层(CustomView)与第二层(Header & Footer)的中间插入了更细分的类,方便我们进行半自定义。

  • UNIX主要支持三种通信方式:
  • 基本通信:主要用来协调进程间的同步和互斥
    • 锁文件通信:通信的双方通过查找特定目录下特定类型的文件来完成进程间 对临界资源访问时的互斥;例如进程p1访问一个临界资源,首先查看是否有一个特定类型文件,若有,则等待一段时间再查找锁文件。
    • 记录锁文件
  • 管道通信:适应大批量的数据传递
  • IPC :适应大批量的数据传递

MJRefreshComponent

MJRefreshComponent作为基类,定义了MJRefresh的整体流程,其它子类只是在此流程的基础上,通过覆写基类的方法,实现定制。

图片 5

MJRefreshComponent继承自UIView,所以其初始化部分,基本都是覆写了UIView的方法。其添加的自定义方法为:

- prepare;- placeSubviews;

这两个方法的调用,分别写在了init与layoutSubViews的覆写方法中

- (instancetype)initWithFrame:frame{ if (self = [super initWithFrame:frame]) { // 准备工作 [self prepare]; // 默认是普通状态 self.state = MJRefreshStateIdle; } return self;}- prepare{ // 基本属性 self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.backgroundColor = [UIColor clearColor];}- layoutSubviews{ [self placeSubviews]; [super layoutSubviews];}- placeSubviews{}

MJRefreshHeader

图片 6

Header部分,主要是对Component的进一步具象,通过覆盖prepare、placeSubViews方法,更进一步的实现RefreshView的具体细节。我们进行完全自定义的时候,最好是直接继承自MJRefreshHeader类,因为MJ在此类上提供了完整的流程控制和极简的构造方法。Footer部分与Header部分一样,只是具体的逻辑部分会稍有不同。CustomView部分则是进一步具象了,就不进行重复内容的介绍了。

函数实现部分,只是孤零零的存在,缺失了情景的支持,没有任何意义。因此我们需要将函数代入具体的流程中,才能理解,为什么函数内部要这么写。我在这里使用的代码,就是MJRefresh提供的demo,有兴趣的童鞋可以自己用Xtrace追踪下试试。

  • 进程的同步机制原子操作 信号量机制 自旋锁 管程,会合,分布式系统

  • 进程之间通信的途径:共享存储系统消息传递系统管道:以文件系统为基础

  • 进程死锁的原因:资源竞争及进程推进顺序非法

  • 死锁的4个必要条件:互斥、请求保持、不可剥夺、环路

  • 死锁的处理:鸵鸟策略、预防策略、避免策略、检测与解除死锁

Xtrace

首先,简单介绍下Xtrace这款工具,它会打印出所有被追踪类所调用的方法,其使用方法也很简单:

  1. 将Xtrace.h与Xtrace.mm文件拖入工程
  2. 在需要追踪的类中引入Xtrace.h头文件
  3. [specific class xtrace]即可

一般是在AppDelegate 方法中调用,因为这样可以捕捉到完整的调用链。#import "Xtrace.h"#import "MJRefreshNormalHeader.h"- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[MJRefreshNormalHeader xtrace];}

不过在此追踪MJRefresh的话,会提示animate帧太大,使用[Xtrace excludeMethod:]方法排除animate方法时却会报错,这里我也没搞懂怎么回事,如果有熟悉Xtrace的童鞋,希望指导一下。所以这里我只能在类初始化的时候调用Xrace了,不过好在,影响不大。

MJRefresh的初始化VC是MJExampleViewController:

- viewDidLoad{ [Xtrace showReturns:NO]; [MJRefreshNormalHeader xtrace];//在初始化MJRefresh类之前调用Xtrace [super viewDidLoad]; __unsafe_unretained UITableView *tableView = self.tableView; // 下拉刷新 tableView.mj_header= [MJRefreshNormalHeader headerWithRefreshingBlock:^{ // 模拟延迟加载数据,因此2秒后才调用(真实开发中,可以移除这段gcd代码) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 结束刷新 [tableView.mj_header endRefreshing]; }); }]; …… ……

具体流程

图片 7

VC调用MJRefreshHeader的构造方法,该构造方法调用自身init

#pragma mark - 构造方法+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock{ MJRefreshHeader *cmp = [[self alloc] init]; cmp.refreshingBlock = refreshingBlock; return cmp;}+ (instancetype)headerWithRefreshingTarget:target refreshingAction:action{ MJRefreshHeader *cmp = [[self alloc] init]; [cmp setRefreshingTarget:target refreshingAction:action]; return cmp;}

子类中并没有覆写基类的init方法,所以默认还是调用基类的init

#pragma mark - 基类(Component) Init- (instancetype)initWithFrame:frame{ if (self = [super initWithFrame:frame]) { // 准备工作 [self prepare]; // 默认是普通状态 self.state = MJRefreshStateIdle; } return self;}
  • 线程是进程的基本单位。

  • 进程和线程都是由操作系统所产生的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。

  • 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

  • 进程有独立的地址空间,一个进程崩溃后,在保护模式下 不会对其它进程产生影响。

  • 线程只是一个进程中的不同执行路径。

  • 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

  • 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1.2 init : [self prepare]

基类init定义,会直接调用[self prepare]。self prepare 是这么定义的:

#pragma mark - NormalHeader prepare- prepare{ [super prepare]; ... ...}

所以会一层一层优先调用父类的prepare方法:

图片 8

1.2.1 Header prepare:
#pragma mark - MJRefreshHeader prepare- prepare{ [super prepare]; // 设置key self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; // 设置高度 self.mj_h = MJRefreshHeaderHeight;}

图片 9RefreshHeader运行状态

  • performSelector:onThread:withObject:waitUntilDone:
  • NSMachPort(基本机制:A线程创建NSMachPort对象,并加入A线程的runloop。当创建B线程时,将创建的NSMachPort对象传递到主体入口点,B线程就可以使用相同的端口对象将消息传回A线程
1.2.2 StateHeader prepare:
#pragma mark - MJRefreshStateHeader prepare- prepare{ [super prepare]; // 初始化间距 self.labelLeftInset = MJRefreshLabelLeftInset; // 初始化文字 [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling]; [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing];}

图片 10图片 11StateHeader调用.png

1.2.3 NormalHeader prepare:
#pragma mark - 重写父类的方法- prepare{ [super prepare]; self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;}
  • 首先搞清楚什么是线程、什么是多线程
    • Mach是第一个以多线程方式处理任务的系统,因此多线程的底层实现机制是基于Mach的线程
    • 开发中很少用Mach级的线程,因为Mach级的线程没有提供多线程的基本特征,线程之间是独立的
  • 开发中实现多线程的方案
    • C语言的POSIX接口:#include <pthread.h>
    • OC的NSThread
    • C语言的GCD接口(性能最好,代码更精简)
    • OC的NSOperation和NSOperationQueue
Prepare小结:

至此,我们只完成了基类init方法(忘了的童鞋请返回去重新看一下)中的一小步,及[self prepare]。接下来还有一个方法self.state = .....注意,这里会涉及到写属性setState,之所以要介绍这个写属性,是因为其实现代码里涉及到了子视图的加载。

MJ在MJRefreshHeader中,对此写方法进行了定义,看似代码很多,其实核心逻辑很简单:

  1. 判断当前状态(Idle、Pulling、Refreshing)
  2. 根据状态设定MJRefreshHeader SubViews的视图属性
  3. 执行动画
- setState:(MJRefreshState)state{ MJRefreshCheckState // 根据状态做事情 if (state == MJRefreshStateIdle) { if (oldState == MJRefreshStateRefreshing) { self.arrowView.transform = CGAffineTransformIdentity; [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{ self.loadingView.alpha = 0.0; } completion:^(BOOL finished) { // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态 if (self.state != MJRefreshStateIdle) return; self.loadingView.alpha = 1.0; [self.loadingView stopAnimating]; self.arrowView.hidden = NO; }]; } else { [self.loadingView stopAnimating]; self.arrowView.hidden = NO; [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformIdentity; }]; } } else if (state == MJRefreshStatePulling) { [self.loadingView stopAnimating]; self.arrowView.hidden = NO; [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); }]; } else if (state == MJRefreshStateRefreshing) { self.loadingView.alpha = 1.0; // 防止refreshing -> idle的动画完毕动作没有被执行 [self.loadingView startAnimating]; self.arrowView.hidden = YES; }}

因为涉及到了子视图属性的设置,所以会加载子视图,调用流程如下:

图片 12图片 13

版权声明:本文由ag真人发布于专项工作,转载请注明出处:使用Xtrace分析MJRefresh技术实现细节,可能碰到的