iOS基础积累

1.如何保证单例的线程安全?

单例为信息共享提供了方便,但是如果多个线程访问同一个单例的时候,怎么保证只实例化了一次呢?

static JDDSingle *instance = nil;

+ (JDDSingle *)shareInstance {
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        if (instance == nil) {
            instance = [[self alloc]init];
        }
    });
    return instance;
}


/**覆盖该方法主要确保当用户通过[[Singleton alloc] init]创建对象时对象的唯一性,alloc方法会调用该方法,只不过zone参数默认为nil,因该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone:zone]
*/
+ (id)allocWithZone:(struct _NSZone *)zone {
   static dispatch_once_t token;
    dispatch_once(&token, ^{
        if (instance == nil) {
            instance = [super allocWithZone:zone];
        }
    });
    return instance;
}
//自定义初始化方法,本例中只有name这一属性
- (instancetype)init {
    self = [super init];
    if (self) {
        self.jdd = @"Singleton";
    }
    return self;
}
//覆盖该方法主要确保当用户通过copy方法产生对象时对象的唯一性
- (id)copy {
    return self;
}
//覆盖该方法主要确保当前用户通过mutableCopy方法产生对象的唯一性
- (id)mutableCopy {
    return self;
}
//自定义描述信息,用于log详细打印
- (NSString *)description {
    return [NSString stringWithFormat:@"memeory address:%p",self];
}

之前写单例,我都不会去重写allocWithZone的,面试完之后,回来测试了一下,发现用alloc init和用类方法生成的对象地址不是同一个,重写完allocWithZone,可以保证这两种方法生成的对象地址是一样的。

2.block和delegate的区别

Block和Delegate中的方法都可以理解成回调函数,当某件事情发生的时候去执行一段代码片段

Block:

       优点:是一种轻量级的回调,能都直接访问上下文,使用块的地方和块实现的地方在同一个地方,使得代码组织更加连贯。

Delegate:

相对来说是重量级的回调

        缺点:因方法的声明和实现是分开来的,代码的连贯性不是很好

                   代理很多时候需要存储一些临时数据

        优点:代理的回调函数可以是一组多个函数,在不同的时机调用不同的回调函数。

·当回调函数多于3个的时候,采用代理比较好

·使用代码块容易造成循环引用,代理不会出现该问题

·其他情况下优先考虑代码块

3.深拷贝和浅拷贝的区别。

浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。

4.声明属性时,那些关键字的作用,weak和assign的区别。

1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。

IBOutlet连出来的视图属性为什么可以被设置成weak?
    因为父控件的subViews数组已经对它有一个强引用。

不同点:
assign 可以修饰对象和基本数据类型,weak 只可以修饰对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil),不会产生野指针。

5.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

使用copy是为了让本对象的属性不受外界影响,使用copy无论我传入的是一个可变对象还是一个不可变对象,我本身持有的就是一个不可变副本。

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

为了方便理解,可以看看一下例子:

.h

@property (nonatomic, strong) NSString *name1;

.m

NSMutableString *name = [NSMutableString stringWithFormat:@"张三"];
    
    self.name1= name;
    NSLog(@"使用strong第一次得到的名字:%@,name的内存地址:%p,name1的内存地址:%p",self.name1,name,self.name1);
    
    [name appendString:@"丰"];

    NSLog(@"使用strong第二次得到的名字:%@,name的内存地址:%p,name1的内存地址:%p",self.name1,name,self.name1);

打印结果:

2017-12-12 11:47:11.021 BlockTest[7695:2404276] 使用strong第一次得到的名字:张三,name的内存地址:0x23cdc0,name1的内存地址:0x23cdc0
2017-12-12 11:47:11.023 BlockTest[7695:2404276] 使用strong第二次得到的名字:张三丰,name的内存地址:0x23cdc0,name1的内存地址:0x23cdc0

从上面的例子可以看出,在没有修改self.name1的情况下self.name1却被修改了,就好像一个人的名字怎么没有经过自己的同意就被修改了呢?我们的初衷只是想修改name,但是self.name1却被意外的修改了,这就是使用strong所不想看到的,它会破坏程序的封装性。(使用strong后,self.name1和name指向的是同一片内存,所以修改其中一个值,后面的两个值就都变了)

