----

2019-07-07  本文已影响0人  DeCori

第一部分

iOS基础

1、 常用关键字,retain,copy,mutablecopy,weak,assign,strong,属性的本质是什么

IOS const static extern 深度解析
1).getter=getterName,setter=setterName,设置setter与 getter的方法名
2).readwrite,readonly,设置可供访问级别
3).assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题
4).retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序(CC上有相关资料)
5).copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再 Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。
6).nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级。

因为getter/setter方法有加锁的缘故,故在别的线程来读写这个属性之前,会先执行完当前操作.

2、 沙盒目录结构,存放数据

沙盒结构:

3、 tableview的复用原理

4、 bounds和frame区别

5、 内存管理

内存分配

首先既然我们需要对内存进行管理,就需要知道内存是怎么分配的,是分配在哪里的?
在iOS中数据是存在在堆和栈中的,然而我们的内存管理管理的是堆上的内存,栈上的内存并不需要我们管理。

OC中的内存管理也就是引用计数--是计算机编程语言中的一种内存管理技术。

什么是引用计数

当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象时,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。

内存管理方式

无论是MRC(manual reference counting )还是ARC(automatic reference counting)情况下,Objective-C采用的是引用计数式的内存管理方式,这一方式的特点:

自己生成的对象,自己持有。
非自己生成的对象,自己也能持有。
不再需要自己持有对象时释放。
非自己持有的对象无法释放。
ARC自动管理引用计数

MRC

在MRC模式下,开发者自己手动管理内存,这往往要占用开发者大量的时间和精力去调试工程,基于这种情况
苹果在 2011 年的时候,在 WWDC 大会上提出了自动的引用计数(ARC)。

ARC

ARC其实也是基于引用计数,背后的原理是依赖编译器的静态分析能力,在编译时期自动在已有代码中插入合适的内存管理代码(包括 retain、release、copy、autorelease、autoreleasepool)以及在 Runtime 做一些优化。
现在的iOS开发基本都是基于ARC的,所以开发人员大部分情况都是不需要考虑内存管理的。
个别情况下如:

所有权修饰符

Objective-C编程中为了处理对象,可将变量类型定义为id类型或各种对象类型。 ARC中id类型和对象类其类型必须附加所有权修饰符。
其中有以下4种所有权修饰符:
__strong
__weak
__unsafe_unretaied
__autoreleasing

内存管理问题--循环引用

什么是循环引用?循环引用是指两个对象相互之间成为了强引用关系,引用计数都会加1,从而导致对象永远无法释放。这样就造成了内存泄漏。
最容易产生循环引用的两种情况就是Delegate和Block。所以引入了弱引用的概念来修饰对象,使用weak关键字修饰的对象,对象的引用计数不会+1,在对象释放的时候会将引用的对象置为nil,这样就避免了循环引用的产生。
原理:简单的描述就是每一个拥有弱引用的对象都有一张表来保存弱引用的指针地址,这个弱引用并不会使对象引用计数加1,当这个对象的引用计数变为0时,系统就通过这张表,找到所有的弱引用指针把它们都置成nil。
weak表是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
目前,在ARC环境下,导致内存泄漏的根本原因是代码中存在循环引用,从而导致一些内存无法释放,最终导致dealloc()方法无法被调用。
所以在ARC中做内存管理主要就是发现这些内存泄漏,关于内存泄漏Instrument为我们提供了 Allocations/Leaks 这样的工具用来检测。
iOS 内存泄漏排查方法及原因分析

总结

在 ARC 的帮助下,iOS 开发者的内存管理工作已经被大大减轻,但是我们仍然需要理解引用计数这种内存管理方式的优点和常见问题,特别要注意解决循环引用问题。对于循环引用问题有两种主要的解决办法,一是主动断开循环引用,二是使用弱引用的方式避免循环引用。对于 Core Foundation 对象,由于不在 ARC 管理之下,我们仍然需要延续以前手工管理引用计数的办法。
在调试内存问题时,Instruments 工具可以很好地对我们进行辅助,善用 Instruments 可以节省我们大量的调试时间。

6、 安全单例

