美文网首页
2019-10-25 iOS资料复习

2019-10-25 iOS资料复习

作者: marlonxlj | 来源:发表于2019-10-25 16:42 被阅读0次

    1、分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

    答:
    ①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
    ②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
    用范围只能在自身类,而不是子类或其他地方);
    ③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
    ④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
    ⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

    最重要的还是类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
    分类方法未实现,编译器也不会报警告。
    分类方法与原类中相同会优先调用分类。

    分类结构体:

    typedef struct objc_category *Category;
    struct objc_category {
      char *category_name                          OBJC2_UNAVAILABLE; // 分类名
      char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
      struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
      struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
      struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
    }
    

    2、讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

    (1)atomic是线程安全的,但它运行效率慢,noatomic不是线程安全的它他效率高
    (2)atomic的seter getter内部实现是用了互斥锁来保证seter getter方法在多线程中的安全
    (3)nonatomic对象setter和getter方法的实现并么有加互斥锁,所以nonatomic修饰的对象是非线程安全的,同时nonatomic对象setter和getter方法也是非线程安全的,但也正因为没有互斥锁所以性能要比atomic好

    3、互斥锁与自旋锁的区别?

    互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
    自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂
    自旋锁缺点:
    1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低
    2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等

    3、被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?

    被weak修饰的对象在被释放时候会置为nil,不同于assign;

    Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

    1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
    2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
    3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    struct SideTable {
        // 保证原子操作的自旋锁
        spinlock_t slock;
        // 引用计数的 hash 表
        RefcountMap refcnts;
        // weak 引用全局 hash 表
        weak_table_t weak_table;
    }
    
    struct weak_table_t {
        // 保存了所有指向指定对象的 weak 指针
        weak_entry_t *weak_entries;
        // 存储空间
        size_t    num_entries;
        // 参与判断引用计数辅助量
        uintptr_t mask;
        // hash key 最大偏移值
        uintptr_t max_hash_displacement;
    };
    
    

    4、Copy和Strong修饰属性

    数据源为可变字符串而言,使用copy申明属性,会开辟一块新的内存空间存放值,源数据不论怎么变化,都不会影响copy属性中的值,属于深拷贝;使用strong申明属性,不会开辟新的内存空间,只会引用到源数据内存地址,因此源数据改变,则strong属性也会改变,属于浅拷贝
    当原字符串是NSString时,由于是不可变字符串,所以,不管使用strong还是copy修饰,都是指向原来的对象,copy操作只是做了一次浅拷贝。

    5、block 修饰copy strong

    _NSConcreteGlobalBlock 全局的静态 block,不会访问外部局部变量(显然包括无外部变量或者全局变量)。
    _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
    _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁
    
    • block内部没有调用外部局部变量时存放在全局区(ARC和MRC下均是)
    • block使用了外部局部变量,这种情况也正是我们平时所常用的方式。MRC:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.所以在使用Block属性时使用copy修饰。但是ARC中的Block都会在堆上的,系统会默认对Block进行copy操作
    • 用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区
    补充:一个block要使用self,会处理成在外部声明一个weak变量指向self,然而为何有时会出现在block里又声明一个strong变量指向weakSelf?

    原因:block会把写在block里的变量copy一份,如果直接在block里使用self,(self对变量默认是强引用)self对block持有,block对self持有,导致循环引用,所以这里需要声明一个弱引用weakSelf,让block引用weakSelf,打破循环引用。
    而这样会导致另外一个问题,因为weakSelf是对self的弱引用,如果这个时候控制器pop或者其他的方式引用计数为0,就会释放,如果这个block是异步调用而且调用的时候self已经释放了,这个时候weakSelf已就变成了nil。
    当控制器(也可以是其他的控件)pop回来之后(或者一些其他的原因导致释放),网络请求完成,如果这个时候需要控制器做出反映,需要strongSelf再对weakSelf强引用一下。
    但是,你可能会疑问,strongSelf对weakSelf强引用,weakSelf对self弱引用,最终不也是对self进行了强引用,会导致循环引用吗。不会的,因为strongSelf是在block里面声明的一个指针,当block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放

    6、关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?

    可以不改变源码的情况下增加实例变量。
    可与分类配合使用,为分类增加属性。(类别是不能添加成员变量的(property本质也是成员变量 = var + setter、getter),原因是因为一个类的内存大小是固定的,一个雷在load方法执行前就已经加载在内存之中,大小已固定)

    AssociationsManager

    AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。

    class AssociationsManager {
        static OSSpinLock _lock;
        static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
    public:
        AssociationsManager()   { OSSpinLockLock(&_lock); }
        ~AssociationsManager()  { OSSpinLockUnlock(&_lock); }
    
        AssociationsHashMap &associations() {
            if (_map == NULL)
                _map = new AssociationsHashMap();
            return *_map;
        }
    };
    

    销毁
    在obj dealloc时候会调用object_dispose,检查有无关联对象,有的话_object_remove_assocations删除

    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
        // 销毁对象
        objc_destructInstance(obj);    
      // 释放内存
        free(obj);
    
        return nil;
    }
    
    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            // This order is important.
            // C++ 析构
            if (cxx) object_cxxDestruct(obj);
            // 移除 Associated Object
            if (assoc) _object_remove_assocations(obj);
            // ARC 下调用实例变量的 release 方法,移除 weak 引用
            obj->clearDeallocating();
        }
    
        return obj;
    }
    

    7、KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?

    当观察某对象 A 时,KVO 机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。
    Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。

    修改

    使用方法,可实现取消系统kvo,自己触发,也就可控。

    +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        if ([key isEqualToString:@"name"]) {
            return NO;
        }else{
            return [super automaticallyNotifiesObserversForKey:key];
        }
    }
    -(void)setName:(NSString *)name{
        
        if (_name!=name) {
            
            [self willChangeValueForKey:@"name"];
            _name=name;
            [self didChangeValueForKey:@"name"];
        }
          
    }
    

    8、Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?

    在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop


    2530231-3532aca36384ac8c.jpg
    • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)。* AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)。* AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。* 上面的id next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。

    9、iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?

    判断对象类型:
    -(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
    -(BOOL) isMemberOfClass: 判断是否是这个类的实例

    判断对象or类是否有这个方法
    -(BOOL) respondsToSelector: 判读实例是否有这样方法
    +(BOOL) instancesRespondToSelector: 判断类是否有这个方法

    object_getClass:获得的是isa的指向
    self.class:当self是实例对象的时候,返回的是类对象,否则则返回自身。
    类方法class,返回的是self,所以当查找meta class时,需要对类对象调用object_getClass方法

    10、RunLoop的作用是什么?它的内部工作机制了解么?

    runloop原理

    11、哪些场景可以触发离屏渲染?

    • shouldRasterize(光栅化)
    • masks(遮罩)
    • shadows(阴影)
    • edge antialiasing(抗锯齿)
    • group opacity(不透明)
    • 复杂形状设置圆角等
    • 渐变

    12、AppDelegate如何瘦身?

    苹果的官方文档都建议应该由AppDelegate来处理这些工作:
    1.app的启动代码;
    2.响应app的状态,比如app切换到后台和前台等状态;
    3.响应外部传递给app的通知,比如说push,low-memory warnings;
    4.决定了app的状态是否应该保存或者恢复;
    5.响应不是发送给特定view或者vc,而是发送给app本身的事件;
    6.用来保存一些不属于特定vc的数据。
    第三方一些瘦身的库

    13、反射是什么?可以举出几个应用场景么?

    系统Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。

    // SEL和字符串转换
    FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
    FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
    // Class和字符串转换
    FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
    FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
    // Protocol和字符串转换
    FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
    FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);
    
    // 假设从服务器获取JSON串,通过这个JSON串获取需要创建的类为ViewController,并且调用这个类的getDataList方法。
    Class class = NSClassFromString(@"ViewController");
    ViewController *vc = [[class alloc] init];
    SEL selector = NSSelectorFromString(@"getDataList");
    [vc performSelector:selector];
    

    14、App 启动优化策略?最好结合启动流程来说

    App启动过程
    解析Info.plist
    加载相关信息,例如如闪屏
    沙箱建立、权限检查
    Mach-O加载
    如果是胖二进制文件,寻找合适当前CPU类别的部分
    加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)
    定位内部、外部指针引用,例如字符串、函数等
    执行声明为attribute((constructor))的C函数
    加载类扩展(Category)中的方法
    C++静态对象加载、调用ObjC的 +load 函数
    
    程序执行
    调用main()
    
    调用UIApplicationMain()
    
    调用applicationWillFinishLaunching
    
    main之前的优化
    
    动态库加载越多,启动越慢。
    
    ObjC类越多,启动越慢
    
    C的constructor函数越多,启动越慢
    
    C++静态对象越多,启动越慢
    
    ObjC的+load越多,启动越慢
    
    main之后的优化
    
    rootViewController及其childViewController的加载、view及其subviews的加载
    
    具体做法可以打点记录各种vc view的初始化时间。
    
    主要还是针对不同业务的优化,在我的项目中,有个后台串行的队列,去初始化各种不需要立即加载的资源,注册各种三方。
    

    15、App 无痕埋点的思路了解么?你认为理想的无痕埋点系统应该具备哪些特点?

    无痕埋点就是记录所有的事件,需要的时候去查询。
    可分为两种

    用户点击事件

    button 手势的点击,这个可以hook相关的方法,addtarget 等,去埋点,通过view获取vc名,view的层级信息(在vc的第几个subview层级)
    事件 hook的系统类 hook的系统方法
    按钮的点击 UIApplication sendAction:to: from:forEvent:
    手势操作 UIGestureRecognizer initWithTarget:action: addTarget:action:
    列表点击 UITableView和UICollectionView setDelegate:、tableView:didSelectRowAtIndexPath:、collectionView:didSelectItemAtIndexPath:等
    系统弹窗 UIAlertView setDelegate:、alertView:clickedButtonAtIndex:
    (2)页面事件拦截
    事件 hook的系统类 hook的系统方法
    页面事件 UIVIewController viewDidLoad 、viewWillAppear: 、viewDidAppear: 、viewWillDisappear:等生命周期方法:
    系统导航栏返回按钮 UINavigationController navigationBar:didPopItem:

    非点击事件

    进入,离开vc的信息,这个hook vc的相应方法也可达到。

    如何使用

    需要查找某个按钮的点击,需要在相应版本上获取按钮所在vc的subview层级信息,去上报系统中查询。

    log上报

    无痕埋点log量是很大的,实时上传是不可取的。我的方案是,后台写入log,wifi环境下达到一定大小上传(2m)。

    16、有哪些情况会导致app崩溃,分别可以用什么方法拦截并化解?

    1、NSInvalidArgumentException 异常
    向容器加入nil,引起的崩溃。hook容器添加方法,进行判断。
    https://github.com/jasenhuang/NSObjectSafe
    2、 SIGSEGV 异常
    SIGSEGV是当SEGV发生的时候,让代码终止的标识。 当去访问没有被开辟的内存或者已经被释放的内存时,就会发生这样的异常。另外,在低内存的时候,也可能会产生这样的异常。
    3、 NSRangeException 异常
    造成这个异常,就是越界异常了,在iOS中我们经常碰到的越界异常有两种,一种是数组越界,一种字符串截取越界
    4、SIGPIPE 异常
    先解释一下什么是SIGPIPE异常,通俗一点的描述是这样的:对一个端已经关闭的socket调用两次write,第二次write将会产生SIGPIPE信号,该信号默认结束进程。
    SIGABRT 异常 这是一个让程序终止的标识,会在断言、app内部、操作系统用终止方法抛出。通常发生在异步执行系统方法的时候。如CoreData、NSUserDefaults等,还有一些其他的系统多线程操作。 注意:这并不一定意味着是系统代码存在bug,代码仅仅是成了无效状态,或者异常状态。

    17、有哪些情况会导致app卡顿,分别可以用什么方法来避免?

    分cpu卡和gpu卡顿。
    主线程耗时操作
    线程爆炸
    滑动页面渲染卡顿(离屏渲染)
    图像渲染解码
    查看xcode的cpu占用。
    使用instrument 查看耗时代码。查看渲染耗时问题。

    18、App 网络层有哪些优化策略?

    Api请求过程
    当我们向服务器发送一个请求的时候,做了以下事情:
    1.DNS Lookup
    2.TCP Handshake
    3.TLS或SSL Handshake
    4.TCP/HTTP Request/Response
    1、优化DNS解析和缓存
    2、网络质量检测(根据网络质量来改变策略)
    3、提供网络服务优先级和依赖机制
    4、提供网络服务重发机制
    5、减少数据传输量
    6、优化海外网络性能

    19、了解编译的过程么?分为哪几个步骤?

    LLVM编译器
    源代码 --> 预处理器 --> 编译器 --> 汇编 --> 机器码 --> 链接 --> 可执行文件

    clang是实际的编译命令
    -x      objective-c 指定了编译的语言
    -arch   x86_64制定了编译的架构,类似还有arm7等
    -fobjc-arc 一些列-f开头的,指定了采用arc等信息。这个也就是为什么你可以对单独的一个.m文件采用非ARC编程。
    -Wno-missing-field-initializers 一系列以-W开头的,指的是编译的警告选项,通过这些你可以定制化编译选项
    -DDEBUG=1 一些列-D开头的,指的是预编译宏,通过这些宏可以实现条件编译
    -iPhoneSimulator10.1.sdk 制定了编译采用的iOS SDK版本
    -I 把编译信息写入指定的辅助文件
    -F 链接所需要的Framework
    -c ClassName.c 编译文件
    -o ClassName.o 编译产物
    

    过程如果下:

    链接需要的Framework,例如Foundation.framework,AFNetworking.framework,ALiPay.fframework
    编译xib文件
    拷贝xib,图片等资源文件到结果目录
    编译ImageAssets
    处理info.plist
    执行CocoaPod脚本
    拷贝Swift标准库
    创建.app文件和对其签名
    

    最后生成二进制文件

    21、静态链接了解么?静态库和动态库的区别?

    库:库就是写好的、现有的、成熟的程序代码的集合。
    静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
    动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用,节省内存。

    22、内存的几大区域,各自的职能分别是什么?

    • 栈区: 局部变量和方法实参* 堆区:OC中使用new方法创建的对象,被创建对象的所有成员变量保存在堆区中.* BSS段(也叫静态区):
      教科书:未被初始化的全局变量和静态变量.
      Xcode8中: 全局变量和静态变量,不管有没有被初始化,都存放在BSS段中.验证见本篇中的案例.* 常量区(也叫数据段):
      教科书: 存储已经初始化的全局变量,静态变量,常量.
      xcode8: 存储常量* 代码段: 程序的代码.

    23、MVC和MVVM的区别?MVVM和MVP的区别?

    24、

    相关文章

      网友评论

          本文标题:2019-10-25 iOS资料复习

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