那么使用copy会是什么结果呢?

.h

@property (nonatomic, copy) NSString *name2;

.m

NSMutableString *name = [NSMutableString stringWithFormat:@"张三"];
    
    self.name2= name;
    NSLog(@"使用strong第一次得到的名字:%@,name的内存地址:%p,name1的内存地址:%p",self.name2,name,self.name2);
    
    [name appendString:@"丰"];

    NSLog(@"使用strong第二次得到的名字:%@,name的内存地址:%p,name1的内存地址:%p",self.name2,name,self.name2);

打印结果:

2017-12-12 11:54:20.945 BlockTest[7701:2405437] 使用strong第一次得到的名字:张三,name的内存地址:0x16690fe0,name1的内存地址:0x1668b380
2017-12-12 11:54:20.947 BlockTest[7701:2405437] 使用strong第二次得到的名字:张三,name的内存地址:0x16690fe0,name1的内存地址:0x1668b380

这个例子中使用了copy修饰,name通过copy得到了一个新的对象赋值给self.name2,这样再修改name就跟self.name2没有关系了,只有直接对self.name2进行赋值才能改变它的值,这样就保证了程序的封装性。

为什么使用copy而不是strong以上只是一个例子,还有很多情况可以试试。

6.self.xx和_xx的区别。

self.xx是对属性的访问,_xx是对实例变量的访问。

属性是实例变量加上GET,SET方法的一个整合体,主要承担外部访问的一个接口。

实例变量只能在本类中才可以访问,外部不可以访问。

看看@property声明属性后,编辑器为我们做了什么。

    #import "MyClass.h"  
    @property (copy, nonatomic) NSString *var;  
      
    //-------------------分割线上下实现相等-------------  
    #import "MyClass.h"  
    NSString *_var;  
      
    - (NSString *)var {  
         return _var;  
    }  
      
    - (void)setVar:(NSString *)var {  
        if (var != _var) {  
             _var = [var copy];  
         }  
    }  


编辑器为我们生成了对私有实例变量操作的get、set方法,当然getter,setter里面还可以有其他的额外操作。这样就可以看出通过self.var和_var访问实例变量的区别,在.m文件中可以通过_var来访问实例变量,但是getter、setter不会被调用,而来自外部的访问需要通过getter、settter。

通过属性还是直接访问实例变量?

1.由于不经过OC的方法派发,直接访问实例变量会直接访问到变量所在的内存,速度更快。2.直接访问实例变量时,不会调用其setter,这样就绕过了为相关属性所定义的内存管理语义。例如:在ARC下直接访问一个语义为copy的属性,那么并不会copy该属性,而仅仅是保留新值并释放旧值。3.KVO的触发4.通过属性来访问有助于debug与之相关的错误

一般在开发中,在对象内部读取值时,直接通过实例变量来访问(_xx),设置值时,使用setter来赋值(self.xx)。

而在初始化方法和dealloc中,应该直接访问实例变量来读写属性值。

7.category和extension的区别

·分类有名字。类扩展没有分类名字,是一种特殊的分类。

·分类只能扩展方法(属性仅仅是声明,并没有真正实现),类扩展可以扩展属性、成员变量和方法。

8.http和https的区别

很奇怪,为什么面试的时候,面试官们会问http和https的区别。我当时的回答是https相比http的安全性更高,然后面试官说怎么个高法呢。说实话,对于这一块真没用心去了解过,从http换成https之后还可以正常请求通就好了啊。然后面完下场之后,还是默默的百度了起来:

  1) https协议需要到ca申请证书,一般免费证书很少,需要交费。
  2) http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
  3) http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
  4) http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
       想要了解更多的可以自行百度,网上说这个的还挺多。

9.set和get方法

.h
@interface viewController:UIViewController{
	NSString *_name;
}
@property (nonatomic, copy)NSString *name;
-(void)setName:(NSString*)name;
-(NSString*)name;
@end

