iOS最新面试题解答最全

您所在的位置:网站首页 use的单三形式 iOS最新面试题解答最全

iOS最新面试题解答最全

2023-03-31 23:01| 来源: 网络整理| 查看: 265

二十一、React Nactive与原生的交互

1、编写原生的功能类,需要实现RCTBridgeModule协议,类中包含包含RCT_EXPORT_MODULE()宏用来指定React Native中可以访问的类名。另外通过RCT_EXPORT_METHOD()宏来声明哪些方法是在ReactNative中可以调用的 2、直接在React Native中调用就可以了。 三种调用方法 1、通过Callback的方式(同步调用) callback就是在RN中写一个回调方法,等着原生代码执行完成之后回调。 2、通过Promise的方式(同步调用) 3、通过通知的方式(异步调用) 上述的代码给React Native发送了一个名为"testEventName"的通知事件,并且携带了map作为参数。 React Native的代码: React Native 对原生模块名为“testEventName”的事件进行监听。

二十二、podspec作用

podsepc文件的全称我们可以叫做 pod specification,specification是规格说明书的意思,所以顾名思义,podspec就是pod 库的规格说明书(配置文件),这个说明书描述了pod库的版本,包括了源文件的需要的地址,用什么样的文件,需要什么样的构建配置,还有许多普通的元数据像是库的名称,版本号以及描述。

二十三、load方法跟main方法哪个先调用

1、load方法是在main函数执行前执行的; 2、+load方法是在加载类和分类时系统调用,一般不手动调用,如果想要在类或分类加载时做一些事情,可以重写类或分类的+load方法。 3、类、分类的+load方法,在程序运行过程只调用一次。

二十四、iOS 有什么方案让wkwebView加载更快一些 二十五、即时通讯怎么处理心跳问题?怎么处理丢包问题呢?

TCP KeepAlive用于检测连接的死活,而心跳机制则附带一个额外的功能:检测通讯双方的存活状态。 最简单粗暴的方法是定时心跳,如每隔30秒心跳一次,15秒内没有收到心跳包则认为当前连接已失效,断开连接并进行重连 连接可靠性的判断也可以放宽,避免一次心跳超时就认为连接无效的情况,使用错误积累,只在心跳超时n次后才判定当前连接不可用 处理丢包问题:发送一个包 包首,包中,包尾都做了标记,并且有字节大小

