美文网首页
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