美文网首页
iOS知识点总结(2)

iOS知识点总结(2)

作者: 飞哥漂流记 | 来源:发表于2019-12-12 20:59 被阅读0次

1. 内存中的栈和堆的区别是什么?那些数据在栈上,哪些在堆上?

对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来讲,释放工作有程序员控制,容易产生memory Leak。

栈的存储顺序是有高地址向低地址 是一块连续的内存区域。能获得栈的空间较小 栈有两种分配方式:静态分配和动态分配

堆的存储循序是有低地址向高地址  是不连续的内存区域 堆获得的空间比较灵活,也比较大。堆是动态分配和回收内存的,没

有静态分配的堆

2. 什么情况下会出现内存的循环引用block中的weak self,是任何时候都需要加的么?

引发循环引用,是因为当前self在强引用着block,而block又引用着self,这样就造成了循环引用。而需不需要使用[weak self]就

是由循环引用来决定,如果造成了循环引用,就必须使用[weak self]来打破循环.

3. GCD的queue,main queue中执行的代码,一定是在main thread么?

对于queue中所执行的代码不一定在main thread中。如果queue是在主线程中创建的,那么所执行的代码就是在主线程中执

行。如果是在子线程中创建的,那么就不会在main thread中执行。

 对于main queue就是在主线程中的,因此一定会在主线程中执行。获取main queue就可以了,不需要我们创建,获取方式通

过调用方法dispatchgetmain_queue来获取。

4..h文件中的变量,外部可以直接访问么?(注意是变量,不是property)?

直接访问的话是没法访问到的,可以通过runtime的ivar去获取他的所有变量信息遍历就可以获取到

5. NSThread中的Runloop的作用,如何使用?

NSThread+runloop实现常驻线程 

首先常驻线程既然是常驻,那么我们可以用GCD实现一个单例来保存NSThread

+ (NSThread *)shareThread {

    static NSThread *shareThread = nil;

    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{

        shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest2) object:nil];

        [shareThread setName:@"threadTest"];

        [shareThread start];

    });

    return shareThread;

}

+ (void)threadTest

{

    @autoreleasepool {

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

        [runLoop run];}}

[self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];

- (void)test

{ NSLog(@"test:%@", [NSThread currentThread]);}

6. NSOperationQueue有哪些使用方式?

 NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。但是比 GCD 更简单易用、代码可读性也

更高。

可以添加操作之间的依赖关系,方便的控制执行顺序。

可以设定操作执行的优先级。

可以可以很方便的取消一个操作的执行。

使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

 NSOperation 子类NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。

NSOperation 实现多线程的使用步骤分为三步:

创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。

创建队列:创建 NSOperationQueue 对象。

将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。

使用子类 NSBlockOperation,并调用方法 AddExecutionBlock:的情况下,blockOperationWithBlock:方法中的操作 

和 addExecutionBlock:中的操作是在不同的线程中异步执行的。而且,这次执行结果中 blockOperationWithBlock:方法中的操

作也不是在当前线程(主线程)中执行的。从而印证了blockOperationWithBlock:中的操作也可能会在其他线程(非当前线程)

中执行。

queue addOperation:op3];

无需先创建操作,在 block 中添加操作,直接将包含操作的 block 加入到队列中。

- (void)addOperationWithBlock:(void (^)(void))block;

最大并发操作数:maxConcurrentOperationCount

NSOperation操作依赖:

- (void)addDependency:(NSOperation *)op;  添加依赖,使当前操作依赖于操作 op 的完成。

- (void)removeDependency:(NSOperation *)op;  移除依赖,取消当前操作对操作 op 的依赖。

@property (readonly, copy) NSArray<NSOperation *> *dependencies;  在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperation优先级:

