美文网首页
iOS 基础学习

iOS 基础学习

作者: 桀骜不驯的搬砖者 | 来源:发表于2019-03-18 12:30 被阅读0次
    首先感谢这些博主:我这里只是学习和摘抄

    七秒记忆的鱼儿 -- 2017年iOS面试题总结
    就叫yang -- # iOS基础 # iOS面试题一
    iOS-Interview -- 有条理性

    iOS 系统层次架构

    iOS 系统层次架构

    1、属性关键字

    #默认值:
      1.数据类型
        atomic assign readwrite
      2.对象类型
        atomic strong readwrite
    
    1). readwrite 是可读可写特性;需要生成getter方法和setter方法时
    
    2). readonly 是只读特性 只会生成getter方法 不会生成setter方法 ;不希望属性在类外改变
    
    3). assign 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;
        assign 主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。
    
    4). weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
    
    5). copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时。
    
    6). nonatomic 非原子操作,决定编译器生成的setter getter是否是原子操作,atomic表示多线程安全,一般使用nonatomic
    
    7). strong 表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。
    
    8). retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1;
    
    iOS9的几个新关键字: nonnull、nullable、null_resettable、__null_unspecified
    1. nonnull:字面意思就能知道:不能为空(用来修饰属性,或者方法的参数,方法的返回值)
    //三种使用方式都可以
     @property (nonatomic, copy, nonnull) NSString *name;
     @property (nonatomic, copy) NSString * _Nonnull name;
     @property (nonatomic, copy) NSString * __nonnull name;
    
     //不适用于assign属性,因为它是专门用来修饰指针的
    @property (nonatomic, assign) NSUInteger age;
    
     //用下面宏包裹起来的属性全部都具nonnull特征,当然,如果其中某个属性你不希望有这个特征,也可以自己定义,比如加个nullable
    //在NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END之间,定义的所有对象属性和方法默认都是nonnull
    
    //也可以在定义方法的时候使用
    //返回值和参数都不能为空
     - (nonnull NSString *)test:(nonnull NSString *)name;
     //同上
     - (NSString * _Nonnull)test1:(NSString * _Nonnull)name;
    
    2. nullable 表示可以为空。
    //三种使用方式
     @property (nonatomic, copy, nullable) NSString *name;
     @property (nonatomic, copy) NSString *_Nullable name;
     @property (nonatomic, copy) NSString *__nullable name;
    3、null_resettable: get:不能返回空, set可以为空(注意:如果使用null_resettable,
    必须重写get方法或者set方法,处理传递的值为空的情况)
    
     // 书写方式:
     @property (nonatomic, copy, null_resettable) NSString *name; 
     设置一下set或get方法
    - (void)setName:(NSString *)name
     {
        if (name == nil) {
            name = @"Jone";
         }
        _name = name;
     }
    
     - (NSString *)name
     {
         if (_name == nil) {
             _name = @"Jone";
         }
         return _name;
     }
    
    4、_Null_unspecified:不确定是否为空
    
    使用方式只有这两种:
     @property (nonatomic, strong) NSString *_Null_unspecified name; 
     @property (nonatomic, strong) NSString *__null_unspecified name; 
    
    1.1深拷贝和浅拷贝?
    浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
    深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存(生成新对象)
    
    #在非集合类对象中:
    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //深复制
    [mutableObject copy] //深复制
    [mutableObject mutableCopy] //深复制
    
    #在集合对象中:
    对 immutable 对象进行 copy,是指针复制【浅拷贝】, mutableCopy 是内容复制【深拷贝】;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。
    #但是:集合对象的内容复制仅限于对象本身,对象里面的每个元素仍然是指针复制。
    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //单层深复制
    [mutableObject copy] //单层深复制
    [mutableObject mutableCopy] //单层深复制
    #iOS 中集合对象的“深拷贝”只拷贝了一个壳,对于壳内的每个元素是浅拷贝。
    
    1.2.0 如何让自己的类用copy修饰符?
      若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,
      那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
    
    具体步骤:
      1)需声明该类遵从 NSCopying 协议
      2)实现 NSCopying 协议。该协议只有一个方法:
       - (id)copyWithZone:(NSZone *)zone;
    
    1.2 写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name
    - (void)setName:(NSString*)str
    {
       [str retain];
       [name release];
       name = str;
    }
    - (void)setName:(NSString *)str
    {
       id t = [str copy];
       [name release];
       name = t;
    }
    
    1.3 用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
    用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,
    是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,
    他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,
    应该在设置新属性值时拷贝一份。
    
    1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,
    使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
    2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,
    如果这个可变对象在外部被修改了,那么会影响该属性。
    
    #总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,
    #     可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
    
    1.4.请解释以下keywords的区别: assign vs weak, __block vs __weak
    它们都是是一个弱引用。 
    assign适用于基本数据类型,weak是适用于NSObject对象,
    assign其实也可以用来修饰对象,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,
    指针的地址还是存在的,也就是说指针并没有被置为nil。
    如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。 
    #而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。 
    
    首先__block是用来修饰一个变量,这个变量就可以在block中被修改(参考block实现原理) 
    __block:使用__block修饰的变量在block代码快中会被retain(ARC下,MRC下不会retain) 
    __weak:使用__weak修饰的变量不会在block代码块中被retain 
    同时,在ARC下,要避免block出现循环引用 __weak 
    
    1.5 synthesize和dynamic分别有什么作用
    @synthesize的作用:
        为Property指定生成要生成的成员变量名,并生成getter和setter方法。
        用途:对于只读属性,如果同时重新setter和getter方法,就需要使用synthesize来手动合成成员变量
    @dynamic的作用:
        告诉编译器,不用为指定的Property生成getter和setter。使用方式:当我们在分类中使用Property为类扩展属性时,
        编译器默认不会为此property生成getter和setter,这时就需要用dynamic告诉编译器,自己合成了
    

    2、说一下appdelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?iOS应用程序生命周期(前后台切换,应用的各种状态)详解

    image

    3.ViewController生命周期

    按照执行顺序排列:
    1. initWithCoder:通过nib文件初始化时触发。
    2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。     
    //如果不是nib初始化 上面两个换成 initWithNibName:bundle:
    3. loadView:开始加载视图控制器自带的view。
    4. viewDidLoad:视图控制器的view被加载完成。  
    5. viewWillAppear:视图控制器的view将要显示在window上。
    6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
    7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
    8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
    9. viewDidAppear:视图控制器的view已经展示到window上。 
    10.viewWillDisappear:视图控制器的view将要从window上消失。
    11.viewDidDisappear:视图控制器的view已经从window上消失。
    

    4.self 和 super

    self 是类的隐藏参数,指向当前调用方法的这个类的实例。
    super是一个Magic Keyword,它本质是一个编译器标示符,和self是指向的同一个消息接收者。
    不同的是:super会告诉编译器,调用class这个方法时,要去父类的方法,而不是本类里的。
    

    5.事件传递和响应者链条

    #响应者链条
    1.如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
    2.在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,
    则其将事件或消息传递给window对象进行处理
    3.如果window对象也不处理,则其将事件或消息传递给UIApplication对象
    4.如果UIApplication也不能处理该事件或消息,则将其丢弃
    
    #点击屏幕时是如何互动的?
    1.iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,
    2.并放入当前活动Application的事件队列中,
    3.单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,
    4.UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),
    即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。
    
    UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,
    此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:
    (该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),
    如果pointInside:withEvent:返回YES,否则继续逐级调用,直到找到touch操作发生的位置,
    这个视图也就是要找的hit-test view。
    
    #hitTest:withEvent:方法的处理流程如下:
    1、首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;
    2。若返回NO,则hitTest:withEvent:返回nil;
    3.若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,
    所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,
    即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;
    4.若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;
    如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
    
    #事件的传递和响应分两个链:
    #传递链:由系统向离用户最近的view传递。
    UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
    #响应链:由离用户最近的view向系统传递。
    initial view –> super view –> …..–> view controller –> window –> Application
    

    6.category 的作用?它为什么会覆盖掉原来的方法?

    美团--深入理解Objective-C:Category

    一、什么是分类:利用Objective-C的动态运行时分配机制,可以为现有的类添加新方法
    我们添加的实例方法,会被动态的添加到类结构里面的methodList列表里面。
    
    1).Category 的实现原理?
    
      * Category 实际上是 Category_t的结构体,在运行时,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一个方法,执行的实际上是最后一个。
    
      * Category 在刚刚编译完的时候,和原来的类是分开的,只有在程序运行起来后,通过 Runtime ,Category 和原来的类才会合并到一起。
    
    二、主要作用:
    1. 将类的实现分开到不同的文件中
    2. 对私有方法的前向引用
    3. 向对象添加非正式协议
    
    三、它为什么会覆盖掉原来的方法?
    category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,
    这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,
    它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。
    所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法
    
    四、类别和类扩展的区别。
    答:category和extensions的不同在于 后者可以添加属性。另外后者添加的方法是必须要实现的。
    
    6.1.category为什么不能添加属性?
    category 它是在运行期决议的,因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,
    这对编译型语言来说是灾难性的。
    extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 
    extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的
    @implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,
    你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
    但是category则完全不一样,它是在运行期决议的。 
    就category和extension的区别来看,我们可以推导出一个明显的事实,
    extension可以添加实例变量,而category是无法添加实例变量的。
    
    6.2. 那为什么 使用Runtime技术中的关联对象可以为类别添加属性。
    1.其原因是:关联对象都由AssociationsManager管理,
    AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。
    这相当于把所有对象的关联对象都存在一个全局map里面。
    而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),
    而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
    2.如合清理关联对象?
    runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,
    如果有,会调用_object_remove_assocations做关联对象的清理工作。
    

    Objective-C Associated Objects 的实现原理


    7.Block --Block的本质

    1) 什么是Block和它的本质是什么(block 是带自动变量的匿名函数)
       block本质上也是一个OC对象,它内部也有个isa指针
       block是封装了函数调用以及函数调用环境的OC对象
       block是封装函数及其上下文的OC对象
    2) Block 的类型:
       __NSGlobalBlock:不使用外部变量的block是全局block
       __NSStackBlock: 使用外部变量并且未进行copy操作的block是栈block
       __NSMallocBlock:对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block
        Block访问外界变量
        MRC 环境下:访问外界变量的 Block 默认存储栈中。
        ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
    
    3) 在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况?
      1.block作为函数返回值时
      2.将block赋值给__strong指针时
      3.block作为Cocoa API中方法名含有usingBlock的方法参数时
      4.block作为GCD API的方法参数时
    
    4) block 为什么用copy 修饰?
       block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。
       因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
    
    

    Block原理、Block变量截获、Block的三种形式、__block


    8.消息的传递方式

    KVC&KVO的实现原理

    #1.KVC也叫键值编码
      KVC是基于runtime机制实现的,运用 isa 指针,通过 key 间接对对象的属性进行操作
      程序优先调用setKey属性值方法-->_key -->_isKey-->如果上面列出的方法或者成员变量都不存在,  系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。
    
    #2.KVO也叫键值监听
      当某个类的属性第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,
      在这个派生类中重写基类中任何被观察属性的setter 方法。
      并修改当前 isa 指针指向这个类,从而在给被监控属性赋值时执行的是派生类的setter方法
      苹果还重写了class 方法,返回原来的类
    
    #深入 如何手动调用KVO?
      子类setter方法剖析:KVO的键值观察通知依赖于 
      NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,
      在存取数值的前后分别调用2个方法: 被观察属性发生改变之前,willChangeValueForKey:被调用,
      通知系统该 keyPath?的属性值即将变更;
      当改变发生后, didChangeValueForKey: 被调用,
      通知系统该 keyPath 的属性值已经变更;
      之后,observeValueForKeyPath:ofObject:change:context: 也会被调用。
      且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的。
    
    #通知和代理有什么区别
       通知是观察者模式,适合一对多的场景
       代理模式适合一对一的反向传值
       通知耦合度低,代理的耦合度高
    
    #block和delegate的区别
        1.delegate运行成本低,block的运行成本高
        block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,
        使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。
        就像C的函数指针,只多做了一个查表动作。
        2.delegate更适用于多个回调方法(3个以上),block则适用于1,2个回调时。
    

    9.设计模式

    1. 单例模式

    单例模式:
       简单的来说,一个单例类,在整个程序中只有一个实例,并且提供一个类方法供全局调用,
       在编译时初始化这个类,然后一直保存在内存中,到程序(APP)退出时由系统自动释放这部分内存。
    优点:
      (1)、在整个程序中只会实例化一次,所以在程序如果出了问题,可以快速的定位问题所在;
      (2)、由于在整个程序中只存在一个对象,节省了系统内存资源,提高了程序的运行效率;
    缺点:
      (1)、不能被继承,不能有子类;
      (2)、不易被重写或扩展(可以使用分类);
      (3)、同时,由于单例对象只要程序在运行中就会一直占用系统内存,该对象在闲置时并不能销毁,在闲置时也消耗了系统内存资源;
    Q: 如果单例的静态变量被置为nil了,是否内存会得到释放?
       A1: 静态变量修饰的指针保存在了全局区域,不会被释放。但是指针保存的首地址关联的对象是保存在堆区的,是会被释放的。
       A2:不会被释放的,如果想要释放的话 需要重写attempDelloc方法,在里面讲onceToke置为nil,instance置为nil
    

    2.代理模式

    代理模式:
       一种一对一的消息传递方式,由代理对象、委托者、协议三部分组成。
    Q:代理用weak还是assign ?
       A:assign是指针赋值,不对引用计数操作,使用之后如果没有置为nil,可能就会产生野指针;而weak一旦不进行使用后,永远不会使用了,就不会产生野指针。
    

    3.观察者模式

    观察者模式:
       一种一对多的消息传递方式,当一个物体发生变化时,会通知所有观察这个物体的观察者让其做出反应
    
    

    10. 内存管理

    10.1 iOS内存分区情况
    1. 栈区(Stack)
      由编译器自动分配释放,存放函数的参数,局部变量的值等
      栈是向低地址扩展的数据结构,是一块连续的内存区域
    
    2.堆区(Heap)
      由程序员分配释放
      是向高地址扩展的数据结构,是不连续的内存区域
    
    3.全局区
      全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
      未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
      程序结束后由系统释放
    
    4.常量区
      常量字符串就是放在这里的
      程序结束后由系统释放
    
    5.代码区
      存放函数体的二进制代码
    
    注:
      在 iOS 中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
      系统使用一个链表来维护所有已经分配的内存空间(系统仅仅记录,并不管理具体的内容)
      变量使用结束后,需要释放内存,OC 中是判断引用计数是否为 0,如果是就说明没有任何变量使用该空间,那么系统将其回收
      当一个 app 启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。
      而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,
      一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错)
    

    iOS内存管理(MRC、ARC)深入浅出

    1) 什么是 ARC?
      ARC全称是 Automatic Reference Counting,是Objective-C的内存管理机制。
      简单地来说,就是代码中自动加入了retain/release,原先需要手动添加的
      用来处理内存管理的引用计数的代码,可以自动地由编译器完成了。
    
    #ARC的使用是为了解决:对象retain和release匹配的问题。
      以前手动管理造成内存泄漏或者重复释放的问题将不复存在。
    
    2) 什么是 MRC?
      以前需要手动的通过retain去为对象获取内存,
      并用release释放内存。所以以前的操作称为MRC (Manual Reference Counting)。
    
    #内存管理的原则
      1.自己生成的对象,自己持有
         // 使用了alloc分配了内存,obj指向了对象,该对象本身引用计数为1,不需要retain 
        id obj = [[NSObject alloc] init]; 
        // 使用了new分配了内存,objc指向了对象,该对象本身引用计数为1,不需要retain 
        id obj = [NSObject new];
    
      2.也可持有非自己生成的对象
        // NSMutableArray通过类方法array产生了对象(并没有使用alloc、new、copy、mutableCopt来产生对象),因此该对象不属于obj自身产生的
        // 因此,需要使用retain方法让对象计数器+1,从而obj可以持有该对象(尽管该对象不是他产生的)
        id obj = [NSMutableArray array];
        [obj retain];
    
      3.不再需要自己持有对象时释放
        id obj = [NSMutableArray array];  
        [obj retain];
        // 当obj不在需要持有的对象,那么,obj应该发送release消息
        [obj release];
        // 释放了对象还进行释放,会导致奔溃
        [obj release];
        
      4.非自己持有的对象无法释放
        // 释放一个不属于自己的对象
        id obj = [NSMutableArray array]; 
        // obj没有进行retain操作而进行release操作,然后autoreleasePool也会对其进行一次release操作,导致奔溃。
        [obj release];
    
    # 针对[NSMutableArray array]方法取得的对象存在,自己却不持有对象,底层大致实现:
       + (id)object {
           //自己持有对象
           id obj = [[NSObject alloc]init];
           [obj autorelease];
           //取得的对象存在,但自己不持有对象
           return obj;
         }
    
    #注意这些原则里的一些关键词与方法的对应关系: 
     『生成』- alloc\new\copy\mutableCopy 
     『持有』- retain 
     『释放』- release 
     『废弃』- dealloc
    
    10.2 AutoreleasePool底层实现原理

    Objective-C Autorelease Pool 的实现原理

    Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,
    将创建的对象,添加到最近的autoreleasePool中,
    等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器-1.
    
    #autorelease何时释放
      1.在没有使用@autoreleasepool的情况,autorelease对象是在当前的runloop迭代结束时释放。
        每个runloop中都会创建一个 autoreleasepool 并在runloop迭代结束进行释放。
      2.如果是手动创建autoreleasepool,自己创建Pool并释放:
    

    11.UITableview的优化方法 YYKit 作者--iOS 保持界面流畅的技巧

    1、避免cell的过多重新布局,差别太大的cell之间不要选择重用。
    2、提前计算并缓存cell的高度,内容
    3、尽量减少动态添加View的操作
    4、减少所有对主线程有影响的无意义操作
    5、cell中的图片加载用异步加载,缓存等
    6、局部更新cell
    7、减少不必要的渲染时间,比如少用透明色之类的
    

    13.网络

    1. post 和 get 的区别
    1.GET在浏览器回退时是无害的,而POST会再次提交请求。
    2.GET产生的URL地址可以被Bookmark,而POST不可以。
    3.GET请求会被浏览器主动cache,而POST不会,除非手动设置。
    4.GET请求只能进行url编码,而POST支持多种编码方式。
    5.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
    6.GET请求在URL中传送的参数是有长度限制的,而POST么有。
    对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
    7.GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
    8.GET参数通过URL传递,POST放在Request body中。
    
    2. iOS即时通讯,从入门到“放弃”?

    14.多线程

    1.并行 和 并发 有什么区别?

    多线程

    15.Runtime

    关于 Runtime 的一些问题解答

    1.isa指针的理解,对象的isa指针指向哪里?isa指针有哪两种类型?
    *   isa 等价于 is kind of
        1)实例对象的 isa 指向类对象
        2)类对象的 isa 指向元类对象
        3)元类对象的 isa 指向元类的基类
    
    *   isa 有两种类型
        1)纯指针,指向内存地址
        2)NON_POINTER_ISA,除了内存地址,还存有一些其他信息
    
    2.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
      不能向编译后得到的类中增加实例变量;
      能向运行时创建的类中添加实例变量;
    
      1.因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和   
        instance_size 实例变量的内存大小已经确定,同时runtime会调用 class_setvarlayout 或 
        class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。
      2.运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 
        objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
    
    3.runtime如何实现weak变量的自动置nil?
    runtime 对注册的类, 会进行布局,
    对于 weak 修饰的对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,
    当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,
    那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
    
    4.runtime如何通过selector找到对应的IMP地址?
    每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,
    其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
    
    5.objc在向一个对象发送消息时,发生了什么?
    1.根据对象的isa指针找到类对象id,
    2.在查询类对象里面的methodLists方法函数列表,
    3.如果没有找到,再沿着superClass,寻找父类,
    4.再在父类methodLists方法列表里面查询,
    5.最终找到SEL,根据id和SEL确认IMP(指针函数),在发送消息;
    6.当最终查询不到的时候我们会报unrecognized selector错误
    
    6.objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)
    1. 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。
    例如:Person * motherInlaw = [ aPerson spouse] mother]; 
    如果spouse对象为nil,那么发送给nil的消息mother也将返回nil。
    2. 如果方法返回值为指针类型,
    其指针大小为小于或者等于sizeof(void*),float,double,long double 
    或者long long的整型标量,
    发送给nil的消息将返回0。
    3. 如果方法返回值为结构体,发送给nil的消息将返回0。
    结构体中各个字段的值将都是0。其他的结构体数据类型将不是用0填充的。
    4. 如果方法的返回值不是上述提到的几种情况,那么发送给nil的消息的返回值将是未定义的。
    
    7.什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
    消息转发机制
    答:
    objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该
    类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方
    法时,会进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常
    unrecognized selector sent to XXX 
    
    #原因:
    当发送消息的时候,我们会根据类里面的methodLists列表去查询我们要动用的SEL,
    当查询不到的时候,我们会一直沿着父类查询,
    当最终查询不到的时候我们会报unrecognized selector错误
    #处理机制:会有三次机会处理
    #第一次机会:
    当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel
    动态添加一个新方法并执行的机会。
    #第二次机会:
    或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector
    这个方法是系统提供的一个将 SEL 转给其他对象的机会
    #第三次机会:
    这一步是Runtime最后一次给你挽救的机会。
    1.首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。
    2.如果-methodSignatureForSelector:返回nil,
    Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。
    3.如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象
    并发送-forwardInvocation:消息给目标对象。
    

    16.RunLoop -- YYKit 大神对RunLoop的理解sunnyxx 大神对RunLoop 的理解 -- 视频

    1.runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
    RunLoop:RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息。
    一般来说:一个线程一次只能执行一个任务,执行完成后线程就会退出。RunLoop是让线程能随时处理事件但并不退出一种机制。
    
    runloop和线程的关系:线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里
    主线程默认是开启一个runloop。也就是这个runloop才能保证我们程序正常的运行。
    子线程是默认没有开始runloop的
    
    2.runloop的mode是用来做什么的?有几种mode?
    model:是runloop里面的模式,不同的模式下的runloop处理的事件和消息有一定的差别。
    系统默认注册了5个Mode:
    (1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    (2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    (3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
    (4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
    (5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
    注意iOS 对以上5中model进行了封装
    NSDefaultRunLoopMode;
    NSRunLoopCommonModes
    
    3.为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
    NSTimer 对象是在 `NSDefaultRunLoopMode`下面调用消息的,
    但是当我们滑动scrollview的时候,`NSDefaultRunLoopMode`模式就自动切换到`UITrackingRunLoopMode`模式下面,
    却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用NSTimer的消息,
    我们可以把nsrunloop的模式更改为`NSRunLoopCommonModes`
    
    4.AFNetworking 中如何运用 Runloop?

    AFURLConnectionOperation 这个类是基于 NSURLConnection 构建的,其希望能在后台线程接收
    Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop:

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    
    + (NSThread *)networkRequestThread {
        static NSThread *_networkRequestThread = nil;
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
            [_networkRequestThread start];
        });
        return _networkRequestThread;
    }
    

    RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。

    - (void)start {
        [self.lock lock];
        if ([self isCancelled]) {
            [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        } else if ([self isReady]) {
            self.state = AFOperationExecutingState;
            [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        }
        [self.lock unlock];
    }
    
    

    当需要这个后台线程执行任务时,AFNetworking 通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中。


    说一下你对架构的理解? 技术架构如何搭建?

    对于iOS架构的认识过程
    iOS 从0到1搭建高可用App框架


    支付宝支付流程?

    相关文章

      网友评论

          本文标题:iOS 基础学习

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