当我们创建单例对象的步骤分为
1:申请内存(alloc)
2:初始化(init) 这两个步骤
为了确保单例对象的唯一性,我们应该在alloc阶段拦截。
当我们调用alloc方法时,OC内部会调用allocWithZone这个方法来申请内存,我们覆写这个方法,然后在这个方法中调用shareInstance方法返回单例对象,这样就可以达到我们的目的。
拷贝对象也是同样的原理,覆写copyWithZone方法,然后在这个方法中调用shareInstance方法返回单例对象。

#import "Single.h"
static Single *single = nil;
@implementation Single
+(instancetype) shareInstance{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        single = [[super allocWithZone:NULL] init];
    }) ;
    return single ;
}
+(id) allocWithZone:(struct _NSZone *)zone{
    return [Single shareInstance] ;
}
-(id) copyWithZone:(struct _NSZone *)zone{
    return [Single shareInstance] ;
}
@end

7、 堆栈的区别

区别

介绍

队列

8、数组跟链表的区别

第二部分

iOS比较深一点问题

1、load和initialize 方法什么时候调用

1、load方法在类或分类加载到runtime的时候调用;(APP启动后初始化的时候)
2、load方法只调用一次;
3、load方法调用顺序:父类----子类----分类
如果一个类没有实现load方法,那么就不会调用它父类的load方法.
1、initialize在类第一次收到消息的时候调用;(在首次使用类时,会生成类对象,该方法在此时调用)
2、initialize有可能调用多次;(如果子类没有实现initialize方法,则子类第一次收到消息的时候会先调用父类的initialize方法)
3、initialize调用顺序:父类----子类(如果有分类,分类中的initialize会覆盖子类,子类中的方法不会调用)

2、响应链,事件的传递

响应者对象(UIResponder)
事件的产生
事件的传递
事件响应
总结
(1)如果一个button有一部分超出父控件的范围了,这部分无法响应点击,如果想让它响应点击应该怎么做

iOS UIButton 点击无响应的解决办法
button 超出父控件的范围的响应问题解决
iOS中多层视图中,响应事件的透传
重写button父视图的- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event的方法

3、runtime,isa指针,应用场景

iOS Runtime详解
Runtime-iOS运行时基础篇
Runtime重要知识点以及使用场景
iOS Runtime探索之旅

简单总结下Runtime
Runtime消息传递
Runtime消息转发

前文介绍了进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索直到继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。那么消息转发到底是什么呢?接下来将会逐一介绍最后的三次机会。

isa指针
struct objc_object {
    Class isa;
};
typedef struct objc_object *id;
Runtime应用

Runtime简直就是做大型框架的利器。它的应用场景非常多,下面就介绍一些常见的应用场景。

4、kvo

KVO-键值观察机制,原理如下:

kvc

当一个对象调用setValue方法时,方法内部会做以下操作:
1). 检查是否存在相应的key的set方法,如果存在,就调用set方法。
2). 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
4). 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。

5、runloop,timer和perform延迟 在子线程调用

什么是RunLoop
Runloop

function loop() {
    initialize();
    do {
        var message = get_next_message();//没事件/消息或者都处理完了就沉睡
        process_message(message);//如果有事件/消息,就处理
    } while (message != quit);
}

Runloop-题
只有主线程会在创建的时候默认自动运行一个runloop,并且有timer,普通的子线程是没有这些的。这样在子线程中被调用的时候,我们的代码中的延时调用的代码就会一直等待timer的调度,但是实际上在子线程中又没有这样的timer,这样我们的代码就永远不会被调到。因此,perform使用时需要注意环境!
原来performSelector withObject afterDelay这个方法在子线程中,并不会调用SEL方法,而performSelect withObject 方法会直接调用。原因是:
1. afterDelay 方式是使用当前线程的定时器在一定时间后调用SEL,NO AfterDelay方式是直接调用SEL.
2. 主线程的runloop默认开启,子线程的runloop默认不开启,所以timer在子线程中是不会执行的,需要手动开启runloop