二十六、怎么销毁单例 static NGUser *sharedInstance = nil; static dispatch_once_t onceToken; + (instancetype)sharedInstance{ dispatch_once(&onceToken, ^{ //调用父类的allocWithZone,不能使用self,避免循环引用 sharedInstance = [[super allocWithZone:NULL] init]; }); return sharedInstance; } //必须要实现的,当我们创建一个对象,alloc会给对象分配内存,init初始化数据 //alloc会调用allocWithZone,如果创建对象没有使用sharedInstance,而是使用alloc //那么alloc就会调用allocWithZone,重写类方法,调用sharedInstance使得alloc时创建的也是单例对象 +(instancetype)allocWithZone:(struct _NSZone *)zone{ return [self sharedInstance]; } //单例对象被copy -(id)copyWithZone:(nullable NSZone *)zone{ return self; } +(void)attempDealloc{ onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0.这样才能保证下次再次调用shareInstance的时候,再次创建对象. sharedInstance = nil; } 二十七、项目架构,MVVM有什么优势?RAC有什么优势?两者结合起来有什么优势?

MVVM(Model - View/ViewController - ViewModel)是对MVC的一种变形设计模式,解决ViewController代码臃肿、View和Model模块耦合严重两个主要问题。抽出ViewModel来处理ViewController的业务逻辑,分离了UI代码和业务逻辑,并且ViewModel监听model事件,一旦发生变化更新视图,很好的解决了视图与模型的依赖性。看下图

Update.jpg

MVVM优势:

低耦合:View 可以独立于Model变化和修改。 可重用性:可以把一些视图逻辑放在一个 viewModel里面,让很多 view 重用这段视图逻辑。 独立开发:开发人员可以专注于业务逻辑和数据的 viewModel开发。 可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 viewModel来进行测试。 兼容性:MVVM 可以兼容当下使用的MVC架构, 增加应用的可测试性。 ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架。结合了几种编程风格:函数式编程 和 响应式编程 ,使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。

函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。

响应式编程思想:不需要考虑调用顺序,只需要考虑结果,产生一个事件,事件传播出去,然后影响结果。 RAC与MVVM架构设计的优点 1、从上面RAC的用法可以看出,RAC的绑定,这是常用的。比如:用户信息展示界面->登录界面->登录成功->回到用户信息展示界面->展示用户信息,以往我们的做法通常是通知,也可以用协议、block什么的,一旦代码量多了过后,耦合度高,维护成本就会增加,而使用RAC的属性绑定、属性联合等一系列方法,将会有事半功倍的效果,这样就可以解决相当多的需求了。

2、MVVM搭建思路里面会涉及大量的属性绑定、事件传递来实现不同功能,运用RAC能大量简化代码,充分的降低了代码的耦合度,降低维护成本,思路更清晰。

在MVC的基础上,把C拆出一个ViewModel专门负责数据处理的事情,就是MVVM。然后,为了让View和ViewModel之间能够有比较松散的绑定关系,于是我们使用ReactiveCocoa,因为苹果本身并没有提供一个比较适合这种情况的绑定方法。iOS领域里KVO,Notification,block,delegate和target-action都可以用来做数据通信,从而来实现绑定,但都不如ReactiveCocoa提供的RACSignal来的优雅,如果不用ReactiveCocoa,绑定关系可能就做不到那么松散那么好,但并不影响它还是MVVM。 二十八、图形绘制

第一种绘图形式UIBezierPath:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。

-(void)drawRect:(CGRect)rect{ UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColorblueColor]setFill]; [pfill]; }

第二种绘图形式:使用Core Graphics实现绘制蓝色圆。

-(void)drawRect:(CGRect)rect{ CGContextRefcon=UIGraphicsGetCurrentContext(); CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100)); CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor); CGContextFillPath(con); }

第三种绘图形式:我将在UIView子类的drawLayer:inContext:方法中实现绘图任务。drawLayer:inContext:方法是一个绘制图层内容的代理方法。为了能够调用drawLayer:inContext:方法,我们需要设定图层的代理对象。但要注意,不应该将UIView对象设置为显示层的委托对象,这是因为UIView对象已经是隐式层的代理对象,再将它设置为另一个层的委托对象就会出问题。轻量级的做法是:编写负责绘图形的代理类。在MyView.h文件中声明如下代码:

@interface MyLayerDelegate:NSObject @end 然后MyView.m文件中实现接口代码: @implementation MyLayerDelegate -(void)drawLayer:(CALayer*)layerinContext:(CGContextRef)ctx{ UIGraphicsPushContext(ctx); UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColorblueColor]setFill]; [pfill]; UIGraphicsPopContext(); } @end @interface MyView(){ MyLayerDelegate*_layerDeleagete; } @end 使用该图层代理: MyView*myView=[[MyViewalloc]initWithFrame:CGRectMake(0,0,320,480)]; CALayer*myLayer=[CALayerlayer]; _layerDelegate=[[MyLayerDelegatealloc]init]; myLayer.delegate=_layerDelegate; [myView.layeraddSublayer:myLayer]; [myViewsetNeedsDisplay];//调用此方法,drawLayer:inContext:方法才会被调用。

第四种绘图形式:使用Core Graphics在drawLayer:inContext:方法中实现同样操作,代码如下:

-(void)drawLayer:(CALayer*)layinContext:(CGContextRef)con{ CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100)); CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor); CGContextFillPath(con); }

第五种绘图形式:使用UIKit实现:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100),NO,0); UIBezierPath*p=[UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColorblueColor]setFill]; [pfill]; UIImage*im=UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();

