美文网首页
一个菜鸟的搬运

一个菜鸟的搬运

作者: zhlv | 来源:发表于2020-07-27 14:12 被阅读0次

在iOS开发初级的总结

内存管理、修饰符的使用和区别、多线程、事件的传递和响应链、应用的生命周期、Notification、Block、Delegate。TableView的优化方案、Runtime相关.扩展,分类、对Runloop的理解、OC与JS之间的互相调用和通信方案、静态库的打包和集成、Crash日志的收集和分析,GCD,NSOptions, 等等等等等等啊

我只是搬运工,便于自己复习

内存管理

关于 内存管理, 估计都被说烂了...

为什么要使用内存管理?

1.就是对内存资源进行优化,能够是我们的应用程在性能上有很大的提高
2.如果忽略内存管理,可能导致应用占用内存过高,导致程序崩溃

管理的原则

在使用的过程中,谁retain,谁release;或者谁alloc,谁release。
在使用MRC时,当引用计数为0时,必须回收,引用计数不为0,则不回收;如果内存计数为0了,没回收,会造成内存泄露。 如果想使用已经创建好的某个对象(别人创建的),不能直接拿过去用,需要先retain(让计数+1),用完之后应该release(计数-1),否则会造成野指针。
使用ARC,只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁。
使用ARC后,不允许调用release,retain、retainCount等方法。
允许重写dealloc,但是不允许调用[super dealloc],系统会默认调用[super dealloc]。
虽然ARC给我们编程带来的很多好多,但也可能出现内存泄露。如下面两种情况:
循环参照:
1)A有个属性参照B,B有个属性参照A,如果都是strong参照的话,两个对象都无法释放。

  1. 死循环:如果有个ViewController中有无限循环,也会导致即使ViewController对应的view消失了,ViewController也不能释放。

修饰符的使用和区别

原文链接
修饰符assign、strong、weak、copy、readwrite、readonly、nonatomic、atomic。
ARC自动的在合适的地方加入retain、release代码,管理内存。

nonatomic和atomic的区别

atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。非常消耗性能。
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。如果没有使用多线程间的通讯编程,建议使用nonatomic。

readwrite和readonly的区别

readonly表示这个属性是只读的,就是只生成getter方法,不会生成setter方法。
readwrite表示可读写,生成getter和setter方法。

weak和strong的区别

默认情况下,一个指针都会使用 strong 属性,表明这是一个强引用。这意味着,只要引用存在,对象就不能被销毁。

week修饰主要是为了解决循环引用,weak是弱引用。
例如:
1、在修饰代理属性的时候使用weak
2、连线创建控件用weak
3、block避免循环引用、controller强引用block,block代码块内又调用Controller,这时避免循环引用 __weak typeof(self) weakSelf = self;

copy 和 strong的区别

NSString 用copy修饰和用strong修饰
当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。
当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。
所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。
只有copy源对象和修饰属性类型一致时,才是浅拷贝,只引用计数加一,不产生新对象。
mutableCopy 修饰属性都是深拷贝,产生新对象。

assign 和 weak的区别

assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。
assign其实也可以用来修饰对象。那么我们为什么不用它修饰对象呢?因为被assign修饰的对象(一般编译的时候会产生警告:Assigning retained object to unsafe property; object will be released after assignment)在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil,造成野指针。对象一般分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。
那为什么可以用assign修饰基本数据类型?因为基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针。
weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。

block变量定义时用copy

block的循环引用并不是strong导致的,在ARC环境下,系统底层也会做一次copy操作使block从栈区复制一块内存空间到堆区,所以strong和copy在对block的修饰上是没有本质区别的,只不过copy操作效率高而已。一般用copy修饰block。
原文链接

多线程

原文链接
主要有三种:NSThread、NSoperationQueue、GCD

NSThread轻量级别的多线程技术

是我们自己手动开辟的子线程,如果使用的是初始化方式就需要我们自己启动,如果使用的是构造器方式它就会自动启动。只要是我们手动开辟的线程,都需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收

  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是参数"];
    // 当使用初始化方法出来的主线程需要start启动
    [thread start];
    // 可以为开辟的子线程起名字
    thread.name = @"NSThread线程";
    // 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
    thread.threadPriority = 1;
    // 取消当前已经启动的线程
    [thread cancel];
    // 通过遍历构造器开辟子线程
    [NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"构造器方式"];

