知识总结

377 阅读23分钟

#资源: runloop: hit-alibaba.github.io/interview/i…

GCD:joeleee.github.io/2017/02/21/…

字节跳动的博客:mp.weixin.qq.com/s/Drmmx5Jtj… 基础准备

项目准备基础大纲.jpg

计算机基础

1.什么是宏

宏是一种语义替换,根据预定义的规则将特定的输入转化成特定的输出

2.宏怎么工作

宏在预编译阶段,编译器将宏展开,进行代码替换。

3.定义了重复的宏会怎么样

4.宏具体怎么定义

  • 定义对象
#define Height_Tabbar          (Height_StatusBar >20? 83:49)
- 定义函数 
#define sendMessage(msg) \
({\
dispatch_async(dispatch_get_main_queue(), ^{\
   NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];\
   [notificationCenter postNotificationName:SendNotification object:nil userInfo:@{@"msg":msg}];\
   });\
})

//使用
sendMessage(@"发个消息试试");

//有返回的宏函数定义
#define getSum(a,b) \
({\
(a+b);\
})

总结:定义函数,在除最后一行的后面加,在整个方法外面加().

  • 运算符# 把出现在#后面的转化成字符串。 例如 :#define demo1(n) "123"#n NSLog(@"%s",demo1(4,5,6)); 打印结果为123456

  • 运算符##

拼接##两侧的为一个符号。

例如:#define weakify(o) __weak typeof(o) weak##o = o;定义弱引用

项目中的用法自定义一个类

 #define WBChatCellBasicViewDefineEvent(eventName, viewClass) \
FOUNDATION_EXTERN NSString * const WBChatCellViewEvent##eventName; \
@class viewClass; \
@interface WBChatTableViewModel () \
- (BOOL)chatCellView:(viewClass *)view did##eventName:(WBChatDataRecord *)record; \
@end

#define WBChatCellBasicViewDefineImplementation(eventName) \
NSString * const WBChatCellViewEvent##eventName = @#eventName;

5.宏使用注意事项

  • 减少使用,因为增加编译时间。
  • 重复定义会使第一个无效。
  • 可以使用静态变量的不适用宏。

6.iOS系统常用的宏

  • UNAVAILABLE_ATTRIBUTE 告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable;
  • NSAssert(condition,desc);断言,如果条件不成立,则打印描述信息,并终止程序。

oc基础

1.深拷贝和浅拷贝?

深拷贝如果是拷贝一个数组,里面有对象,能修改里面的对象吗? 里面对象的地址是浅拷贝,如果需要深拷贝,需要归解档生成NSData。

2.atomic怎么线程安全,是绝对安全的吗

不是绝对安全的。atomic在setter和getter方法中都加了锁。对于atomic修饰的读写都是安全的。但是一个对象的线程安全并不只是读写安全。还可能,一个线程将修饰对象release,一个线程去写或读。 对于self.a = self.a + 1;也是不安全的。在x线程执行加号的时候,另一个Y线程读取了a的值。之后a的值更新。Y线程取到的就不是最新的值。

3.响应链?

  • www.cnblogs.com/guoshaobin/…
  • 响应链是最佳响应者的nextresponse属性构成的一条链。事件通过响应链寻找处理 事件的响应者。如果知道applecation都没有处理,则事件丢弃。
  • 获取最佳响应者,事件通过UIWindow向其子视图进行寻找。调用hittest和pointInside两个方法,递归查找最合适的view.
  • 事件查找步骤:
  1. 判断当前视图是否 userInteactionEnable = yes,alpha > 0, hidden =yes。如果是返回nil.
  2. 判断当前视图是否包含点。如果不包含,返回nil.
  3. 如果包含点,则倒序遍历subviews.如果没有subviews,返回本身。
  4. 每个subview,转换点坐标在当前subview.
  5. subview递归调用hittest.
  6. 如果找到hitview,返回hitview。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"-----%@",self.nextResponder.class);
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) return nil;
    //判断点在不在这个视图里
    if ([self pointInside:point withEvent:event]) {
        //在这个视图 遍历该视图的子视图
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            //转换坐标到子视图,self上的point点坐标转换到以subview为基准的坐标。
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            //递归调用hitTest:withEvent继续判断
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                //在这里打印self.class可以看到递归返回的顺序。
                return hitTestView;
            }
        }
        //这里就是该视图没有子视图了 点在该视图中,所以直接返回本身,上面的hitTestView就是这个。
        NSLog(@"命中的view:%@",self.class);
        return self;
    }
    //不在这个视图直接返回nil
    return nil;
}

点击一次,让hitview的父视图也相应???

3.1如何让一个特定view响应?

重写特定view的hitTest:inView的方法。返回特定的View. 也可以在特定View中重写hitTest:inView。但是可能会被先遍历到的符合条件的view截胡。

4.bounds和frame的区别?

www.jianshu.com/p/7e3ed50b3…

  • frame是视图相对于父视图坐标系统的位置信息;bounds是视图以自己为坐标的位置信息。
  • frame改变bounds,影响子视图的frame,不影响自己。改变自己的bounds,相当于改变子视图的坐标系。

  • 旋转view,frame改变,bounds不变。如果子视图没有一起旋转,那么frame改变,如果一起旋转,则frame不变。
  • 缩放view,frame的origin和size都改变,bounds的origin不变,size改变。/r
  • scrollview怎么实现的:不断改变外层视图的bounds,内部视图就会自动滚动。

