美文网首页
iOS基础相关

iOS基础相关

作者: 6ffd6634d577 | 来源:发表于2020-03-31 17:05 被阅读0次
    1. 什么是ARC?

    引用计数机制
    在 Objective-C 中,利用 引用计数器 来进行内存管理:每个对象都有一个对应的引用计数,当这个对象被持有的时,其引用计数就会递增,当这个对象的某个持有被释放时,对象的引用计数就会递减,当这个对象的引用计数为 0 的时候,这个对象就会被释放

    每个对象都对应着一个引用计数,在内存中,通过一个 SideTable RefcountMap 来存储这个对应关系:对象的地址作为 Key,引用计数的值作为 Value

    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;
    };
    

    在 iOS 中,Objective-C中提供了 两种机制来管理对象的引用计数器,第一种是MRC(内存的手动管理),第二种是ARC(自动管理内存)

    • MRC: 需要手动添加的用来处理内存管理的引用计数的代码,与对变量的管理相关的方法有:retain,release和autorelease。retain和release方法操作的是引用计数,当引用计数为零时,便自动释放内存
    • 在ARC的内存管理中,都是由系统去管理的,编译器自动帮我们添加代码,不需要我们去做任何内存操作。
    2. block一般用那个关键字修饰,为什么?

    copy修饰,把MRC下的栈block拷贝到堆里,防止访问的时候block销毁,造成崩溃

    3. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
    • 用@property声明 NSString、NSArray、NSDictionary 经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
    • 如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

    如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

    5. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。

    “属性”(property)有两大概念:ivar(实例变量)、存取方法(access method=getter),即@property = ivar + getter + setter

    Xcode5.0 之后编译器默认帮我们实现了这些

    6. 分别写一个setter方法用于完成

    @property (nonatomic,retain)NSString *name
    @property (nonatomic,copy) NSString *name

    -(void)setName:(NSString *)name
    {
        [name retain];
        [_name release];
        _name = name;
    }
    -(void)setName:(NSString *)name
    {
        
        [_name release];
        _name = [name copy];
    }
    
    7. 说说assign vs weak,_block vs _weak的区别
    8. 请说出下面代码是否有问题,如果有问题请修改?
    @autoreleasepool {
            for (int i=0; i[largeNumber; i++) { (因识别问题,该行代码中尖括号改为方括号代替)
                Person *per = [[Person alloc] init];
                [per autorelease];
            }
        }
    

    autorelease虽然会使引用计数减一,但是它并不是立即减一,它的本质功能只是把对象放到离他最近的自动释放池里。当自动释放池销毁了,才会向自动释放池中的每一个对象发送release消息。这道题的问题就在autorelease。因为largeNumber是一个很大的数,autorelease又不能使引用计数立即减一,所以在循环结束前会造成大次数循环内存暴涨溢出

    @autoreleasepool {
            for (int i=0; i[100000; i++) { (因识别问题,该行代码中尖括号改为方括号代替)
                @autoreleasepool {
                Person *per = [[Person alloc] init];
                [per autorelease];
            }
          }
        }
    
    9. 请问下面代码是否有问题,如有问题请修改?
    @autoreleasepool {
            NSString *str = [[NSString alloc] init];
            [str retain];
            [str retain];
            str = @"jxl";
            [str release];
            [str release];
            [str release];
    }
    

    这道题跟上题一样存在内存泄露问题
    1.内存泄露
    2.指向常量区的对象不能release。

    指针变量str原本指向一块开辟的堆区空间,但是经过重新给str赋值,str的指向发生了变化,由原来指向堆区空间,到指向常量区。常量区的变量根本不需要释放,这就导致了原来开辟的堆区空间没有释放,照成内存泄露。

    10. 什么情况下使用weak关键字,相比assign有什么不同?什么情况使用weak关键字?
    什么情况使用 weak 关键字?
    1. 在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate、block。
    2. 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义IBOutlet 控件属性一般也使用 weak,使用 storyboard(xib 不行)创建的 vc,会有一个叫 _topLevelObjectsToKeepAliveFromStoryboard 的私有数组强引用所有 top level 的对象,所以这时即便 outlet 声明成 weak 也没关系。当然,也可以使用 strong。
    weak 和 assign 的不同点:
    1. weak、assign 修饰的属性指向一个对象时都不会增加对象的引用计数。然而在所指的对象被释放时,weak 属性值会被置为 nil,而assign 属性不会
    2. assign 可以用非 OC 对象以及基本类型,而 weak 必须用于 OC 对象。
    11.内存管理语义(assign、strong、weak等的区别)
    • assign
      主要用于修饰基本数据类型,例如NSInteger,CGFloat,存储在栈中,内存不用程序员管理。assign是可以修饰对象的,但是会出现野指针问题。
    • weak
      weak 修饰符指向但是并不持有该对象(弱引用),引用计数也不会加1。在 Runtime 中对该属性进行了相关操作,无需处理,可以自动销毁。weak用来修饰对象,多用于避免循环引用的地方。weak 不可以修饰基本数据类型
    • unsafe_unretained
      此特质的语义和assign相同,但是它适用于“对象类型”,该特质表达一种“非拥有关系”,当目标对象遭到推毁时,属性值不会自动清空,这一点与weak有区别。
    • copy
      copy关键字和 strong类似,copy 多用于修饰有可变类型的不可变对象上 NSString,NSArray,NSDictionary上
    • Strong
      Strong 修饰符表示指向并持有该对象(强引用),其修饰对象的引用计数会加1。该对象只要引用计数不为0就不会被销毁。当然可以通过将变量强制赋值 nil 来进行销毁。
    • retain
      retain属性的setter方法是保留新值并释放旧值,然后更新实例变量,令其指向新值。

    参考

    12. @synthesize和@dynamic分别有什么作用?
    • @property 有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize 和 @dynamic 都没写,那么默认的就是 @syntheszie var = _var;。
    • @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
    • @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
    13. @property中有哪些属性关键字?

    属性可以拥有的特质分为四类:

    1. 原子性--- nonatomic 特质
      在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。请注意,尽管没有名为“atomic”的特质(如果某属性不具备 nonatomic 特质,那它就是“原子的” ( atomic) ),但是仍然可以在属性特质中写明这一点,编译器不会报错。若是自己定义存取方法,那么就应该遵从与属性特质相符的原子性。
    2. 读/写权限---readwrite(读写)、readonly (只读)
    3. 内存管理语义---assign、strong、 weak、unsafe_unretained、copy
    4. 方法名---getter=<name> 、setter=<name>
    14. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
    • 对应基本数据类型默认关键字是
      atomic, readwrite, assign
    • 对于普通的 Objective-C 对象
      atomic, readwrite, strong
    15. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

    若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyingNSMutableCopying 协议。

    具体步骤:

    1. 需声明该类遵从 NSCopying 协议
    2. 实现 NSCopying 协议。该协议只有一个方法:
    - (id)copyWithZone:(NSZone *)zone;
    
    对于很多现有类,如NSString,NSDictionary,。。。这个方法已经实现
    
    至于如何重写带 copy 关键字的 setter这个问题,

    如果抛开本例来回答的话,如下:

    - (void)setName:(NSString *)name {
        //[_name release]; MRC
        _name = [name copy];
    }
    
    15. 如何调试BAD_ACCESS错误

    野指针:指针指向的对象已经被回收掉了.这个指针就叫做野指针

    僵尸对象 : 一个OC对象引用计数为0被释放后就变成僵尸对象了,僵尸对象的内存已经被系统回收,虽然可能该对象还存在,数据依然在内存中,但僵尸对象已经是不稳定对象了,不可以再访问或者使用,它的内存是随时可能被别的对象申请而占用

    BAD_ACCESS:野指针错误,主要的原因是,当某个对象被完全释放,也就是retainCount引用计数为0后。再去通过该对象去调用release或者访问成员变量就会发生野指针错误

    1. 重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object
    2. 通过Zombie
    3. 设置全局断点快速定位问题代码所在行
    4. Xcode 7 已经集成了BAD_ACCESS捕获功能:Address Sanitizer。 用法如下:在配置中勾选✅Enable Address Sanitizer


      image.png
    使用野指针访问僵尸对象.有的时候会出问题报错(EXC_BAD_ACCESS),有的时候不会出问题
    • 当野指针指向的僵尸对象所占用的空间还没有分配给别人的时候,这个时候其实是可以访问的.因为对象的数据还在.
    • 当野指针指向的对象所占用的空间分配给了别人的时候 这个时候访问就会出问题. 所以,你不要通过1个野指针去访问1个僵尸对象.
    16. iOS nil,Nil,NULL,NSNULL的区别

    nil      (id)0
    是OC对象的空指针,可正常调用方法(返回空值,false,零值等)

    Nil     (Class)0
    是OC类的空指针,主要运用于runtime中,Class c = Nil; 其他特性与nil一致

    NULL     (void *)0
    是C指针的空值,在OC中对非对象指针赋空值,如C指针,int *p = NULL

    NSNULL    [NSNULL null]
    是OC中的空对象,可补足NSArray,NSDictinory中不能存储nil的缺陷,在命令行输出一般为"null"

    17. 反射机制

    内省(反射)机制是面向对象语言的一个强大特性 , 检查对象自己在运行时的信息(在继承树上的位置,是否遵循特定的协议,是否可以响应特定的消息)来避免出现未识别方法等问题。
    1、获取Class对象

    // 获取Class对象
      + (Class)class; 
    
    Class对象其实本质上就是一个结构体,这个结构体中的成员变量还是自己,这种设计方式非常像链表的数据结构。
      typedef struct objc_class *Class;
      struct objc_class {
          Class isa  OBJC_ISA_AVAILABILITY;                                  
      }
    
    //实例对象获取Class对象
      [self class]
    //类对象获取Class对象
      [Person class]        
    

    2、动态的调用方法

    // 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);
    

    通过这些方法,我们可以在运行时选择创建那个实例,并动态选择调用哪个方法。

    3、检查继承关系

    // 当前对象是否这个类或其子类的实例
        - (BOOL)isKindOfClass:(Class)aClass;    
    // 当前对象是否是这个类的实例  
        - (BOOL)isMemberOfClass:(Class)aClass;  
    
    // 当前对象是否遵守这个协议
      - (BOOL)conformsToProtocol:(Protocol *)aProtocol;
    // 当前对象是否实现这个方法
      - (BOOL)respondsToSelector:(SEL)aSelector;
    
    18. 一个NSObject对象占多大的内存?
    image.png image.png

    所以我们可以很好的回答这个问题,系统分配了16个字节空间给NSObject对象,但是在64位环境下,NSObject只使用了8个字节

    19. 你知道有哪些情况会导致app崩溃,分别可以用什么方法拦截并化解?
    • unrecognized selector crash

    可以利用Runtime的消息转发机制,通过重写NSObject的forwardingTargetForSelector方法,我们就可以将无法识别的方法进行拦截并且将消息转发到安全的桩类对象

    • KVO crash

    参考KVOViewCOntroller,创建一个中间代理对象,被观察对象dealloc之前,可以通过delegate自动将与自己有关的KVO关系都注销掉,避免了KVO的被观察者dealloc时仍然注册着KVO导致的crash

    • NSNotification crash

    NSNotification Crash的防护原理很简单, 利用method swizzling hook NSObject的dealloc函数,再对象真正dealloc之前先调用一下removeObserver:即可。
    同时hookNSNotificationCenteraddObserver函数,在其添加observer的时候,对observer动态添加标记flag。这样在observer dealloc的时候,就可以通过flag标记来判断其是否有必要调用removeObserver函数了。

    • NSTimer的crash

    参考NSTimer防止循环应用的方法

    • Container crash(数组越界NSRangeException,插nil等)
    • NSString crash (字符串操作的crash)

    NSArray/NSMutableArray/NSDictionary/NSMutableDictionary/NSCache的一些常用的会导致崩溃的API进行method swizzling,然后在swizzle的新方法中加入一些条件限制和判断,从而让这些API变的安全,比如AvoidCrash就是给各个系统类的添加分类实现method swizzling

    • UI not on Main Thread Crash (非主线程刷UI)
    • 野指针、僵尸对象

    AvoidCrash
    参考文章

    20. [self class] 与 [super class]

    下面代码输出什么?

     @implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self)
        {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
    return self;
    }
    @end
    

    self和super的区别:

    self 是类的一个隐藏参数,每个方法的实现的第一个参数即为self。
    super 并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。

    在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend

     id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
     
     struct objc_super {
         id receiver;
         Class cls; // the class to search
      }
    

    在objc_msgSendSuper方法中,第一个参数是一个 objc_super 的结构体,这个结构体里面有两个变量,一个是接收消息的 receiver,一个是 当前类的父类 super_class

    objc_msgSendSuper 的工作原理:
    从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用这个selector。注意,最后的调用者是objc->receiver

    objc_super->receiver = self
    
    21. isKindOfClass 与 isMemberOfClass

    下面代码输出什么?

     @interface Sark : NSObject
     @end
    
     @implementation Sark
     @end
    
     int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
        BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
    
       NSLog(@"%d %d %d %d", res1, res2, res3, res4);
    }
    return 0;
    }
    

    先来分析一下源码

    + (Class)class {
        return self;
    }
    
    - (Class)class {
        return object_getClass(self);
    }
    
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    inline Class 
    objc_object::getIsa() 
    {
        if (isTaggedPointer()) {
            uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
            return objc_tag_classes[slot];
        }
        return ISA();
    }
    
    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
        return (Class)(isa.bits & ISA_MASK);
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    image.png

    第一行res1输出应该为YES
    第二行res2输出NO
    第三行的res3输出为NO
    第四行res4输出NO

    Root class(meta) 的 superclass 就是 Root class(class),也就是NSObject本身。所以第二次循环相等,于是第一行res1输出应该为YES。

    22. Objective-C 如何实现多重继承?

    Object-c的类没有多继承,只支持单继承,如果要实现多继承的话,可使用如下几种方式间接实现
    1. 通过组合实现
    A和B组合,作为C类的组件
    2. 通过协议实现
    C类实现A和B类的协议方法
    3. 消息转发实现
    forwardInvocation:方法

    23. LLDB常用的调试命令有哪些?

    po:print object的缩写,表示显示对象的文本描述,如果对象不存在则打印nil。
    p:可以用来打印基本数据类型。
    call:执行一段代码 如:call NSLog(@"%@", @"yang")
    bt:打印当前线程堆栈信息 (bt all打印所有线程堆栈信息)
    expr:动态执行指定表达式
    image:常用来寻找栈地址对应代码位置 如:image lookup --address 0xxxx
    breakpoint:断点操作

    相关文章

      网友评论

          本文标题:iOS基础相关

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