第六种绘图形式:使用Core Graphics实现:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100),NO,0); CGContextRefcon=UIGraphicsGetCurrentContext(); CGContextAddEllipseInRect(con,CGRectMake(0,0,100,100)); CGContextSetFillColorWithColor(con,[UIColorblueColor].CGColor); CGContextFillPath(con); UIImage*im=UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 二十九、自动布局,通过布局优先级来适配按钮离底部的距离10-35?

在Autolayout中每个约束都有一个优先级,优先级的范围是1 ~ 1000,默认创建的约束优先级是最高的1000。

三十、怎么实现同步修改struct原数据的值 #include struct person { char *name; int age; int weight; }; void changePersonl(struct person p); void showPersonl(struct person p); void showPersonl(struct person p) { printf("name:%s\n", p.name); printf("age:%d\ n", p.age); printf("weight:%d\n", p.weight); } void changePersonl(struct person p) { p.age = p.age - 2; p.weight = p.weight - 5; printf("in changePersonl()\n"); showPersonl(p); return; } int main() { struct person bob, sue; sue.name = "sue"; sue.age = 26; sue.weight = 50; bob.name = "bob"; bob.age = 20; bob.weight = 60; showPersonl(sue); printf("********before changeperson*************\n"); showPersonl(bob); printf("*************after changepersonl*************\n"); changePersonl(bob); showPersonl(bob); return 0; } image.png

if 想要通过调用函数修改这个结构体中原始数据的值,需要定义指针,这个指针指向结构体 每次声明person结构体的时候,需要写struct person 太繁琐了,所以可以用 typedef,typedef可以定义一个等价的量,

typedef struct person Person; struct person *sue,*bob;//两个结构体指针 Person *sue,*bob;//两个结构体指针 //两句话等价 //一开始这两个指针并没有指向任何一个结构体,因为结构体还没分配内存空间, //先给结构体分配内存空间,并确定指针指向了它中的元素 //以下可以这么写 Alloc_1D(sue,1,Person); //它为结构体person分配内存,并把指针sue与内存联系到一起

通过指针访问Person中的元素,和直接访问Person中的元素不同, 为了设定sue中的age元素可以有以下两种写法

(*sue).age=21; sue->age=21; 三十一、 block 底层原理 一、Block定义 返回值类型 (^block变量名)(形参列表) = ^(形参列表) { }; // 调用Block保存的代码 block变量名(实参); 二、Block底层实现

block的底层实现是结构体,和类的底层实现类似,都有isa指针,可以把block当成是一个对象。下面通过创建一个控制台程序,来窥探block的底层实现 block 的内存结构图:

image.png 三、Block变量截获 Block如何捕获外部变量一:基本数据类型

局部变量截获 是值截获

image.png

一:auto变量

auto变量:自动变量,离开作用域就会销毁,一般我们创建的局部变量都是auto变量 ,比如 int age = 10,系统会在默认在前面加上auto int age = 10

首先我们要搞清楚,什么是捕获,所谓捕获外部变量,意思就是在block内部,创建一个变量来存放外部变量,这就叫做捕获.先做一个小小的Test:

{ int age = 10; void (^block)(void) = ^{ NSLog(@"age is %d",age); }; age = 20; block(); }

输出的age是10 二:static变量 局部静态变量截获 是指针截获。

auto int age = 10; static int height = 20; void (^block)(void) = ^{ NSLog(@"age is %d, height is %d",age,height); }; age = 20; height = 30; block();

打印的结果是age is 10, height is 30 我们看到,对于age是捕获到内部,把外部age的值存起来,而对于height,是把外部变量的指针保存起来,所以, 我们在修改height时,会影响到block内部的值 总结:block捕获外部基本数据类型变量: auto变量是值传递;static变量是指针传递 三:全局变量 为什么全局变量不需要捕获? 因为全局变量无论哪个函数都可以访问,block内部当然也可以正常访问,所以根本无需捕获 为什么局部变量就需要捕获呢? 因为作用域的问题,我们在一个函数中定义变量,在block内部访问,本质上跨函数访问,所以需要捕获起来.

@implementation Person - (void)test{ void(^block)(void) = ^{ NSLog(@"会不会捕获self--%@",self); }; block(); } @end

答案是会捕获self,我们看看底层代码:

struct __Person__test_block_impl_0 { struct __block_impl impl; struct __Person__test_block_desc_0* Desc; Person *self; __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };

很显然block内部的确声明了一个Person *self用于保存self,既然block内部捕获了self,那就说明self肯定是一个局部变量.那问题就来了, 为什么self会是一个局部变量?它应该是一个全局变量呀?我们看一下转换后的test()方法:

static void _I_Person_test(Person * self, SEL _cmd) { void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }

我们 OC 中的test()方法时没有参数的,但是转换成 C++ 后就多了两个参数self,_cmd,其实我们每个 OC 方法都会默认有这两个参数,这也是为什么我们在每个方法中都能访问self和_cmd,而参数就是局部变量,所以block就自然而然的捕获了self.

总结:

局部变量截获 是值截获;局部静态变量截获 是指针截获;全局变量不需要捕获。 一:只要是局部变量,不管是auto 变量,还是static 变量,block都会捕获. 不同的是,对于auto 变量,block是保存值,而static 变量 是保存的指针. 二:如果是全局变量,根本不需要捕获,直接访问 本篇只是讲解了block捕获基本数据类型的auto变量

[block如何捕获外部变量(对象类型变量)

结论: 不管是 ARC 环境还是 MRC 环境,栈空间的block是不会拥有外部对象的 ; 堆空间的block会拥有外部对象,在 ARC 环境下就是强引用,在 MRC 环境下就是 retain.

结论: 当block内部访问了对象类型的auto变量时: 1:如果block是在栈上,将不会对auto变量产生强引用(不管是 MRC 或者 ARC) 2:如果block是在堆上,就说明block进行过copy操作,进行copy操作的block会自动调用block内部的__main_block_copy_0函数,__main_block_copy_0函数内部会根据auto变量的修饰符形成相应的强引用(retain)或者弱引用. 3:当block销毁时,block会自动调用内部的dispose函数,dispose函数会自动调用内部的__main_block_dispose_0释放引用的auto变量

__block 修饰的外部变量

对于用 block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改block 修饰的外部变量的值。

image.png __block int age = 10; myBlock block = ^{ NSLog(@"age = %d", age); }; age = 18; block();

输出为:

age = 18 四、Block的几种形式

block有三种类型:

全局块(_NSConcreteGlobalBlock) 栈块(_NSConcreteStackBlock) 堆块(_NSConcreteMallocBlock)

image.png

全局块存在于全局内存中, 相当于单例. 栈块存在于栈内存中, 超出其作用域则马上被销毁 堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存 1、不使用外部变量的block是全局block

NSLog(@"%@",[^{ NSLog(@"globalBlock"); } class]); 输出:NSGlobalBlock

2、使用外部变量并且未进行copy操作的block是栈block

NSInteger num = 10; NSLog(@"%@",[^{ NSLog(@"stackBlock:%zd",num); } class]); 输出:NSStackBlock

日常开发时是将block当做参数传递给方法:

- (void)testWithBlock:(void(^)(NSString *string))callback{ NSString *string = @"string"; callback(string); NSLog(@"%@",[callback class]); } NSInteger num = 10; [self testWithBlock:^(NSString *string) { NSLog(@"stackBlock:%zd",num); }]; 输出:NSGlobalBlock

3、对栈block进行copy操作,就是堆block,而对全局block进行copy,仍是全局block

比如堆1中的全局进行copy操作,即赋值: void (^globalBlock)(void) = ^{ NSLog(@"globalBlock"); }; NSLog(@"%@",[globalBlock class]); 输出:NSGlobalBlock 仍是全局block 而对2中的栈block进行赋值操作: NSInteger num = 10; void (^mallocBlock)(void) = ^{ NSLog(@"stackBlock:%zd",num); }; NSLog(@"%@",[mallocBlock class]); 输出:NSMallocBlock

对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block c、block变量与forwarding

在copy操作之后,既然block变量也被copy到堆上去了, 那么访问该变量是访问栈上的还是堆上的呢?forwarding 终于要闪亮登场了,如下图:

image.png

通过forwarding, 无论是在block中还是 block外访问block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3