1.NSTimer是Foundation库提供的一个类,基于runloop实现.
可以只执行一次,也可定期反复执行(设置repeat参数).其中只执行一次时,执行后自动销毁.重复执行的,必须手动调用invalidate才能销毁.
使用时应注意:
1.)必须在有runloop的线程中使用.而根据runloop的特性,如果不处于Timer的mode时,就无法响应Timer事件.
2.)创建和销毁必须在同一线程.
3.)会对要执行方法的对象,造成强引用.容易产生循环引用.
2.performSelector是NSObject实现的成员方法.
依赖于NSTimer实现,故存在跟NSTimer的问题.
提供了功能的封装,故使用起来较方便.较NSTimer比,最大的问题是不能重复执行.
3.dispatch_after是GCD层面提供的c接口.
虽不存在NSTimer的一系列问题.该接口的最大问题就是:无法Cancel.

6、应用启动过程

App 启动速度怎么做优化与监控

7、iOS 静态库使用的配置 -ObjC

1、如果静态库中有category,那么需要添加 -ObjC 参数标识,否则可能会报:unrecognized selector sent to instance

2、参数说明(引用自:http://www.cnblogs.com/robinkey/archive/2013/05/27/3101095.html

-ObjC:加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中

-all_load:会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。

-force_load:所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载

第三部分

项目相关

项目的架构,重点和难点技术,项目设计思路

项目大了人员多了,架构怎么设计更合理
ToB和ToC产品的差异,知道这3点就够了

第四部分

简单的算法

1、 有序数组找特定元素

2、 选择排序

3.常用的排序算法

选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:

都将数组分为已排序部分和未排序部分。

选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。

冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。

插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。
/** 
 *  【选择排序】:最值出现在起始端
 *  
 *  第1趟:在n个数中找到最小(大)数与第一个数交换位置
 *  第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置
 *  重复这样的操作...依次与第三个、第四个...数交换位置
 *  第n-1趟,最终可实现数据的升序(降序)排列。
 *
 */
void selectSort(int *arr, int length) {
    for (int i = 0; i < length - 1; i++) { //趟数
        for (int j = i + 1; j < length; j++) { //比较次数
            if (arr[i] > arr[j]) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}
 
/** 
 *  【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾
 *  第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置
 *  第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置
 *   ……   ……
 *  第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置 
 */
void bublleSort(int *arr, int length) {
    for(int i = 0; i < length - 1; i++) { //趟数
        for(int j = 0; j < length - i - 1; j++) { //比较次数
            if(arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        } 
    }
}
 
/**
 *  折半查找:优化查找时间(不用遍历全部数据)
 *
 *  折半查找的原理:
 *   1> 数组必须是有序的
 *   2> 必须已知min和max(知道范围)
 *   3> 动态计算mid的值,取出mid对应的值进行比较
 *   4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
 *   5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1
 *
 */ 
 
// 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置 
int binarySearch(int *arr, int length, int key) {
    int min = 0, max = length - 1, mid;
    while (min <= max) {
        mid = (min + max) / 2; //计算中间值
        if (key > arr[mid]) {
            min = mid + 1;
        } else if (key < arr[mid]) {
            max = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

iOS冒泡排序、插入排序、选择排序、快速排序、二分查找

iOS 开发中常用的排序(冒泡、选择、快速、插入、希尔、归并、基数)算法

/**
 冒泡排序
 1. 首先将所有待排序的数字放入工作列表中;
 2. 从列表的第一个数字到倒数第二个数字,逐个检查:若某一位上的数字大于他的下一位,则将它与它的下一位交换;
 3. 重复2号步骤(倒数的数字加1。例如:第一次到倒数第二个数字,第二次到倒数第三个数字,依此类推...),直至再也不能交换。
 
 最好的时间复杂度为O(n)
 最坏的时间复杂度为O(n^2)
 平均时间复杂度为O(n^2)
 */
- (NSMutableArray *)bubbleSortWithArray:(NSArray *)array
{
    id temp;
    NSUInteger i, j;
    NSMutableArray *a = [NSMutableArray arrayWithArray:array];
    
    for (i = 0; i < a.count-1; i++) {
        for (j = 0; j < a.count-1-i; j++) {
            if (a[j] > a[j+1]) {        // 升序
                temp = a[j];
                a[j] = a[j+1];
                a[j+1] = temp;
            }
        }
    }
    
    NSLog(@"bubbleSort:%@", a);
    return a;
}

/**
 插入排序
 1.初始时,a[0]自成1个有序区,无序区为a[1..n-1]。令i=1
 2.将a[i]并入当前的有序区a[0…i-1]中形成a[0…i]的有序区间。
 3.i++并重复第二步直到i==n-1。排序完成。
 
 最好的时间复杂度为O(n)
 最坏的时间复杂度为O(n^2)
 平均时间复杂度为O(n^2)
 */
- (NSMutableArray *)insertSortWithArray:(NSArray *)array
{
    id temp;
    NSUInteger i, j;
    NSMutableArray *a = [NSMutableArray arrayWithArray:array];
    
    for (i = 1; i < a.count; i++) {
        temp = a[i];
        for (j = i; j>0 && a[j-1]>temp; j--) {
            a[j] = a[j-1];
        }
        a[j] = temp;
    }
    
    NSLog(@"insertSort:%@", a);
    return a;
}

/**
 选择排序
 1. 设数组内存放了n个待排数字,数组下标从0开始,到n-1结束;
 3. 从数组的第a[i+1]个元素开始到第n个元素,寻找最小的元素。(具体过程为:先设a[i](i=0)为最小,逐一比较,若遇到比之小的则交换);
 4. 将上一步找到的最小元素和a[i]元素交换;
 5. 如果i=n-1算法结束,否则回到第3步。
 
 最好的时间复杂度为O(n^2)
 最坏的时间复杂度为O(n^2)
 平均时间复杂度为O(n^2)
 */
- (NSMutableArray *)selectSortWithArray:(NSArray *)array
{
    id temp;
    NSUInteger min, i, j;
    NSMutableArray *a = [NSMutableArray arrayWithArray:array];
    
    for (i = 0; i < array.count; i++) {
        min = i;
        for (j = i+1; j < array.count; j++) {
            if (a[min] > array[j]) {
                min = j;
            }
        }
        if (min != i) {
            temp = a[min];
            a[min] = a[i];
            a[i] = temp;
        }
    }
    
    NSLog(@"insertSort:%@", a);
    return a;
}

/**
 快速排序
 1.先从数列中取出一个数作为基准数;
 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边;
 3.再对左右区间重复第二步,直到各区间只有一个数。
 
 最好的时间复杂度为O(nlog2n)
 最坏的时间复杂度为O(n^2)
 平均时间复杂度为O(nlog2n)
 */
- (void)quickSortWithArray:(NSMutableArray *)array
{
    [self quickSortWithArray:array left:0 right:array.count-1];
}

- (void)quickSortWithArray:(NSMutableArray *)a left:(NSUInteger)left right:(NSUInteger)right
{
    if (left >= right) {
        return;
    }
    NSUInteger i = left;
    NSUInteger j = right;
    id key = a[left];
    
    while (i < j) {
        while (i < j && key <= a[j]) {
            j--;
        }
        a[i] = a[j];
        
        while (i < j && key >= a[i]) {
            i++;
        }
        a[j] = a[i];
    }
    
    a[i] = key;
    [self quickSortWithArray:a left:left right:i-1];
    [self quickSortWithArray:a left:i+1 right:right];
}


#pragma mark - 二分查找法
/**
 *  当数据量很大适宜采用该方法。
    采用二分法查找时,数据需是排好序的。 
    基本思想:假设数据是按升序排序的,对于给定值x,从序列的中间位置开始比较,如果当前位置值等于x,则查找成功;若x小于当前位置值,则在数列的前半段 中查找;若x大于当前位置值则在数列的后半段中继续查找,直到找到为止。
时间复杂度:O(logn)
 */
- (NSInteger)BinarySearch:(NSArray *)array target:(id)key
{
    NSInteger left = 0;
    NSInteger right = [array count] - 1;
    NSInteger middle = [array count] / 2;
    
    while (right >= left) {
        middle = (right + left) / 2;
        
        if (array[middle] == key) {
            return middle;
        }
        if (array[middle] > key) {
            right = middle - 1;
        }
        else if (array[middle] < key) {
            left = middle + 1;
        }
    }
    return -1;
}

- (void)print:(NSArray *)array
{
    for (id m in array) {
        NSLog(@"%@", m);
    }
    NSLog(@"-----------------");
}

产品相关:假如给你一个新产品,你将从哪些方面来保障它的质量?

五加密算法

iOS加密详解
iOS中常见的几种加密方式总结!

iOS开发——BAT面试题合集(持续更新中)

上一篇 下一篇

猜你喜欢

热点阅读