5.super调用的底层实现?

  1. [super selector]执行,首先生成一个结构体 struct objc_super2{ id receiver;//实际消息接受者 Class current_class;//接受者的类对象 }
  2. 系统调用objc_messagesendsuper(objc_super2,sel)方法。第一个参数为生成的结构体,第二个参数为调用的方法。
  3. 通过superClass指向父类的方法列表,查找方法名。找到,用receiver调用。

6.selector和SEL,IMP分别是什么?

IMP是函数的指针 @selctor () 生成的是SEL类型的消息名称。 SEL是消息名称的类型。

7.协议声明属性?

协议中声明属性只有对应的setter和getter方法,没有实例变量。而setter,getter方法就是操作的成员变量,所以调用会导致崩溃。想要调用的话,需要自己声明成员变量, @synthesize cellType = _cellType;

8.子类能不能继承父类的实例变量?

如果实例变量没有公开,没有在.h中声明,则不能使用。 如果只公开了属性,也不能直接使用实例变量。

9.类的扩展是否可以写多个?

可以,可以在多个扩展中实现不同的方法。

KVO

  • KVO原理
  1. 当类的某个属性添加addobserve的时候,系统利用runtime创建该类的子类,NSKeyNotifyning_类名.
 /** 
 * 创建一个新类和元类.
 * 
 * @param superclass 这个类是新创建的类的父类,可以传入Nil去创建一个新根类.
 * @param name 这个字符串是类的名字(例:"NSObject")
 * @param extraBytes 一般传入0 
 * @return 新的类,如果返回的是Nil,那么就是这个类创建失败了(例:创建的是"NSObject"类,然而这个类已经存在了)
 */
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 
/** 
 * 注册使用`objc_allocateClassPair`方法创建的类
 * 
 * @param cls 需要注册的类(不能为Nil)
 */	
objc_registerClassPair(Class _Nonnull cls) 
  1. 将本类对象的isa指针指向生成的子类.
/*
指定isa指针
*/
object_setClass(self, subclass);
  1. 在子类重写监听属性的setter方法,setter方法中,赋值前加willChange,赋值后添加didChange方法.
  2. 执行两个方法后,内部会触发observeKeypathdidChange方法.
  • 自己写一个kvo,怎么写? 自己可以利用runtime实现,类似于Aspects原理.

  • kvo监听实例变量或不用点方法赋值都检测不到变化

  • kvo不romove为啥会崩溃?

  • kvo监听kvc能否成功? 能,因为kvo监听该属性后,会生成setter方法。而kvc的原理是如果没有找到该属性,就会去查找属性的setter方法,会找到kvo生成的方法。完成调用。

kvc原理

www.jianshu.com/writer#/not… 未命名文件-2.png

副本 未命名文件.png

block的问题:

1.block作为参数的时候是怎么引起循环引用的?用__block避免循环引用靠谱吗?

  • 因情况不同而定,主要看有没有互相持有.像一般的request的callback持有self.但是self不持有callback.所以,不加weakself的时候,callback回来还会执行里面self的东西.如果加上weakself,则不会执行callback中self的操作.
  • __block 修饰变量,在block实现中需要将变量置为nil,并且执行block,才可以避免循环引用。

2.为什么执行block之前需要先判断这个block存不存在?

  • 因为不判断,如果block不存在会崩溃,抛出读取某块地址出错.
  • 因为block的结构体中有指向具体实现的函数指针.没有这个实现,指针就会指向一块什么都没有的地址.就会引起崩溃.

3.为什么要加__strong?

为了保证self在block中生命周期是完整的.block不持有self,self有可能被释放掉.所以通过__strong对self的引用计数+1.

4.为什么系统的usingBlock和GCD的block不需要使用weakself?

因为不会造成循环引用,使用self,block会持有self.但是self不会持有block.

5.block的格式

return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };

6.block的分类?

NSGlobalBlockNSStackBlockNSMallocBlock
不捕获auto变量捕获auto变量[NSSStackBlock copy]

4种情况是自动拷贝到堆的block,属于NSMallocBlock

  1. block作为函数的返回值
  2. block使用__strong修饰
  3. cocoa API使用usingBlock做为函数参数
  4. GCD使用的block作为参数.

7.为什么__block修饰对象之后可以修改内部值?

  • 因为__block修饰的对象会生成一个回溯指针,指向其原来的地址. 可以通过这个指针去修改.
  • 用__block修饰的变量,会生成一个对象,对象结构体中包含__isa,__forwarding,__size,__var(使用值)。forwarding对指向捕获对象的地址。
  • __block修饰全局变量或static变量会报错。
  • __block变量和auto变量在栈上不会被强引用,在堆上会被强引用。

8.没有__block修饰的对象可以修改其属性吗?

可以.不能修改对象指针的指向.没有__block修饰的NSMutableArray也可以修改内部值.

9.block是什么?

  • block本质是oc对象,内部有isa指针。
  • block是包含了函数调用和函数调用环境的oc对象

10.block捕获变量

  • 局部变量,block捕获的是值。
  • static变量捕获的是指针。用__block修饰会报错。
  • 全局变量不被捕获。用__block修饰会报错。

runtime

1.oc的消息机制

oc接受到消息,执行底层objc_msgSend方法.通过三层 消息发送 :查找方法,并执行.如果没有找到.则进入到动态解析(可以说的详细点.) image.png

动态解析 :以前解析过的,直接使用. image.png

消息转发:进行消息处理.如果没有处理,直接crash. image.png

2.消息转发机制流程是什么样的?