performSelector...只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
在编译阶段并不会去检查方法是否有效存在,如果不存在只会给出警告

    //在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
        [self performSelector:@selector(aaa) withObject:nil afterDelay:1];
      // 回到主线程。waitUntilDone:是否将该回调方法执行完在执行后面的代码,如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;如果是NO:就是不等回调方法结束,不会阻塞当前线程
        [self performSelectorOnMainThread:@selector(aaa) withObject:nil waitUntilDone:YES];
      //开辟子线程
        [self performSelectorInBackground:@selector(aaa) withObject:nil];
      //在指定线程执行
        [self performSelector:@selector(aaa) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]

需要注意的是:如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)

[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];

而performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行

GCD

原文链接

dispatch_async(dispatch_get_global_queue(0, 0), ^{
//执行耗时操作
……
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程进行UI刷新操作
};
};

熟悉不, 可能最常用的就是这个了

串行 & 并行

GCD中dispatch_queue大致可以分为三类

全局的并行的queue
主线程的串行的queue
自定义的queue
全局的queue和主线程的queue结合使用(上边提到的)就是我们平常最常用的一种用法,在异步线程中执行耗时操作,然后在UI线程执行刷新操作。
全局的queue我们可以通过dispatch_get_global_queue(0, 0)直接获取,这里有两个参数,第一个表示线程执行的优先级(第二个参数是预留参数暂时没有什么鸟用),什么意思呢?当我们通过dispatch_async(globalQueue, ^{}); 这种方式去异步执行一个操作时,实际上操作系统会创建一个新的线程,当我们同时执行多个这样的操作时,我们如何能保证哪个线程先执行呢?这时候这个参数就派上了用场。这个参数一共可以有四个值:

#######define DISPATCH_QUEUE_PRIORITY_HIGH 2
#######define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#######define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#######define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

GCD 对比 NSOprationQueue

我们要明确NSOperationQueue与GCD之间的关系
GCD是面向底层的C语言的API,NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象。

1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便
2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂
4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)

实际项目开发中,很多时候只是会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的且优化完善、运行快速的GCD是首选
如果考虑异步操作之间的事务性,顺序行,依赖关系,比如多线程并发下载,GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
不论是GCD还是NSOperationQueue,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建,调度管理和释放都做得很好。而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销
写个Demo

事件的传递和响应链

原文连接

UIResponder

在iOS中,能够响应事件的对象都是UIResponder的子类对象。UIResponder提供了四个点击的回调方法,分别对应用户点击开始、移动、结束和取消,其中只有在程序强制退出或者来电时,取消点击事件才会调用。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
响应链的传递

iOS应用程序加载时会先执行main函数
依次加载:UIApplication->AppDelegate->UIWindow->UIViewController->superView->subviews
关系为:[UIApplication sharedApplication].keyWindow.rootViewController.view.subviews
事件的产生和传递
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中。
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常先发送事件给应用程序主窗口(keyWindow)。
主窗口会在视图层次结构中找到一个最适合的视图来处理当前触摸事件,这也是整个事件处理过程的第一步。
找到合适的视图控件后,就会调用视图控件的touches方法来做具体事件的处理。

Hit-Testing机制

iOS使用Hit-Testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地(recursively)检查该view的所有子view。在层级上处于最近且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。
重写 hit方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    // 1.判断当前控件能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
 
    // 2. 判断点在不在当前控件
    if ([self pointInside:point withEvent:event] == NO) return nil;
  
    // 3.从后往前遍历自己的子控件
    NSInteger count = self.subviews.count;
    for (NSInteger i = count - 1; i >= 0; i--) {
        UIView *childView = self.subviews[i];
        // 把当前控件上的坐标系转换成子控件上的坐标系
        CGPoint childP = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childP withEvent:event];
        if (fitView) { // 寻找到最合适的view
            return fitView;
       }
  }
 // 循环结束,表示没有比自己更合适的view
return self;

响应链的事件传递过程

如果当前view的控制器存在,就传递给控制器;如果控制器不存在,则将其传给它的父视图。
在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,此时会将事件或消息传递给window对象进行处理。
如果window对象也不处理,则将事件或消息传递给UIApplication对象。
如果UIApplication也不能处理该事件或消息,则将其丢弃。
原文地址

VC的生命周期

原文地址
要了解UIViewController,先要弄清楚其生命周期。在面向对象的语言中,是对象,就一定要有生命周期,UIViewController也不例外,生命周期管理Controller的作用范围和时间,也管理其内对象的作用范围和时间。
首先,UIViewController中与其生命周期有关的几个函数如下:

