iOS基础概念总结(一)

时间:2021-04-18 10:31:15

关键词、关键概念

#import#include 有什么区别, @class呢,#import ""#import <>有什么区别

  • #include 在C 语言中会引入一个头文件,但是可能出现交叉编译, #import,在OC 中引入自己创建的头文件(#import ""),或者系统框架(#import <>),#import不会出现交叉编译
  • @class的作用是告诉编译器有@class后面的内容是一个类名。只是告诉编译器存在这么一个类,类具体包含哪些方法,属性和变量的并没有告诉编译器。一般在类的头文件中使用 @class来引入其他类。
  • #import<>用于对系统文件的引用,编译器会在系统文件目录中去查找文件
  • #import ""用于对自定义的文件的引用,编译器首先回去用户目录下查找,然后去安装目录,最后去系统目录中查找文件。

描述一下KVO 和 KVC

  • Key-Value Observing (简写为KVO) ,当指定的对象的属性被修改了,允许对象接受到通知的机制。每次指定的被观察对象的属性被修改的时候, KVO都会自动的去通知相应的观察者。
  • Key-Value Coding(简写为KVC),它是一种可以直接通过字符串的名字 (key)来访问类属性的机制。而不是通过调用setter,getter方法访问。

类目和继承的区别

  • 类目是对方法的扩展,不能添加成员变量。继承可以在原来父类的成员变量的基础上,添加新的成员变量
  • 类目只能添加新的方法,不能修改和删除原来的方法。继承可以增加、修改和删除方法。
  • 类目不提倡对原有的方法进行重载。继承可以通过使用 super对原来方法进行重载。
  • 类目可以被继承,如果一个父类中定义了类别,那么其子类中也会继承此类别。

什么是懒加载? 在使用懒加载时的注意事项是什么?

所谓的懒加载指的是延迟创建对象,只有当需要的时候才创建对象。在真正开发的过程中其实懒加载就是重写 getter方法。在getter方法的内部,实现对象的创建,如果对象为 nil才创建,如果不为nil,直接返回对象。在真正使用懒加载时需要注意的是当第一次使用对象时,需要调用 self.因为只有这样才能调用对应的 getter方法,对象才会被创建。

简述对UIView 、UIWindow、 CALayer的理解

  • CALayer是图层类,本身可以显示的,但是不能响应事件。
  • UIViewiOS 系统中界面元素的基础,所有的界面元素都继承自它。事件的处理由它来执行,但是显示其实是由其对应的 layer层来操作的,UIView 内嵌了一个layerlayer显示内容,UIView 本身增加了事件处理的功能。
  • UIWindow 继承自 UIView,主要的作用是作为窗口呈现其他的视图。而且一个应用程序一般情况下只有一个窗口。

id声明的对象有什

id是任意对象类型的,不能表示基本类型。id类型是通用指针类型,因为通过指针,也就是内存地址来引用对象,所以可以将任意对象赋值给id类型的对象。返回id类型值的方法是返回指向内存中某对象的指针。然后可以将该值赋给任何对象变量(强制类型转换即可)。因为无论在哪里,对象总是携带它的isa成员。所以即使将它存储在id类型的通用对象变量中,也总是可以确定它的真实类型,id是多态的一种体现

@synthesize和@dynamic 有什么区别?

  • @property有两个对应的词,一个是@synthesize,一个是@dynamic。如果@synthesize@dynamic都没写,那么默认的就是@syntheszie var = _var
  • @synthesize的语义是如果你没有手动实现setter方法和getter 方法,那么编译器会自动为你加上这两个方法。
  • @dynamic告诉编译器:属性的settergetter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。
  • 假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = instance.var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
  • @dynamic可用于在分类中添加属性(需要用到 objc_getAssociatedObjectobjc_setAssociatedObject 函数)。

在Category 中本身不允许为已有的类添加新的属性或者成员变量,有没有其他的方法可以在 category中添加属性或者是成员变量

  • 一种方法就是使用runtime.h中的objc_getAssociatedObjectobjc_setAssociatedObject来访问和生成关联对象。例如为 NSObject添加一个类目,分类中添加一个属性。代码如下所示:

NSObject+Test.h文件

#import <Foundation/Foundation.h>

@interface NSObject (Test)
@property (nonatomic, strong) NSString *test;
@end

NSObject+Test.m文件

#import “NSObject+Test.h"
#import <objc/runtime.h>
static const void *instanceNameKey = &instanceNameKey;
@implementation NSObject (Test)
@dynamic test;