第一步动态解析,调用resolveInstanceSelector方法,判断有没有动态实现.如果没有进入第二步.调用forwardingTargetSelector返回转发对象.如果没有返回转发对象,进入第三步,完整消息转发,生成方法的签名,然后转发消息.

3.什么是runtime,平时用在哪里?

runtime是运行时调用的底层api,实现很多动态函数. 用途: 消息转发, 分类添加实例变量, hook方法, 获取私有的成员变量.

4.计算结果 ???????

 NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
 NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
 NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]);
 NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]);
        

分析:

  • 第一个: 前:NSObject类对象 比较的是NSObject的元类和NSobjectr的类对象.基类的元类是基类的子类.所以正确.
  • 第二个: memberClass必须前后类完全一致.
  • 第三个: 前面 : MJPerson的元类 后面:MJPerson的类对象. 但是他俩之间没有子类和父类的关系.
  • 第四个: memberClass必须前后类完全一致. isKindOfClass 内部实现会对前面的调用对象进行一次【 class】操作。 结果: 1 0 0 0

class的实例方法和类方法不同

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

isMemberOfClass的类方法和实例方法的不同:类方法需要对调动者进行isa取值,实例方法需要对调用者取类对象.isKindOfClass方法一样。

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

isMemberOfClass和isKindOfClass区别在于:isMemberOfClass 调用者进行isa或类对象取值之后和传入cls参数进行等号判断。isKindOfClass进行的是循环和父类比较

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

5.为什么分类不能添加成员变量,可以添加属性?

因为类对象的结构体ivars是const修饰的一维只读数组. 属性是可读可写的二维数组.

6.class结构体包含哪些信息?

  • class结构体包含isa指针,记录了类的基本信息。
  • 父类superClass
  • 方法缓存cache,内部用哈希表(bucket)缓存使用过的方法。
  • class_rw_o结构体。class_rw_t结构体中包含方法列表,属性列表,协议列表,都是二维数组,是可读可写的,class_ro_t结构体。
  • class_ro_t结构体中包含baseMethodList、baseProtocols、ivars、baseProperties,是一维数组,只读的。记录类的初始内容。

category

1.关联对象存储在哪里

  • 关联对象存储在全局的AssociationManager管理的hashmap中,hasmapkey为disguised_object(value地址值转为unsigned long 类型,再逐位取反),value为该对象的关联对象map.关联对象map对应的为设置的key,和association结构体.association结构体存储policy和value. image.png

  • 关联对象在对象执行dealloc的时候,会被清除。

  • juejin.cn/editor/draf…

2.分类的加载步骤

runloop

###1.runloop的理解 runloop是一个对象.以NSRunloop和CFRunloop的形式存在.他可以保持一个app持续处于运行状态,处理app中的各个事件,节约cpu的耗能(一会休息,一会运行).

2.runloop和线程的关系

  • runloop和线程是一一对应的关系,以key,value的格式存放在全局字典中.
  • 当获取runloop的时候,才会创建该线程的runloop.有一个监听回调,线程结束,runloop销毁.

从代码中可以看出,如果是第一次获取,则会先获取主线程的runloop image.png

3.runloop和autoreleasepool的关系

autoreleasepool基于runloop创建.observer在loop entry状态的时候,创建autoreleasepool.在休眠之前销毁autoreleasepool,然后创建新的autoreleasepool.在exit的状态下,清除autoreleasepool.

6.source0 和source1有什么不一样?

  • source1包含一个mach_port 和一个回调,会主动唤醒线程.处理系统事件.
  • source0只包含一个回调,不能主动触发事件,先调用

7.runloop的几种状态?

entry -- beforeTimes -- beforeSources -- beforeWaiting -- afterWaiting -- exit 进入loop - >即将处理Timer ->即将处理source ->即将进入休眠 - >即将唤醒 ->退出loop

8.runloop的各种作用?

  • 保持程序的持续运行
  • 处理系统的各种事件 :手势事件,定时器事件.
  • 节约cpu的耗能,有事情的时候处理事情,没事情的时候休眠.

9.mode在runloop中的作用?

  • CFRunloopModeReff是runloop的运行模式.
  • 一个runloop有多中mode,一个mode汇总有多个source0,source1,timer,observer.
  • runloop一次只能运行一种mode.

10.mode常见mode?

UITrackingRunloopMode, kCFRunloopDefaultMode是常见的两种mode

11.为什么设置为common之后就可以执行两种mode.

被标记为common的mode,系统会将commonItems中的事件添加到common标记的mode中,从而可以同时执行两个mode中的事件.

12.runloop在app中的应用?

  • 线程保活,场景:反复在子线程重复做事情.传统每次做事情都要创建一个线程.现在可以在一个保活的子线程中做,前提是任务是非并发的情况下执行.AFN中使用了这种方式.
  • 解决NStimer在页面滚动的时候继续计时
  • 使用CADisplayLink监控卡顿

13.线程保活的步骤

  • 首先创建一个子线程
  • 添加nsport在子线程中
  • 使用runmode让线程run起来.
  • 添加操作在子线程中.
  • 在合适的时机停止runloop.

14.runloop的工作机制?

runloop就是进入某一种mode,拿出这种mode下的各种事件处理 /// 用DefaultMode启动 void CFRunLoopRun(void) { CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); }

/// 用指定的Mode启动,允许设置RunLoop超时时间 int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); }

/// RunLoop的实现 int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;