agma mark --- life circle
// 非storyBoard(xib或非xib)都走这个方法
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
    
    }
    return self;
}

// 如果连接了串联图storyBoard 走这个方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
     NSLog(@"%s", __FUNCTION__);
    if (self = [super initWithCoder:aDecoder]) {
        
    }
    return self;
}

// xib 加载 完成
- (void)awakeFromNib {
    [super awakeFromNib];
     NSLog(@"%s", __FUNCTION__);
}

// 加载视图(默认从nib)
- (void)loadView {
    NSLog(@"%s", __FUNCTION__);
    self.view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.view.backgroundColor = [UIColor redColor];
}

//视图控制器中的视图加载完成,viewController自带的view加载完成
- (void)viewDidLoad {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLoad];
}


//视图将要出现
- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillAppear:animated];
}

// view 即将布局其 Subviews
- (void)viewWillLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillLayoutSubviews];
}

// view 已经布局其 Subviews
- (void)viewDidLayoutSubviews {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidLayoutSubviews];
}

//视图已经出现
- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidAppear:animated];
}

//视图将要消失
- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewWillDisappear:animated];
}

//视图已经消失
- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"%s", __FUNCTION__);
    [super viewDidDisappear:animated];
}

//出现内存警告  //模拟内存警告:点击模拟器->hardware-> Simulate Memory Warning
- (void)didReceiveMemoryWarning {
    NSLog(@"%s", __FUNCTION__);
    [super didReceiveMemoryWarning];
}

// 视图被销毁
- (void)dealloc {
    NSLog(@"%s", __FUNCTION__);
}

.initWithNibName:bundle
初始化UIViewController,执行关键数据初始化操作,非StoryBoard创建UIViewController都会调用这个方法。
** 注意: 不要在这里做View相关操作,View在loadView方法中才初始化。**

initWithCoder:
如果使用StoryBoard进行视图管理,程序不会直接初始化一个UIViewController,StoryBoard会自动初始化或在segue被触发时自动初始化,因此方法initWithNibName:bundle不会被调用,但是initWithCoder会被调用。
awakeFromNib
作为第二个方法的助手,方法处理一些额外的设置。
当awakeFromNib方法被调用时,所有视图的outlet和action已经连接,但还没有被确定,这个方法可以算作适合视图控制器的实例化配合一起使用的,因为有些需要根据用户喜好来进行设置的内容,无法存在storyBoard或xib中,所以可以在awakeFromNib方法中被加载进来。
loadView
创建或加载一个view并把它赋值给UIViewController的view属性。
当执行到loadView方法时,如果视图控制器是通过nib创建,那么视图控制器已经从nib文件中被解档并创建好了,接下来任务就是对view进行初始化。
loadView方法在UIViewController对象的view被访问且为空的时候调用。这是它与awakeFromNib方法的一个区别。
假设我们在处理内存警告时释放view属性:self.view = nil。因此loadView方法在视图控制器的生命周期内可能被调用多次。
loadView方法不应该直接被调用,而是由系统调用。它会加载或创建一个view并把它赋值给UIViewController的view属性。

在创建view的过程中,首先会根据nibName去找对应的nib文件然后加载。如果nibName为空或找不到对应的nib文件,则会创建一个空视图(这种情况一般是纯代码)
注意:在重写loadView方法的时候,不要调用父类的方法。

viewDidLoad
此时整个视图层次(view hierarchy)已经放到内存中,可以移除一些视图,修改约束,加载数据等。
当loadView将view载入内存中,会进一步调用viewDidLoad方法来进行进一步设置。此时,视图层次已经放到内存中,通常,我们对于各种初始化数据的载入,初始设定、修改约束、移除视图等很多操作都可以这个方法中实现。
视图层次(view hierachy):因为每个视图都有自己的子视图,这个视图层次其实也可以理解为一颗树状的数据结构。而树的根节点,也就是根视图(root view),在UIViewController中以view属性。它可以看做是其他所有子视图的容器,也就是根节点。

viewWillAppear
视图加载完成,并即将显示在屏幕上。还没设置动画,可以改变当前屏幕方向或状态栏的风格等。
系统在载入所有的数据后,将会在屏幕上显示视图,这时会先调用这个方法,通常我们会在这个方法对即将显示的视图做进一步的设置。比如,设置设备不同方向时该如何显示;设置状态栏方向、设置视图显示样式等。
另一方面,当APP有多个视图时,上下级视图切换是也会调用这个方法,如果在调入视图时,需要对数据做更新,就只能在这个方法内实现。