.m
-(void)setName:(NSString*)name{
	if(_name != name){
		_name = name;
	}
}
-(NSString *)name{
	return _name;
}

10.以下代码运行结果如何?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

上面的代码只会打印1,然后线程卡死。

分析:

因为dispatch_get_main_queue()得到的是一个串行队列,串行队列的特点: 一次只调度一个任务,队列中的任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行

dispatch_sync提交一个打印任务NSLog(@”2”)到主线程关联的串行队列中,主线程关联的串行队列现在有一个viewDidLoad任务,打印任务NSLog(@”2”)排在viewDidLoad后面,队列FIFO(先进先出)的原则,打印任务NSLog(@”2”);想要得到执行必须等到viewDidLoad执行完毕后才能得到执行,但是viewDidLoad想要执行完毕必须要等打印任务NSLog(@”2”)执行完毕,所以就卡死在这了。

11.retain和assign的区别 :

retain是MRC中的,同ARC的strong类似,就是指针指向地址,同时进行引用计数加1.

Assign不会使得引用计数+1。

assign同weak,不会使得引用计数+1。

assign可以用于修饰非oc对象,而weak必须用于oc对象。

Weak表明该属性定义了一种“非拥有关系”,在属性所指的对象销毁时,属性值会自动清空(nil)。

12.tableview的重用机制。

为了避免不必要的内存消耗,只显示屏幕能放得下的cell数据,在用户滑动屏幕的过程中,再加载新的数据,这就是tableview cell的重用机制。

重用机制实现了数据和显示的分离,并不会为每个要显示的数据都创建一个Cell,一般情况下只创建屏幕可显示的最大的cell个数+1,每当有一个cell从屏幕消失,就将其放到缓存池中,如果有新的cell出现,就去缓存池中取,如果缓存池中没有,再创建。

13.[NSString stringWithFormat:@“…..”]和 [[NSString alloc]initWithFormat:@“…..”]的区别。

区别:

1、initWithFormat是实例方法

只能通过 NSString* str = [[NSString alloc] initWithFormat:@"%@",@"Hello World"] 调用,但是必须手动release来释放内存资源

2、stringWithFormat是类方法

可以直接用 NSString* str = [NSString stringWithFormat:@"%@",@"Hello World"] 调用,内存管理上是autorelease的,不用手动显式release

14.Object-C中是否可以一个类同时继承多个父类;如何实现java中继承多个接口的效果?

Objective-c的类不可以有多继承,OC里面都是单继承,多继承可以用protocol委托代理来模拟实现

可以实现多个接口,可以通过实现多个接口完成OC的多重继承

15.autoreleasePool的工作原理

AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

16.Object-C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?

线程创建有三种方法:使用 NSThread 创建、使用 GCD 的 dispatch、使用子类化的 NSOperation,然后将其加入 NSOperationQueue;

在主线程执行代码,方法是 performSelectorOnMainThread

17、写一个方法:用递归计算N的阶乘,然后将计算结果进行存储,以便应用退出后下次启动可直接获取该值。

int prime(int n){
                if(n>1)
                return n*prime(n-1); //返回值最好是long或者是lld
                else
                return 1;
                }

18.@property(copy)NSMutableDictionary *dic,这种写法会出现什么问题,怎么解决?

没有指明nonatomic,因此就是atomic原子操作,会影响性能。该属性使用了同步锁,会在创建时生成一些额外的代码用于帮助编写多线程程序,这会带来性能问题,通过声明nonatomic可以节省这些虽然很小但是不必要的额外开销。在我们的应用程序中,几乎都是使用nonatomic来声明的,因为使用atomic并不能保证绝对的线程安全,对于要绝对保证线程安全的操作,还需要更高级的方式来处理,比如NSSpinLock、@sysncronized等

因为使用的是copy,所以得到的实际是NSArray类型,它是不可变的,若在使用中使用了增、删、改操作,则会crash。

19.使用block时什么情况会发生引用循环,如何解决?

一个对象中强引用了block,在block中又强应用了该对象,就会发生循环引用。