/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
    
    Boolean sourceHandledThisLoop = NO;
    int retVal = 0;
    do {

        /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
        /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
        /// 执行被加入的block
        __CFRunLoopDoBlocks(runloop, currentMode);
        
        /// 4. RunLoop 触发 Source0 (非port) 回调。
        sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
        /// 执行被加入的block
        __CFRunLoopDoBlocks(runloop, currentMode);

        /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
        if (__Source0DidDispatchPortLastTime) {
            Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
            if (hasMsg) goto handle_msg;
        }
        
        /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
        if (!sourceHandledThisLoop) {
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
        }
        
        /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
        /// • 一个基于 port 的Source 的事件。
        /// • 一个 Timer 到时间了
        /// • RunLoop 自身的超时时间到了
        /// • 被其他什么调用者手动唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
            mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
        }

        /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
        
        /// 收到消息,处理消息。
        handle_msg:

        /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
        if (msg_is_timer) {
            __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
        } 

        /// 9.2 如果有dispatch到main_queue的block,执行block。
        else if (msg_is_dispatch) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } 

        /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
        else {
            CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
            sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
            if (sourceHandledThisLoop) {
                mach_msg(reply, MACH_SEND_MSG, reply);
            }
        }
        
        /// 执行加入到Loop的block
        __CFRunLoopDoBlocks(runloop, currentMode);
        

        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入loop时参数说处理完事件就返回。
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout) {
            /// 超出传入参数标记的超时时间了
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(runloop)) {
            /// 被外部调用者强制停止了
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
            /// source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        
        /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (retVal == 0);
}

/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

}

www.jianshu.com/p/ae0118f96…

15.怎么找到runloop在底层的入口?

touchesbegan中打印东西,打断点在touchesbegan,lldb中bt查看调用栈。 可以找到入口为CFRunloopSpecific.

16.用performSelector去调用多个参数方法怎么做?

将方法包装成NSInvocation,然后invocation去invoke启动调用。

-(id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects{
    NSMethodSignature *sig = [PerformSelectorWithArguments  instanceMethodSignatureForSelector: aSelector];
    if (sig == nil) {
        NSLog(@"没有此方法");
    }
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
    invocation.selector = aSelector;
    invocation.target = self;
    //从第二位是因为去除self,sel.
    for (int i = 0; i < sig.numberOfArguments - 2; i ++) {
        NSString *str = objects[i];
        [invocation setArgument:&str atIndex:i+2 ];
    }
    [invocation invoke];
    //添加返回值
    id returnValue = nil;
    if (sig.methodReturnType) {
        [invocation getReturnValue:&returnValue];
    }
    return returnValue;
    
}

-(NSString *)testWithParam:(NSString *)p0 param1:(NSString *)p1 param2:(NSString *)p2{
    NSLog(@"p0 : %@,p1 : %@, p2:%@",p0,p1,p2);
    return @"返回值";
}

多线程

1.队列组的作用?

  // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });

dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务3-%@", [NSThread currentThread]);
        }
    });
 dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务4-%@", [NSThread currentThread]);
        }
    });
    

任务2和任务1并发执行,最后执行任务3.任务4.任务3,任务4.并发交替执行.

2.多读单写的线程安全法

实现可同时读,不可以同时读写,不可以同时写.

  • 使用pthread_rwlock读写锁可以实现
- (void)read {
    pthread_rwlock_rdlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}
  • 使用dispatch_barrier_async
 for (int i = 0; i < 10; i++) {
        [self read];
        [self read];
        [self read];
        [self write];
    }
}


- (void)read {
    dispatch_async(self.queue, ^{
        sleep(1);
        NSLog(@"read");
    });
}

- (void)write
{
    dispatch_barrier_async(self.queue, ^{
        sleep(1);
        NSLog(@"write");
    });
}

注意: -使用berrir表示此时有且只有这一条线程执行任务. -读和写在同一个队列. -队列必须是自己创建的并发队列,berrir才能发挥这个作用.如果传进去的是串行队列,则相当于没有使用berrir.

3. 全局队列和自定义并发队列的区别

  • 全局队列是唯一的,即使生成多个,也只有一个地址.
  • 并发队列即使名字不一样,地址也不同.

4.锁

自旋锁和互斥锁.

5.死锁的情况

定义: 两个任务互相等待.

  • 主线程中同步执行一个任务.
  • NSLock,多次上锁
  • 在串行队列中sync一个任务.

6.线程安全的手段

  • 加锁
  • 信号量
  • 栅栏

7.信号量

作用: - 加锁

  • 控制最大并发数 具体操作:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        //临界区,即待加锁的代码区域

        dispatch_semaphore_signal(semaphore);
    });
}

8.GCD控制网络请求顺序

www.jianshu.com/p/92eed31b7…

9.dispatch_once—的实现原理?不使用gcd实现单例?

oncetoken为0时,执行block. oncetoken为-1时,跳过block. oncetoken为其他值时,阻塞线程,等待oncetoken的值变化。

oncetoken第一次执行代码时,值为0,执行block.执行的过程中,值变为140734537148864。处于等待,阻塞线程。

具体源码分析:www.jianshu.com/p/b25535a74… 有时间再研究。

#import "Person.h"

static Person *person = nil;
@implementation Person

+(instancetype)sharedSingleton{
    if (!person) {
        @synchronized (self) {
            if (!person) {
                person = [[super allocWithZone:NULL]init];
            }
        }
    }
    return person;
}

+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    return [Person sharedSingleton];
}

-(id)copy
{
    return self;
}