viewWillLayoutSubviews
即将开始子视图位置布局
view 即将布局其Subviews。 比如view的bounds改变了(例如:状态栏从不显示到显示,视图方向变化),要调整Subviews的位置,在调整之前要做的工作可以放在该方法中实现
viewDidLayoutSubviews
用于通知视图的位置布局已经完成
view已经布局其Subviews,这里可以放置调整完成之后需要做的工作。

viewDidAppear
视图已经展示在屏幕上,可以对视图做一些关于展示效果方面的修改。
在view被添加到视图层级中以及多视图,上下级视图切换时调用这个方法,在这里可以对正在显示的视图做进一步的设置。
viewWillDisappear
视图即将消失
在视图切换时,当前视图在即将被移除、或被覆盖是,会调用该方法,此时还没有调用removeFromSuperview。

viewDidDisappear
view已经消失或被覆盖,此时已经调用removeFromSuperView;
dealloc
视图被销毁,此次需要对你在init和viewDidLoad中创建的对象进行释放。
didReceiveMemoryWarning
在内存足够的情况下,app的视图通常会一直保存在内存中,但是如果内存不够,一些没有正在显示的viewController就会收到内存不足的警告,然后就会释放自己拥有的视图,以达到释放内存的目的。但是系统只会释放内存,并不会释放对象的所有权,所以通常我们需要在这里将不需要显示在内存中保留的对象释放它的所有权,将其指针置nil。

Notification