解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

20、如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图,并显示出来)

//使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。

//创建队列组
dispatch_group_t_group = dispatch_group_create();

//获取全局并发队列
dispatch_queue_t_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_async(group,queue,^{/*加载图片1*/});
dispatch_group_async(group,queue,^{/*加载图片2*/});
dispatch_group_async(group,queue,^{/*加载图片3*/});

//当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
	//合并图片
});

21、如何调试BAD_ACCESS错误

出现BAD_ACCESS的情境

访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。死循环

如何调试:

1、重写object的respondsToSelector方法,显示出现EXEC_BAD_ACCESS前访问的最后一个object

2、通过Zombie

3、设置全局断电快速定位问题代码所在行

4、Xcode7已经集成了BAD_ACCESS捕获功能:Address Sanitizer。

具体可参考链接

22、a=1,b=a++,c=++a。那么a,b,c分别是多少?

a++和++a,这时候a都会自增1

有“=”的时候,b=a++;会先把a赋值给b,然后a自增1.而b=++a,是先a自增1,然后把a赋值给b。所以这个里面a=1,b=a++。这一步a先赋值给b,b=1,然后a自增1,a=2。c=++a,这一步是先a自增1,a=3,然后把a的值赋值给c,c=3.

23、在Xcode中,需要编译混合Objective-C和C++的源码文件,需要将文件格式的后缀改为.mm。

24、运行如下代码写出redView的frame和bound。

UIView *redView = [[UIView alloc]init];
redView.backgroundColor = UIColor.redColor;
redView.frame = CGRectMake(200, 200, 100, 100);
[self.view addSubview:redView];
redView.transform = CGAffineTransformMakeTranslation(50, 50);
redView.center = CGPointMake(redView.center.x + 30, redView.center.y - 30);

frame:{{280, 220}, {100, 100}},bounds:{{0, 0}, {100, 100}}

CGAffineTransformMakeTranslation实现以初始位置为基准,在x轴方向上平移x单位,在y轴方向上平移y单位。

CGAffineTransformMakeTranslation(),这是一个实现相对位移的函数,每次移动都是相对(200,200)的这个点

打印View的frame或者bounds:

NSLog(@"frame:%@,bounds:%@",NSStringFromCGRect(redView.frame),NSStringFromCGRect(redView.bounds));

25、UIViewController在显示过程中,各个方法调用的顺序是:

init-初始化程序

viewDidLoad-加载视图

viewWillAppear-UIViewController对象的视图即将加入窗口时调用;

viewDidApper-UIViewController对象的视图已经加入到窗口时调用;

viewWillDisappear-UIViewController对象的视图即将消失、被覆盖或是隐藏时调用;

viewDidDisappear-UIViewController对象的视图已经消失、被覆盖或是隐藏时调用;

viewWillUnload-当内存过低时,需要释放一些不需要使用的视图时,即将释放时调用;

viewDidUnload-当内存过低,释放一些不需要的视图时调用。

26、写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个?

#define MIN(X,Y) ((X)>(Y)?(Y):(X))

结果分析:

define只会是纯替换作用,所以X,Y均需要加括号,以防止X,Y为表达式的情况

27、说一下ios的反射机制。

点击查看​​​​​​​

28、self->和self.的区别

1> self.是调用get方法

2> self.是当前本身,是一个指向当前对象的指针

3> self->是直接访问成员变量

29、成员变量和属性的区别:

@interface ViewController ()
{
    NSString *name;
}

@property (nonatomic, strong) NSString *birthday;

@end

如上图所示:
1、{ }中定义的变量为成员变量,name则为成员变量
2、@property声明属性,birthday为属性

1、成员变量是不与外界接触的变量,应用于类的内部,所以当用于类内部,属性为private时,就可以将变量定义为成员变量;
2、属性变量可以让其他对象访问,可以设置只读、只写等属性,当属性为public时,定义属性在.h中;
3、当类的内部需要用getter/setter方法实现一些功能时,需要定义属性在.m中。

https://www.jianshu.com/p/80b808321e11​​​​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值