-(id)mutableCopy
{
    return self;
}
@end

内存管理

苹果内存管理官方文档

1.CADisplayLink和NSTimer的区别

-执行频率不同 : CADisplayLink屏幕刷新一次,就执行一次selector.NSTimer可以直接在初始化方法设置.

  • 精确度不同: CSDisplayerLink在正常情况下都会在屏幕刷新完之后执行.NSTimer在runloop阻塞情况下,会在下一个周期执行.

2.内存的几大区域

  • 栈区 (高地址) : 局部变量
  • 堆区 : 通过alloc,malloc创建的对象
  • 数据区 :(初始化,未初始化)全局变量,(初始化,未初始化)静态变量,字符串.
  • 代码区(低地址) :编译之后的代码

3.对iOS内存管理的理解

iOS内存是通过引用计数器管理内存的.遵循谁持有,谁释放的原则.

4.ARC帮我们做了什么?

  • retain操作,引用计数 +1
  • release操作,引用计数-1

5.weak指针实现的原理

weak修饰的对象,将该对象和该指针存在一个哈希表里.key为改对象,value为改指针.当该对象的引用为0的时候,清除key和value

6.autorelease对象在什么实际会调用release?

在runloop休眠之前,会对多有对象进行pop操作 loop退出之前,会对对象进行pop操作.

7.方法中的局部变量,出了方法后会立即释放吗?

不是,如果当前loop没有退出,还会持有.

8.tagedPointer的原理和好处

  • tagedPointer用来存储NSNumber,NSString,NSDate小对象.
  • 传统NSNumber需要动态分配内存在堆中,指针中存放的是堆内存的地址.tagedPointer的指针存放的是tag+ data.将值直接存在指针中,不需要分配堆内存.指针中存不下数据时,才会动态分配堆内存.
  • 好处:当取数据的值时,直接从指针中取,节约调用开销.
  • 判断一个指针是不是tagedPointer,iOS中,最高有效位开头为1.

9.执行结果是什么?

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abcdefghijk"];
        });
    }

会崩溃 因为底层是调用set方法,set方法转化为mrc下,会先release老的_name.再将新的name copy给_name.