在 iOS 中,NSNotification和NSNotificationCenter是使用观察者模式来实现的用于跨层传递消息。
观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并自动更新。
首先,信息的传递就依靠通知(NSNotification),也就是说,通知就是信息(执行的方法,观察者本身(self),参数)的包装。
那么这个集合很容易想到就是NSDictionary!,key就是name,value就是NSArray(存放数据模型),里面存放观察者对象。当发送通知时,在通知的字典,根据``name找到value,这个value就是一数组,数组里面存放数据模型(observer、SEL)`。即可执行对应的行为

Block、

关于Block的文章
参考
参考
参考

Block简介

block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block是封装函数及其上下文的OC对象
一、block定义

//直接定义
@property(nonatomic,copy)void(^block)(NSInteger);
//方法中定义
-(void)setWithblock:(void(^)(NSInteger index))block;
//其他定义
typedefvoid(^Block)(NSInteger index);
@property(nonatomic,copy)Block block;

block捕获问题

1,对于 基本数据 类型 的 局部变量 截获 其值;
2,对于对象类型 的 局部变量 连同其 所有权修饰符一起截获;
3,局部静态变量 以 指针形式 截获 ;
4,不截获全局变量和静态全局变量 直接访问;

block循环引用

(1)__block
在ARC 和 MRC都可以使用,可以修饰对象,也可以修饰基本数据类型,可以在block中重新赋值。
(2)__weak
弱引用,只能在ARC下使用,只能修饰对象类型,在block中 只能使用不能修改。
(1)__strong
强引用,防止block中的对象 由于弱引用 被释放。

(why为什么block要变量捕获):为了保证block内部能够正常访问外部变量。
(why为什么局部变量需要捕获):考虑作用域的问题,需要跨函数访问,所以全局变量直接采用对应的变量值,静态变量因为数据值保存内对堆栈里,可以直接用指针访问到。
Q:block里访问self是否会捕获?
会,self是当调用block函数的参数,参数是局部变量,self指向调用者
Q:block里访问成员变量是否会捕获?
会,成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量

block为什么要用Copy修饰

block是一个对象, 所以block理论上是可以retain/release的. 但是block在创建的时候它的内存是默认是分配在栈(stack)上, 而不是堆(heap)上的. 所以它的作用域仅限创建时候的当前上下文(函数, 方法...), 当你在该作用域外调用该block时, 程序就会崩溃.
一般情况下你不需要自行调用copy或者retain一个block. 只有当你需要在block定义域以外的地方使用时才需要copy. Copy将block从内存栈区移到堆区.

block为什么要用weakself 或者strongself.

参考地址
参考地址
很nice
weakSelf和strongSelf
这么做和直接用self有什么区别,为什么不会有循环引用:外部的weakSelf是为了打破环,从而使得没有循环引用,而内部的strongSelf仅仅是个局部变量,存在栈中,会在block执行结束后回收,不会再造成循环引用。

这么做和使用weakSelf有什么区别:唯一的区别就是多了一个strongSelf,而这里的strongSelf会使ClassB的对象引用计数+1,使得ClassB pop到A的时候,并不会执行dealloc,因为引用计数还不为0,strongSelf仍持有ClassB,而在block执行完,局部的strongSelf才会回收,此时ClassB dealloc。

这样做其实已经可以解决所有问题,但是强迫症的我们依然能找到它的缺陷:

block内部必须使用strongSelf,很麻烦,不如直接使用self简便。

很容易在block内部不小心使用了self,这样还是会引起循环引用,这种错误很难发觉。

@weakify和@strongify

查看github上开源的libextobjc库,可以发现,里面的EXTScope.h里面有两个关于weak和strong的宏定义。

// 宏定义
#define weakify(...) \
    ext_keywordify \
    metamacro_foreach_cxt(ext_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
    ext_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(ext_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

// 用法
@interface ClassB ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, strong) NSString *str;
@end
@implementation ClassB
- (void)dealloc {
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.str = @"111";
    @weakify(self)
    self.block = ^{
        @strongify(self)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", self.str);
        });
    };
    self.block();   
}

系统是我block 为什么直接用self

UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。
不需要,之所以需要弱引用本身,是因为怕对象之间产生循环引用,引起程序的崩溃!
所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,

Delegate

代理是一种设计模式,以@protocol形式体现,一般是一对一传递。
一般以weak关键词以规避循环引用。

TableView卡顿

参考地址
cell赋值内容时,会根据内容设置布局,也就可以知道cell的高度,若有1000行,就会调用1000次 cellForRow方法,而我们对cell的处理操作,都是在这个方法中赋值,布局等等,开销很大。
3.优化方法
3.1优化:heightForRow方法处理cell高度。

 思路:赋值和计算布局分离。cellForRow负责赋值,heightRorRow负责计算高度。

3.2自定义cell绘制:

 遇到比较复杂的界面的时候,如复杂点的图文混排,上面的那种优化行高的方式可能就不能满足要求了,当然了,由于我的开发经验尚短,说实话,还没遇到要将自定义的Cell重新绘制。至于这方面,大家可以参考这篇博客,绝对是开发经验十足的大神,分享足够多的UITableView方面的性能优化,好多借鉴自这里,我都不好意思了。[http://www.cocoachina.com/ios/20150602/11968.html](https://link.jianshu.com?t=http%3A%2F%2Fwww.cocoachina.com%2Fios%2F20150602%2F11968.html)

3.3按需加载(UIScrollView方面):

 开发的过程中,自定义Cell的种类千奇百怪,但Cell本来就是用来显示数据的,不说100%带有图片,也差不多,这个时候就要考虑,下滑的过程中可能会有点卡顿,尤其网络不好的时候,异步加载图片是个程序员都会想到,但是如果给每个循环对象都加上异步加载,开启的线程太多,一样会卡顿,我记得好像线程条数一般3-5条,最多也就6条吧。这个时候利用UIScrollViewDelegate两个代理方法就能很好地解决这个问题。

4.总结

1.提前计算并缓存好高度,因为heightForRow最频繁的调用。

  • (UITableViewCell)tableView:(UITableView)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath;

2.异步绘制,遇到复杂界面,性能瓶颈时,可能是突破口。

3.滑动时按需加载,这个在大量图片展示,网络加载时,很管用。(SDWebImage已经实现异步加载)。

4.重用cells。

5.如果cell内显示得内容来自web,使用异步加载,缓存结果请求。当cell中的部分View是非常独立的,并且不便于重用的,而且“体积”非常小,在内存可控的前提下,我们完全可以将这些view缓存起来。当然也是缓存在模型中。

6.少用或不用透明图层,使用不透明视图。对于不透明的View,设置opaque为YES,这样在绘制该View时,就不需要考虑被View覆盖的其他内容(尽量设置Cell的view为opaque,避免GPU对Cell下面的内容也进行绘制)

7.减少subViews。分析Cell结构,尽可能的将 相同内容的抽取到一种样式Cell中,前面已经提到了Cell的重用机制,这样就能保证UITbaleView要显示多少内容,真正创建出的Cell可能只比屏幕显示的Cell多一点。虽然Cell的’体积’可能会大点,但是因为Cell的数量不会很多,完全可以接受的

8.少用addView给cell动态添加view,可以初始化的时候就添加,然后通过hide控制是否显示。只定义一种Cell,那该如何显示不同类型的内容呢?答案就是,把所有不同类型的view都定义好,放在cell里面,通过hidden显示、隐藏,来显示不同类型的内容。毕竟,在用户快速滑动中,只是单纯的显示、隐藏subview比实时创建要快得多。

Runtime理解

参考

OC与JS

参考

静态库的打包和集成

参考
参考

面试题:
https://www.jianshu.com/p/2d179ec72cb2
[2020-iOS最新面试题解析(原理篇]
(https://www.jianshu.com/p/a66da9351fff)
[2020-iOS最新面试题解析(Swift篇)]
(https://www.jianshu.com/p/e4168664d196)
[2020-iOS最新面试题解析(UI篇)]
(https://www.jianshu.com/p/6799d5a652f6)
[2020-iOS最新面试题解析(网络篇)]
(https://www.jianshu.com/p/7aa730830238)

自我介绍

面试官你好, 我叫XX,今年XX岁,xxxx年毕业于xx学校,XX年开始学习iOS开发, 至今有xx年开发经验, 先后经历过的团队协作开发,(iOS开发就有10多个) ,自也有独立开发的经验, 也熟悉APP上架流程, 以及申请账号的过程,上一份工作是在xx公司, 做的一个xx项目, 里面用到了xx技术,最近项目中的亮点, ....写自己拿手的(抛出话题, 就开始聊技术了), 最后说一下自己的性格.

Tableview为什么卡顿!
run loop 里面做哪些事情!
weakself storngself
uiviewanimation 动画 block
block 为什么要里面weakself
单例模式,怎么保持唯一,如果不用gcd怎么创建单例
autoreleasepool的理解
锁的使用, 有多少种
切圆角的方法,有几种, 你最长用的是那种, 为什么

swift中的
泛型 重点
https://www.jianshu.com/p/e903b67468b8

算法参看
https://www.jianshu.com/u/ea6b850c130e

相关文章

  • 一个菜鸟的搬运

    在iOS开发初级的总结 内存管理、修饰符的使用和区别、多线程、事件的传递和响应链、应用的生命周期、Notifica...

  • 2021-07-08R 数据重塑

    重塑分为 1、合并:merge() 2、融合:melt() 3、拆分cast() 搬运的菜鸟教程的代码:

  • android-学习自定义view

    菜鸟一枚,搬运工一个,搬轮子还算熟能生巧,突然让根据业务自定义UI了,瞬间懵逼。只能说运气好罢了,在GitHub里...

  • JSTL标签查看表

    内容声明:本文全部内容搬运自菜鸟教程。点击这里查看原文内容。注意:本文内容较多!不适合学习,适合在遗忘某个标签用法...

  • 拷贝般运常见问题解答

    问题1.拷贝搬运是指搬运什么?答:搬运文章的全部内容(文字/图片),不是单纯复制一个URL。问题2.怎么拷贝搬运?...

  • 今日读书《你从未真正拼过》

    一个菜鸟的自我修养就是在低级职位上不抓狂,当一个优秀的菜鸟就是为了有一天不当菜鸟。瞅准了机会迅速脱离菜鸟轨道,然后...

  • 菜鸟主管自救指南:新官上任三把火烧还是不烧?

    职场菜鸟新人,主管也有菜鸟主管。职场新人的烦恼,菜鸟主管也会遇到。很多菜鸟主管走马上任时,都会纠结一个问题“新官上...

  • 菜鸟是如何变成老鸟的?

    菜鸟是如何变成老鸟的?自己也是从一个菜鸟过来的,毕业到现在10多年了,也看着很多菜鸟变老鸟。今天想聊聊菜鸟变老鸟的...

  • 手把手教你操作电影搬运项目,零基础小白也可以学

    搬运赚钱算是网赚中最简单,最经典的一种赚钱方式了。各式各样的搬运,搬运文章,搬运视频,搬运资料,搬运商品,把网站内...

  • 网易号怎么搬运文章视频赚钱,技巧分享

    网易号怎么搬运视频,网易号怎么搬运文章赚钱.平台最重要的也是搬运,文章搬运也好,视频搬运也好,只要你会搬运,你做哪...

网友评论

      本文标题:一个菜鸟的搬运

      本文链接:https://www.haomeiwen.com/subject/jwijlktx.html