/ 优先级的取值

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {

    NSOperationQueuePriorityVeryLow = -8L,

    NSOperationQueuePriorityLow = -4L,

    NSOperationQueuePriorityNormal = 0,

    NSOperationQueuePriorityHigh = 4,

    NSOperationQueuePriorityVeryHigh = 8

NSOperation 常用属性和方法:

取消操作方法

- (void)cancel;可取消操作,实质是标记 isCancelled 状态。

判断操作状态方法

- (BOOL)isFinished;判断操作是否已经结束。

- (BOOL)isCancelled;判断操作是否已经标记为取消。

- (BOOL)isExecuting;判断操作是否正在在运行。

- (BOOL)isReady;判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。

操作同步

- (void)waitUntilFinished;阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。

- (void)setCompletionBlock:(void (^)(void))block;completionBlock会在当前操作执行完毕时执行 completionBlock。

- (void)addDependency:(NSOperation *)op;添加依赖,使当前操作依赖于操作 op 的完成。

- (void)removeDependency:(NSOperation *)op;移除依赖,取消当前操作对操作 op 的依赖。

@property (readonly, copy) NSArray<NSOperation *> *dependencies;在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperationQueue常用属性和方法:

取消/暂停/恢复操作

- (void)cancelAllOperations;可以取消队列的所有操作。

- (BOOL)isSuspended;判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。

- (void)setSuspended:(BOOL)b;可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。

操作同步

- (void)waitUntilAllOperationsAreFinished;阻塞当前线程,直到队列中的操作全部执行完毕。

添加/获取操作

- (void)addOperationWithBlock:(void (^)(void))block;向队列中添加一个 NSBlockOperation 类型操作对象。

- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束

- (NSArray *)operations;当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。

- (NSUInteger)operationCount;当前队列中的操作数。

获取队列

+ (id)currentQueue;获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。

+ (id)mainQueue;获取主队列。

7. UIButton的父类是什么?UILabel呢?

UIButton是继承于UIControl,并且也遵循NSCoding的协议  

UILabel是继承于UIView,并且也遵循NSCoding的协议;

8. #define和const定义的变量,有什么区别?

 1. define是宏定义,程序在预处理阶段将用define定义的内容进行了替换。因此程序运行时,常量表中并没有用define定义的常量,系统不为它分配内存。

const 定义的常量,在程序运行时在常量表中,系统为它分配内存。

 2.define 定义的常量,预处理时只是直接进行了替换。所以编译时不能进行数据类型检验。

  const 定义的常量,在编译时进行严格的类型检验,可以避免出错。

3.define定义表达式时要注意“边缘效应”

9.  Struct 和 Class 有什么区别?

class有这几个功能struct没有的:

class可以继承,这样子类可以使用父类的特性和方法

类型转换可以在runtime的时候检查和解释一个实例的类型

一个类可以被多次引用

struct也有这样几个优势:

结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全

10. lass A 继承 class B,class B 继承 NSObject。画出完整的类图。

11. pushViewController和presentViewController的区别?

从源码可以看出来,pushViewController是作用于navigationController 的。也就是说,如果想要使用pushViewController来进行

界面的跳转,就必须保证当前页面有导航栏(navigationController )。至于presentViewController,就没有这个限制条件了,

在当前页面没有导航栏的时候,也可以使用.

还有,pushViewController是把一个视图压入栈,然后显示出来,这样可以方便查找之前的视图,能够直接退回到之前的任意一个视图。而presentViewController是直接显示一个视图,这样每次就只能退回到前面的那一个视图

而且,在一直使用pushViewController的导航之间,一旦使用了presentViewController,当前界面的导航栏

(navigationController )就会变成空的,那么pushViewController也就没办法使用了。这时,如果想要再次使用

pushViewController,就必须重新设置导航栏(navigationControlle.

12. 手写一个枚举?

typedef NS_ENUM(NSInteger, CYLSex) {

  CYLSexMan,

  CYLSexWoman

};

13. Category 中有 load 方法吗?load 方法是什么时候调用的?load 方法能继承吗?

在运行时时期, 会将 Category 中的实例方法列表, 协议列表, 属性列表添加到主类中, 并且不会对 load 方法做特殊处理, 故 load 

方法跟其它方法一样, 被插到主类中.

父类的 load 方法先调用

主类中的 load 方法先调用, 分类中的 load 方法后调用

分类之间的 load 方法调用顺序, 看文件编译的顺序

在运行时时期, 循环调用所有类的 +load 方法. 直接使用函数内存地址的方式 (*load_method)(cls, SEL_load); 而不是使用发送消

息 objc_msgSend 的方式.

14.  layoutIfNeeded和setNeedsLayout区别?

layoutIfNeeded方法一被调用,主线程会立即强制重新布局,它会从当前视图开始,一直到完成所有子视图的布局

setNeedsLayout 和layoutIfNeeded相似,唯一不同的是他不会立即强制视图重新布局,而是在下一个布局周期才会触发更新.他主

要用于多个视图布局先后更新的场景;

15. 谓词的认识?

NSPredicate类是用来定义逻辑条件约束的获取或内存中的过滤搜索

例子1:NSArray*array=@[@1,@2,@3,@4,@5,@6,@7];

NSPredicate*predicate=[NSPredicate predicateWithFormat:@"self > 2 && self < 5"];

NSArray*filterArray=[array filteredArrayUsingPredicate:predicate];

//filterArray is @[@3,@4];

例子2:

NSNumber *number = @123;

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self = 123"];

BOOL result = [predicate evaluateWithObject:number];

例子三:

NSNumber *number = @123;

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self between {123, 200}"];

BOOL result = [predicate evaluateWithObject:number];

16. Autolayout有遇到性能问题吗?

AutoLayout,一个基于约束布局,动态计算视图大小和位置的库。性能肯定比固定计算的高度慢

17. 如何通过一个view查找它所在的viewController?

在UIKit中,UIApplication、UIView、UIViewController这几个类都是直接继承自UIResponder类。

id responder = self.nextResponder;

while (![responder isKindOfClass: [UIViewController class]] && ![responder isKindOfClass: [UIWindow class]])

    {

        responder = [responder nextResponder];

    }

    if ([responder isKindOfClass: [UIViewController class]])

    {

        // responder就是view所在的控制器

        // do something

    }

18. 内存泄露的原因及查找?

内存泄露( memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露

堆积后果很严重,无论多少内存,迟早会被占光。

静态内存泄漏分析方法:

通过xcode打开项目,然后点击product-->Analyze  

静态分析方法能发现大部分的问题,但是只能是静态分析结果,有一些并不准确,还有一些动态分配内存的情形并没有进行分

动态分析方法:

分析内存泄露不能把所有的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。那就需要用到Instruments了。

具体操作是通过xcode打开项目,然后点击product-->profile  按上面操作,build成功后跳出Instruments工具,如上图右侧图所

示。选择Leaks选项

选中Leaks Checks,在Details所在栏中选择CallTree,并且在右下角勾选Invert Call Tree 和Hide System Libraries,会发现显示若

干行代码,双击即可跳转到出现内存泄漏的地方,修改即可

内存泄露原因:

在目前主要以ARC进行内存管理的开发模式,导致内存泄漏的主要原因是代码中存在循环引用

 ViewController中存在NSTimer

ViewController中的代理delegate

ViewController中Block

非OC对象内存处理

加载大图片或者多个图片

地图类

NSNotification的观察者忘记移除

ViewController的子视图对self的持有

.这个问题也是我的项目中内存泄漏的问题所在。我们有时候需要在子视图或者某个cell中点击跳转等操作,需要在子视图或cell中持有当前的ViewController对象,这样跳转之后的back键才能直接返回该页面,同时也不销毁当前ViewController。此时,你就要注意在子视图或者cell中对当前页面的持有对象不能是强引用,尽量assign或者weak,否则会造成循环引用,内存无法释放。

19. 如何扩大view的响应范围?

重写view的下面方法可以实现

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event;

的这个方法就可以实现。其实这个方法就是传个你点击的点 然后你去判断这个点是否在视图上。

@interface UIView (ChangeScope)

- (void)changeViewScope:(UIEdgeInsets)changeInsets;

@end

在这个分类的实现中:

#import "UIView+ChangeScope.h"

#import <objc/runtime.h>

@implementation UIView (ChangeScope)

static char *changeScopeKey;

- (void)setChangeScope:(NSString *)changeScope

{

    objc_setAssociatedObject(self, &changeScopeKey, changeScope, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)changeScope

{

    return objc_getAssociatedObject(self, &changeScopeKey);

}

- (void)changeViewScope:(UIEdgeInsets)changeInsets

{

    self.changeScope = NSStringFromUIEdgeInsets(changeInsets);

}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

{

    UIEdgeInsets changeInsets = UIEdgeInsetsFromString(self.changeScope);

      if (changeInsets.left != 0 || changeInsets.top != 0 || changeInsets.right != 0 || changeInsets.bottom != 0) {

          CGRect myBounds = self.bounds;

          myBounds.origin.x = myBounds.origin.x + changeInsets.left;

          myBounds.origin.y = myBounds.origin.y + changeInsets.top;

          myBounds.size.width = myBounds.size.width - changeInsets.left - changeInsets.right;

          myBounds.size.height = myBounds.size.height - changeInsets.top - changeInsets.bottom;

        return CGRectContainsPoint(myBounds, point);

      } else {

        return CGRectContainsPoint(self.bounds,point);

    }

}

@end

或者

解决方案:

重写一个Button类,这个button类继承与UIButton,

重写- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event, 作用:判断下传入过来的点在不在方法调用者的坐标系上

// 作用:判断下传入过来的点在不在方法调用者的坐标系上

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{

    CGRect bounds =CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);

    //宽高希望扩展的范围

    CGFloat widthDelta =30;

    CGFloat heightDelta =20;

    //相当于bounds 上下左右都增加了10的额外

    bounds =CGRectInset(bounds, -0.5*widthDelta, -0.5* heightDelta);//注意这里是负数,扩大了之前的bounds的范围

    //点击的点是否在这个范围

    return CGRectContainsPoint(bounds, point);

}

20.字典的实现原理?

字典通过使用- (void)setObject:(id)anObject forKey:(id)aKey;方法,用Hash表来实现key和value之间映射和存储的。

哈希概念:哈希表本质是一个数组,每一个元素称为一个箱子,而箱子里面的存放的是键值对。

哈希表(Hash Table)也叫做散列表,这是根据关键码(key vlaue)而直接进行访问的数据结构。换句话说,通过把关键码映

射到表里面的一个位置来访问记录,用来加快查找的速度。映射的函数就叫做散列函数,存放记录的数组也叫做散列表。

哈希存储过程

3.1 首先根据key计算出它的哈希值h

3.2 如果箱子的个数为n,那么key值是应该放在第(h%n)个箱子中

3.3 如果该箱子已经有了值,就使用开放寻址法或者拉链法进行解决冲突。

如果我们在使用拉链法解决哈希冲突时候,每个箱子都是一个链表,属于同一个箱子的所有键值都会排列在链表中。

相关文章

网友评论

      本文标题:iOS知识点总结(2)

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