iOS基础相关

2020-03-31  本文已影响0人  6ffd6634d577
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(自动管理内存)

2. block一般用那个关键字修饰,为什么?

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

3. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用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等的区别)

参考

12. @synthesize和@dynamic分别有什么作用?
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下,不显式指定任何属性关键字时,默认的关键字都有哪些?
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),有的时候不会出问题
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崩溃,分别可以用什么方法拦截并化解?

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

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

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

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

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

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:断点操作

上一篇 下一篇

猜你喜欢

热点阅读