- (void)setName:(NSString *)name
{
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

之前的name在多线程下会被释放两次.所以会崩溃.

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    for (int i = 0; i < 1000; i++) {
        dispatch_async(queue, ^{
            self.name = [NSString stringWithFormat:@"abc"];
        });
    }

会正常执行 因为短的字符串会使用tagedPoint存储.存储在指针中.所以不会调用set方法.故不会有release和retain操作.

9.引用计数的存储方式

64位架构中,直接存储在isa的指针的. 如果不够存储,则存储在sideTable散列表中refcountMap中.

objc_object::rootRetainCount()
{
    assert(!UseGC);
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    if (bits.indexed) { //判断是否是优化过的isa指针.
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) { //引用计数不是存储在isa中,而是存储在sidetable中.
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

10.autorelease在什么时候释放?

背景知识: @autoreleasepool{

}在本质上是在开头调用autoreleasepoolobj = objc_autoreleasePoolPush(); 在结尾调用 objc_autoreleasePoolPop(autoreleasepoolobj);

push方法和pop方法做了什么? autorelease对象通过autoreleasepoolpage管理,每个autoreleasepoolpage有4096byte.存放内部成员变量,剩下的空间存放autorelease对象的地址。 page和page之间通过双向链表的形式连接在一起。通过parent和child指向来连接上一个和下一个。next指针永远指向能存放下一个autorelease对象的地址。 begin()函数是开始存储autorelease对象的开始地址。 end()函数是结束地址。 push()方法,将一个POOL_BOUNDARY放进链表中,并将该地址返回。 pop()方法,将该autoreleasepool中的所有autorelease对象进行release操作。直到POOL_BOUNDARY地址。嵌套的autoreleasepool一样的。 runloop注册两个observer,一个监听kCFRunloopEntry ,进入runloop调用push函数。 一个监听kCFRunloopBeforeWaiting | kCFRunloopExit,进入休眠之前,先进性pop,再进行push.推出runloop之前,进行pop操作。

所以,是在autoreleasepool对象调用相对应的pop函数的时候释放。在遇到自己所处的autoreleasepool的下括号的时候调用pop函数。或者由所属runloop本次休眠之前调用release.

11。计时器会引起内存泄漏,怎样避免?

CADisplayLink,NSTimer产生强引用。 计时器和self之间会产生强引用。 即使使用weakself,NSTimer内部也会强引用self. 解决办法:使用中间变量。 NSTimer - 强-> 第三方 -弱-》vc -强-》NSTimer

12 。静态分析和动态分析的区别?

静态分析是在不运行程序的情况下,通过上下文语法分析内存泄漏。比如,未使用初始化的变量。 动态分析,需要在运行程序,分析用户每一步操作的内存情况。

13,检查内存泄漏的方式?

第一种,静态分析。 第二种,采用xcode的leaks工具,进行动态分析。 第三种,采用例如MLeakFinder的第三方工具分析内存泄漏。 以MLeakFinders为例,以vc为单位,在vc销毁时,hook vc的退出方法,在hook方法执行完成之后,dispatch_after 2秒,用weakself持有self,并执行self的一个方法。如果能执行成功,则表示有内存泄漏,如果不能执行成功,说明self已经释放。

14.下面的变量存在哪????

  • (void)viewdidload{ int a = 1; int b = a; NSString *str = @"123"; }

a,b存在栈区, str :如果是taged point存在栈区tag+data,@"123"也在栈区。如果不是taged point,str存在栈区,@“123”存在常量区。

15.内存碎片什么情况下会发生?

16. MRR 和ARC的区别?

不同点: MRC:需要自己管理创建的对象内存。 ARC:系统在编译期将内存管理函数自动插入到代码中,无需自己管理.

共同点:多使用引用计数器管理内存。

17.autoreleasepool在系统中的应用场景?

可以用在想要释放对象所有权,但又不希望立马释放对象所有权时使用。例如:方法的返回值。

18.怎样去调试分析内存问题?

编译阶段,使用 Clang Static Analyzer 如果内存问题还是增长,可以使用NSZombie,

19.dealloc中引用self会有什么后果,分贝用weak和strong修饰呢?

weakself 在dealloc中修饰self,会直接导致崩溃,因为weak的赋值机制会判断,如果对象正在释放中,则直接崩溃。具体源码解释 strongself修饰self,可能会导致崩溃,野指针。因为对象可能已经释放掉,引用计数不会加1,strongself指向一个未知内存,并调用方法,导致崩溃。

架构

1.讲一下mvc和mvvm,mvp?

mvc:m是model数据模型,v是view视图层,vc是视图控制器。 vc持有view,持有model.在vc中建立model和view的连接。view中持有model,建立 view和model的映射。 优点:对apple版的mvc,对vc进行了瘦身,将vc的一部分工作放在了view中做。 缺点:view对model依赖,增加了耦合性 mvp:m是数据模型,view是视图层,p是present中介。 p持有vc,生成view和model.将view和vc进行关联,view和modle进行映射。 优点:view可重复利用。vc不重。 mvvm:m是数据模型,view是视图层,vm是持有vc,生成view和model,映射view和 model.view持有viewmodel,并监听viewmodel的model属性。 优点:耦合性低,view可以复用。

- (instancetype)initWithController:(UIViewController *)controller
{
    if (self = [super init]) {
        self.controller = controller;
        
        // 创建View
        MJAppView *appView = [[MJAppView alloc] init];
        appView.frame = CGRectMake(100, 100, 100, 150);
        appView.delegate = self;
        appView.viewModel = self;
        [controller.view addSubview:appView];
        
        // 加载模型数据
        MJApp *app = [[MJApp alloc] init];
        app.name = @"QQ";
        app.image = @"QQ";
        
        // 设置数据
        self.name = app.name;
        self.image = app.image;
    }
    return self;
}

2.mvc和mvvm的不同?

  • mvc代码少,简单,但是vc繁重。
  • mvvm将vc中的部分代码拿到vm中实现。vm实现view和model的映射和,负责view和vc建立关系。解耦model和view.

3.什么是架构?

是一种设计方案,可以是类与类,模块与模块的关系

4.mvp和mvvm的区别和相似点?

mvp和mvvm都是将vc中的工作放在一个中间类中。 mvvm会增加在view中监听vm的数据变化。

数据结构

1.数组和链表的区别?

| 数组 | 链表 | | ------ | ------ | ------ | | 静态分配内存 | 动态分配内存 | | 内存连续 | 内存不连续 | |插入删除比较低效| 插入删除比较高效| |查询时间复杂度比较低为o(1) |查询时间复杂度比较高,为o(n)| |插入,删除时间复杂度为o(n) |插入,删除时间复杂度为o(1)|

2.可变数组怎么分配内存,怎么实现扩容呢?

1.在堆内存中分配内存存储数组的容量,长度,指向数组的首地址指针。 2.在堆中开辟一块存储数据(线性表存储)。 3.如果容量不够,则开辟另一个空间,赋值之前的数据,销毁之前的空间。

3.hashmap在oc中怎么实现?

字典的实现就是用hashmap实现的。

项目

1.你们项目有多少用户?日活多少?

2.你在项目中负责什么?

1.负责我的tab。 2.负责性能优化(启动时间,包瘦身) 3.负责部分h5和需求开发工作 4.负责建立混合私有库 5.负责作业模块

3.你做的最好的是哪块?

1.是最近做的一个联动表单。实现了从两周的工作两天完成。

2.混合框架私有库 主要作用:赋予h5一些原生功能;传递给h5原生的数据;h5和原生的交互。 app://xindongfang/?apppush&

4.你觉得你那块比较擅长?

准备h5的性能优化和缓存的知识。h5像原生一样流畅,加载时间快。

第三方库

1.讲一个你熟悉的第三方库?

软性问题

1.有什么优点和缺点?

优点:自主学习能力比较强。 逻辑比较缜密,思考问题比较全面。

缺点:知识广度有待加强。爱生气(但不是跟同事)。

2. 职业规划?

1.学习一个脚本语言,写脚本。 2.在项目中,找出一个自己可以深入挖掘的点。 3.加强对编译器和链接库的了解。

3. 为什么离职?

因为公司架构调整,不适合我了。---被合并到别的组。

4. 工作流程是什么样的?

需求评审 -- 需求分配 -- 技术方案制定,评审 -- 技术方案不过关再单独审 -- 开发需求 -- 代码评审 -- 自测 --提测。

5.和别人比你的优势是什么?

1..我的bug率低。因为我比较严谨且代码强壮。

2.我会写h5,会写iOS,可以交叉补位。还对h5和原生的交互比较熟悉。

网络

1.怎样防止抓包工具抓包?AFN怎么实现的?

2.NSURLSession实现断点续传?

blog.csdn.net/u012243474/…

3. 网络七层都有啥?每层的作用是什么

物理层 - 数据链路层 - 网络层 - 传输层 - 会话层 - 表达层 - 应用层
物理层:网络传输的物理设备,将0,1的信息编码转化为电流强弱进行传输。
数据链路层:负责跨网络链接的
网络层:负责查找ip地址
传输层: 负责数据的可靠传输
会话层:
表达层:
应用层:负责端到端的链接。

什么是http?

http是超文本传输协议,是计算机世界里字体两点之间传输包含普通文本,图片,视频等超文本数据的协议,规范。服务端和服务端之间也可以传输超文本,不只在服务端和浏览器。

http的状态码有什么?

2** :请求成功,正在处理报文
3** :重定位,需要客户端重新请求
4** :客户端报错,服务端无法处理
5** :服务端报错,服务端处理请求时发生了内部错误。

http的相关字段有什么,表示什么意思?

host :指定服务器的域名
content-Type:返回的数据格式
connection:是否持久连接,一般设置为keep-alive,兼容HTTP 1.0
content-encoding:返回数据解压缩方式
content-length:返回的数据长度

get和post的区别?

http的优缺点?

缺点:明文传输,不验身份,无法保证报文完整性
优点:简单

4. Http和https的区别

https 是http之前进行SSL/TLS握手之后,再进行http通信。
提高数据传输安全性和数据完整性。
安全性通过非对称加密认证双方身份,对称加密传输数据.
数据完整性通过摘要算法对数据明文加密,服务端解密,并使用摘要算法加密,查看是否加密结果一致。

6. 抓包的原理是什么?

客户端安装charles证书中心,charles截获服务器的证书,发送自己的证书给客户端。这样,客户端将charles作为服务端进行通信。服务端将charles作为客户端进行通信。 详细介绍

6. 怎么避免抓包

使用https进行通信,同时不随意安装根证书(证书中心).

7. socket,长连接,心跳逻辑。

8. 断点续传是怎么做的

9. ssl是否了解,建联过程

等同于下方https的建立过程

10. 发了一个HTTP:post请求,主要经历几个步骤。请求包含哪些内容。

需要经历三次握手,4次挥手。 请求包含host,accept-encoding,accepcppt-type,connection

11. http的基本概念:重传机制,滑动窗口什么的

重传机制:

  1. 超时重传
    方式:当报文syn超出时间没有接收到回执报文,则认定报文数据丢失,重新传送数据。
    解决的问题:报文丢失可找回。
    问题:如果rto(超时时间)过长,则重发太慢,性能差。
  2. 快速重传
    方式:当连续收到3次相同的ack回执,则认为ack回执之前的报文丢失,触发重传。
    解决的问题:报文丢失可在3次回执之后重发,效率较高。
    问题:有可能因为网络延迟,收到重复的数据包
  3. sack
    方式:回执报文会将收到的地图发送给发送方,发送方根据地图确认哪块数据丢失。
    解决的问题:可确定具体报文丢失的段。
    问题:不能确认是
  4. D-sack
    方式:同sack
    解决的问题:解决通知发送方哪些报文重复发送了。

滑动窗口
定义:无需确认回答,可以继续发送数据的最大值。
确认字段:tcp头中的window。
由谁决定窗口的大小:由接收方决定窗口的大小。
发送方的滑动窗口组成和滑动:
由4部分组成L:发送出去并且收到应答的数据,发送出去没有收到应答的数据,未发送可以发送到数据,暂时不能发送的数据
接收方的滑动窗口组成和滑动:
由3部分组成:接受到并回应的数据,未接收到但可以接收的数据,不可以接收数据的部分。
接收端和发送端的窗口大小是相等的吗?
差不多,如果应用读取数据速度变快,接收端窗口变大,会和发送端同步新的窗口大小,但这会有时间差。

流量控制

  • 目的:让发送方根据接收方的实际接受能力发送数据量。

  • 流量控制的方式和场景: 方式:通过控制窗口的大小实现控制流量。 场景:

  • 窗口关闭的概念和隐患及解决办法: 概念:当接收方窗口大小设置为0时,发送方会关闭窗口。 隐患:当接收方再次发送非0的窗口设置时,报文丢失,发送方未接收到重启窗口通知。接收方和发送方处于互相等待状态,发生死锁。 解决办法:当发送方接受到窗口为0的设置时,开始定时器。超时向接收方发送窗口探测报文,对方在收到探测报文时,给出窗口大小。

  • 糊涂窗口综合征: 现象:当接收方来不及处理数据时,会导致发送方发送的窗口越来越小。导致发送的数据量越来越小。 解决办法: 接收方:避免发送小窗口,当窗口大小小于(mss,缓存空间/2)时,会向发送方通告窗口为0. 发送方:避免发送小数据,等到窗口大小大于一定值,以及受到之前发送数据的ack回包之后,发送数据,之前一直囤积数据。

拥塞控制

  • 目的:避免发送方的数据填满整个网络
  • 方式:发送方维护一个状态变量叫拥塞窗口。发送窗口的值是拥塞窗口和接受窗口的较小值。

12.tcp和udp的区别?

tcp是可靠传输,拥有流量控制,超时重传,拥塞控制。 udp只管传输数据,是否接受到,是否完整接受,不在考虑范围。适用于广播式的传输。

13.https的建联过程?

自己总结的流程:

  1. 服务端将自己的公钥注册在ca机构进行签名,生成证书。证书中包含发给客户端的公钥。

  2. 客户端发起client hello,发送c随机数,加密方式,TLS版本号值服务端。

  3. 服务端发送数字证书和s随机数,以及加密方式至客户端。

  4. 客户端收到数字证书,使用ca机构的公钥解密证书,得到服务端的公钥。客户端用服务端的公钥加密pre_master报文发送至服务端。

  5. 客户端将s随机数,c随机数,premaster三个随机数组成秘钥发送给服务端,告知通信加密方式。且finished

  6. 服务端使用私钥解密报文,将s随机数,c随机数,premaster三个随机数组成秘钥,改用秘钥进行通信。并发送finished

  7. 客户端发送 ack.

  8. 进入http通信阶段,双端使用会话秘钥加密报文。

简图 详图

公众号总结的流程

1. client hello,向服务端发送

a. c随机数
b. 支持的TLS版本
c. 支持的密码套件列表。

2. server hello,向客户端发送

a. 确认的TLS版本
b. s随机数
< c. 确认的密码套件列表
d. 数字证书

3. 客户端回应

a. 用CA私钥验证数字证书的真实性,取出服务器的公钥
b. 一个pre_master(用于合成会话秘钥),用服务器的公钥加密 pre_master.
c. 加密通信算法改变,表示以后会使用会话秘钥进行通信。
d. 将以上的数据做过额摘要发送给服务端,并表示会话结束

4. 服务器响应

a. 通过三个随机数,生成会话秘钥。加密通信算法改变通知,表示随后的信息使用会话秘钥加密通信。
b. 发送之前所有的内容作为摘要,发送客户端以供校验。同时表示握手结束。

14.http的演进过程

算法

1.二叉树的后序遍历(非递归)?

www.jianshu.com/p/b4785e922…

private static ArrayList<Integer> postOrder(TreeNode root) {
        if(root==null)  return new ArrayList<Integer>();

        // 存储:"根右左"的遍历顺序
        Stack<Integer> reverseRes = new Stack<Integer>();

        Stack<TreeNode> s = new Stack<TreeNode>();//辅助栈,保存待遍历的节点
        s.push(root);

        while (!s.isEmpty()) {
            TreeNode tem = s.pop();
            reverseRes.push(tem.val);//存储:"根右左"的遍历顺序,先入"根"节点

         // “右左”的遍历顺序,所以在栈(LIFO)中对应的就是:先进"左",再进"右"
            if (tem.left != null)
                s.push(tem.left);
            if (tem.right != null)
                s.push(tem.right);
        }

        ArrayList<Integer> res = new ArrayList<Integer>();//获得“根左右”的遍历序列
        while (!reverseRes.isEmpty()) {
            res.add(reverseRes.pop());
        }
        return res;
    }

2.二叉树前序遍历?

public static void preOrderIteration(TreeNode head) {
	if (head == null) {
		return;
	}
	Stack<TreeNode> stack = new Stack<>();
	stack.push(head);
	while (!stack.isEmpty()) {
		TreeNode node = stack.pop();
		System.out.print(node.value + " ");
		if (node.right != null) {
			stack.push(node.right);
		}
		if (node.left != null) {
			stack.push(node.left);
		}
	}
}

3.二叉树中序遍历?

public static void inOrderIteration(TreeNode head) {
	if (head == null) {
		return;
	}
	TreeNode cur = head;
	Stack<TreeNode> stack = new Stack<>();
	while (!stack.isEmpty() || cur != null) {
		while (cur != null) {
			stack.push(cur);
			cur = cur.left;
		}
		TreeNode node = stack.pop();
		System.out.print(node.value + " ");
		if (node.right != null) {
			cur = node.right;
		}
	}
}

//cur :储存入栈的变量
//node:存储出栈的变量
//1.保留树根节点  2.遍历节点的左子树,直至没有左节点
//3.pop出栈   4.将pop节点的有子树入栈。

4.两个链表相交,求交点?

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode pA = headA, pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}


列联表

leetcode-cn.com/problems/in…

5.链表是否有环?环的第一个节点是哪个?

leetcode-cn.com/problems/li…

  • 使用快慢指针,快指针在走两步,慢指针走1步。如果有环,总会相遇。如果没环,则会指向null.
  • 如果有环,两个指针第一次相遇.f:快指针步数,s:慢指针步数。a:环前面的节点数 b:环后面的节点数。f = 2s ; f = s + nb. ====》s = nb ; f = 2nb.
  • 则fast指针指向头,向前走a步,则慢指针走a+nb步,两者相遇刚好是环节点。
public class Solution {
   public ListNode detectCycle(ListNode head) {
       ListNode fast = head, slow = head;
       while (true) {
           if (fast == null || fast.next == null) return null;
           fast = fast.next.next;
           slow = slow.next;
           if (fast == slow) break;
       }
       fast = head;
       while (slow != fast) {
           slow = slow.next;
           fast = fast.next;
       }
       return fast;
   }
}

6.链表的反转?

leetcode-cn.com/problems/fa…

public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = NULL, *pre = head;
        while (pre != NULL) {
            ListNode* t = pre->next;
            pre->next = cur;
            cur = pre;
            pre = t;
        }
        return cur;
    }
};

7.二分法

  • 只要面试题里给出的是有序数组,想想是否可以使用二分法。

其他问题

1.静态库和动态库的区别?哪个更加节省包大小?

2.面向对象的七大原则?

3.递归与循环的区别?

4.怎样减少第三方库中的图片?

5.官方appleDelveloper的使用?