- (NSString *)test
{
    return objc_getAssociatedObject(self, instanceNameKey);
}
- (void)setTest:(NSString *)test
{
    objc_setAssociatedObject(self, instanceNameKey, test, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

sizeof 和strlen 的区别和联系

  • sizeof(...)是运算符,在头文件中typedefunsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。
    —它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
  • strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char *)。当数组名作为参数传入时,实际上数组就退化成指针了。
    —它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL
  • 区别和联系
    1. sizeof操作符的结果类型是size_t,它在头文件中 typedefunsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
    2. sizeof是运算符,strlen是函数。
    3. sizeof可以用类型做参数,strlen只能用 char*做参数,且必须是以'\0'结尾的。sizeof还可以用函数做参数,比如:short f(); printf("%d\n",sizeof(f())); 输出的结果是 sizeof(short),即2 。
    4. 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
    5. 大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
    char str[20]="0123456789";
    int a=strlen(str); // a=10;
    int b=sizeof(str); //b=20;
    6. strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
    7. sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
    8. 当适用了于一个结构类型时或变量,sizeof返回实际的大小,当适用一静态地空间数组, sizeof 归还全部数组的尺寸。sizeof操作符不能返回动态地被分派了的数组或外部的数组的尺寸

const意味着"readonly",关键字const什么含义? 下面的声明都是什么意思 ?

const int a; 
int const a;  
const int *a; 
int * const a;  
int const * a const;
  • 前两个的作用是一样,a是一个常整型数
  • 第三个意味着 a是一个指向常整型数的指针 (也就是 ,整型数是不可修改的 ,但指针可以 )
  • 第四个意思a是一个指向整型数的常指 针 (也就是说 ,指针指向的整型数是可以修改的 ,但指针是不可修改的 )
  • 最后一个意 味着a是一个指向常整型数的常指针 (也就是说 ,指针指向的整型数是不可修改的 ,同时指针也是不可修改的 )。欲阻止一个变量被改变 ,可以使用const关键字。在定义该const变量时 ,通常需要对它进 ⾏初始化, 因为以后就没有机会再去改变它了
    1. 对指针来说, 可以指定指针本身为const, 也可以指定指针所指的数据为 const, 或二者同时指定为 const;
    2. 在一个函数声明中,const 可以修饰形参 ,表明它是一个输入参数 ,在函数内部不能改变其值 ;
    3. 对于类的成员函数,若指定其为 const 类型, 则表明其是一个常函数 ,不能修改类的成员变量 ;
    4. 对于类的成员函数,有时候必须指定其返回值为 const 类型, 以使得其返回值不为“左值”

nil,NSNULL,NULL 区别

  • nil定义一个实例(instance)为空, 指向OC中对象的空指针,是对 Objective-C id对象赋空值,对于 Objective-C 集合类对象比如数组对象,字典对象,当我们不需要再使用他们的时候,对他们release的同时最好也把他们赋值为nil,这样确保安全性,如果不赋值nil,可能导致程序崩溃
  • NSNull类定义了一个单例对象用于表示集合对象的空值。集合对象无法包含 nil作为其具体值,如NSArray、NSSetNSDictionary。相应地,nil值用一个特定的对象NSNull来表示。NSNull提供了一个单一实例用于表示对象属性中的的nil值。默认的实现方法中,dictionaryWithValuesForKeys:setValuesForKeysWithDictionary:自动地将NSNull1nil相互转换,因此您的对象不需要进行NSNull的测试操作。
  • NULL可以用在C 语言的各种指针上 ,在 Objective-C里,nil对象被设计来跟NULL空指针关联的。他们的区别就是nil是一个对象,而 NULL只是一个值。而且我们对于nil调用方法,不会产生 `crash或者抛出异常。

NSDictionary 和NSMutableDictionary的区别,是有序还是无序(追问:可以排序吗?如何排序,最好代码实现)

NSDictionary是不可变的对象,NSMutableDictionary是可变对象,可以进行添加和删除操作。是无序的排序的话可以这样做:

  • 获取所有的key
NSArray *myKeys = [myDictionary allKeys];
  • 对key 进行排序
NSArray *sortedKeys = [myKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
  • 按照key 获取对象
id firstObject = [myDictionary objectForKey: [sortedKeys objectAtIndex:0]];

keysSortedByValueUsingSelector/keysSortedByValueUsingComparator 通过使用指定SELNSComarator来对allKeys进行排序,然后通过objectsForKeys取出排序后的键-值(key-values)对。

内存中的栈和堆的区别是什么?哪些数据在栈上哪些数据在堆上?

  1. 管理方式: 对于栈来讲,是由编译器自动管理 ,无需我们手工控制 ;对于堆来说 ,释放工作由程序员控制 ,容易产生memory leak。
  2. 申请大小: 能从栈获得的空间较小 ,堆是向高地址扩展的数据结构 ,是不连续的内存区域。堆的大小受限于计算机系统中 有效的虚拟内存。由此可见 ,堆获得的空间比较灵活 ,也比较大。
  3. 碎片问题: 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续 ,从而造成大量的碎片 ,使程序效率降低。对于栈来讲 ,则不会存在这个问题 ,因为栈是先进后出的队列 ,他们是如此的一一对应 ,以至于永远都不可能有一个内存块从栈中间弹出
  4. 分配方式: 堆都是动态分配的 ,没有静态分配的堆。栈有 2种分配方式 :静态分配和动态分配。静态分配是编译器完成 的 ,比如局部变量的分配。动态分配由 alloca函数进行分配, 但是栈的动态分配和堆是不同的 ,他的动态分配是由编译器 进行释放 ,无需我们手工实现。
  5. 分配效率: 栈是机器系统提供的数据结构 ,计算机会在底层对栈提供支持 :分配专门的寄存器存放栈的地址 ,压栈出栈 都有专门的指令执行 ,这就决定了栈的效率比较高。堆则是C/C++函数库提供的, 它的机制是很复杂的。
  6. 在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。

浅复制和深复制的区别

浅层复制:只复制指向对象的指针 ,而不复制引用对象本身。
深层复制:复制引用对象本身。
意思就是说有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一 个内存资源,复制的只不过是一个指针,对象本身资源还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改。

iOS基础概念总结(一)

深复制就好理解了 ,内存中存在了两份独立对象本身。

iOS基础概念总结(一)

深浅拷贝前提是:是实现 NSCopying或者NSMutableCopying 协议。
深拷贝则对对象本身复制,同时对对象的属性也进行复制。
浅拷贝只是复制对象本身,对象的属性和包含的对象不做复制。

Foundation框架支持复制的类,默认是浅拷贝。其中对 Foundation中不可变的对象进行copy时作用相当于 retain。而如果是 mutablecopy时,无论对象是否可变,副本是可变的,并且实现了真正意义上的 copy。如果对可变对象进行copy,副本对象是不可变的,同样是真正意义上的 copy。

readwrite ,readonly, assign,retain, copy,nonatomic 属性的作用

@property是一个属性访问声明,扩号内支持以下几个属性
1. getter=getterName,setter=setterName,设置 settergetter 的方法名
2. readwrite,readonly,设置可供访问级别
3. assign,setter方法直接赋值, 不进行任何retain操作 ,为了解决原类型与环循引用问题
4. retain,setter方法对参数进行release旧值再 retain新值, 所有实现都是这个顺序 (CC上有相关资料 )
5. copy,setter方法进行copy 操作,与 retain 处理流程一样, 先旧值release,再 copy出新的对象, retainCount为1。这是为了减少对上下文的依赖而引入的机制。 copy是在不希望a 和b共享一块内存时会使用到。 a和 b各自有自己的内存。
6.nonatomic, 非原子性访问, 不加同步,多线程并发访问会提高性能。注意 ,如果不加此属性 ,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级。atomicnonatomic用来决定编译器生成的 getter和setter 是否为原子操作。在多线程环境下 ,原子操作 是必要的 ,否则有可能引起错误的结果。

@property中属性retain,copy,assgin的含义分别是什么?有什么区别?将其转换成get/set方法怎么做?有什么注意事项?

  • assign: 简单赋值,不更改索引计数( Reference Counting)。使用assign对基础数据类型 (NSIntegerCGFloat)和C数据类型(int, float, double, char, 等等)
  • copy: 建立一个索引计数为 1的对象,然后释放旧对象。使用copyNSString
  • retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为 1。使用retain对其他 NSObject和其子类

  • retain 表示持有特性,setter方法将传入参数先保留,再赋值,传入参数的 retaincount会+1

- (void)setInstance:(id)instance{
    if (_instance != instance) {
        [_instance release];
        _instance = [instance retain];
    }
}
  • copy 表示赋值特性,setter方法将传入对象复制一份;需要完全一份新的变量时
- (void)setInstance:(id)instance{
    if (_instance != instance) {
        [_instance release];
        _instance = [instance copy];
    }
}
  • assign 是赋值特性,setter 方法将传入参数赋值给实例变量;仅设置变量时
- (void)setInstance:(id)instance{
    if (_instance != instance) {
        _instance = instance;
    }
}

setValue 和setObject区别

在使用NSMutableDictionary的时候经常会使用setValue: forKey:setObject: forKey:,他们经常是可以交互使用的,代码中经常每一种的使用都有。
他们二者的区别就是
1. setObject:forkey:value是不能够为nil 的,不然会报错。setValue: forKey:value能够为nil,但是当valuenil的时候,会自动调用 removeObject:forKey:方法
2. setValue: forKey:key的参数只能够是NSString类型,而setObject: forKey:的可以是任何类型
3. setObject:forKey:方法NSMutabledictionary特有的 ,而setValue: forKey:方法是KVC(键-值编码)的主要方法。

使用圆角效果时为什么会出现卡顿效果 ,如何解决这个问题 ?

不要在滚动视图使用cornerRadius或者mask,添加

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;

采取预先生成圆角图片,并缓存起来这个方法才是比较好的手段。预处理圆角图片可以在后台处理,处理完毕后缓存起来,再在主线程显示,这就避免了不必要的离屏渲染了。