&%¥

858 阅读22分钟

方法和选择器有何不同

  • selector是一个方法的名字
  • method是一个组合体,包含了名字和实现

Objective-C中的反射机制

class反射

  • 通过类名的字符串实例化对象
Class cls = NSClassFromString(@"Student");
Student *stu = [[cls alloc]init];
  • 将类名变为字符串
NSString *className = NSStringFromClass([Student class]);

SEL反射

  • 通过方法的字符串实例化方法
SEL sel = NSSelectorFromString(@"methodName");
[stu performSelector:sel];
  • 将方法变成字符串
NSString *methodName = NSStringFromSelector(@selector(methodName));

关于SEL

SEL是什么

SEL就是对方法的一种包装。包装的SEL类型数据它对应相应的方法地址,找到方法地址就可以调用方法。在内存中每个类的方法都存储在类对象中,每个方法都有一个与之对应的SEL类型数据,根据SEL数据就可以找到对应的方法地址,进而调用方法。

如何声明一个SEL

  • 将方法包装成SEL对象
SEL sel = @selector(methodName);
  • 将一个字符串转换成SEL对象
SEL sel = NSSelectorFromString(@"methodName");

如何调用方法

  • 直接通过方法名来调用
Student *stu = [[Student alloc]init];
[stu methodName];
  • 通过SEL数据来调用
SEL sel = NSSelectorFromString(@"methodName");
[stu performSelector:sel];

关于协议Protocol

  • 协议中是什么意思 遵守NSObject 协议
  • 子类继承了父类,子类会遵守父类中遵守的协议 会
  • 协议中可以定义成员变量吗 能,但只能在头文件中声明,编译器是不会自动生成实例变量的,需要自己处理getter和setter方法
  • 如何约束一个对象类型的变量要存储的地址是遵守一个协议对象 id

面向对象有哪些特征

  • 继承:继承是从已有类得到继承信息创建新类的过程。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

  • 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。在类中所编写的方法就是一种封装。一个类就是对数据和数据操作的封装。封装就是隐藏一切可隐藏的东西,只对外界提供最简单的编程接口。

  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。也就是用同样的对象引用调用同样的方法但是做了不同的事情。分为编译时多态性和运行时多态性。

    1)编译时多态性是指方法重载(overload)。如:相同的函数名不同的参数。

    2)运行时多态指方法重写(override)。子类继承父类并重写父类中已有的或抽象的方法;用父类型引用子类型对象,这样同样的引用调用同样的方法会根据子类对象的不同而表现出不同的行为。

  • 抽象:抽象就是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

KVC KVO

KVO:一对多,观察者模式,键值观察机制,提供了观察某一属性变化的方法

KVC:键值编码,使用字符串直接访问对象的属性。

一个对象在调用setValue时执行步骤:

  • 检查是否存在相应key的set方法,存在就调用
  • set方法不存在,就查找_key的成员变量是否存在,存在就直接赋值
  • 如果_key没有找到,就查找相同名称的key,存在就赋值
  • 如果没有就调用valueForUndefinedkeysetValue:forUndefinedKey

KVO的底层实现

kvo是基于runtime机制实现的,使用了isa混写(isa-swizzling),当一个对象(假设对象时person,person的类是MyPerson)的属性值(person的age)发生改变时,系统会自动生成一个类,继承自MyPerson:NSKVONotifying_MyPerson,在这个类的setAge方法里,调用[super setAge:age][self willChangeValueForKey:@"age"][self didChangeValueForKey:@"age"],而这两个方法内部会主动调用监听者内部的- (void)observeValueForKeyPath方法。

扩展

若一个类有实例变量 NSString *_foo,调用setValue:forKey:时,使用foo 和 _foo 作为key都是可以的。

类别重写类的方法

在类别(category)中,是可以重写类的方法的,调用时会调用类别中实现的方法。

关于category和extension

  • category 在没有原类.m文件的情况下给类添加方法,无法添加成员变量,因为没有办法对其进行初始化
  • extension 一种特殊形式的类别,主要在一个类的.m文件中声明和实现延展的作用,就是给某个类添加私有方法或私有变量
  • 区别:

1)延展可以添加属性并且他添加的方法是必须实现的。可以认为是一个私有的类别

2)类别可以在不知道,不修改原来代码的情况下往里添加新的方法,只能添加,不能删除修改

3)如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级

4)继承可以增加,修改,删除方法,添加属性

#include、#import、@class

  • #import指令是Objective-C针对#include的改进版,可以确保引用的文件只会被引用一次,不会出现递归包含的问题
  • import会链入该头文件的所有信息,包括实例变量和方法等;而@class只是告诉编译器,其后面声明的名称是类的名称,至于类是如何定义的,暂不考虑
  • 在头文件中一般使用@class来声明类,因为在此处一般不需要知道其内部的实例变量和方法
  • 在实现类里,因需要知道引用类的实体变量和方法,所以使用#import来包含这个被引用类的头文件
  • 如果有循环依赖关系,如:A->B,B->A这样的相互依赖关系,如果使用 #import 来相互包含,会出现编译错误,使用@class在两个类的头文件中相互声明,则不会有编译错误出现。

谓词

谓词就是通过 NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选。

常用的使用方式,show me the code

    NSMutableArray *array = [[NSMutableArray alloc]init];
    for (int i = 0; i < 50; i++) {
        Person *p = [[Person alloc]init];
        p.age = i;
        p.name = [NSString stringWithFormat:@"%ds",i];
        [array addObject:p];
    }
    
    //构建谓词对象
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
    
    //使用 && 或 || 进行多条件过滤
    predicate = [NSPredicate predicateWithFormat:@"age>40 || age < 10"];
    predicate = [NSPredicate predicateWithFormat:@"age<40 && age > 10"];
    
    //使用 IN 来构建一个确定的范围
    predicate = [NSPredicate predicateWithFormat:@"age IN {2,3,4}"];
    
    //指定字符串开头或结尾是否包含指定字符
    predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH '1'"];
    predicate = [NSPredicate predicateWithFormat:@"name ENDSWITH '0'"];
    
    //指定字符串是否包含
    predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '3'"];
    
    //使用?代表一个字符,如第二个字符是 s
    predicate = [NSPredicate predicateWithFormat:@"name like '?s'"];
    //使用谓词条件过滤数组中的数据,过滤之后返回查询结果
    NSArray *result = [array filteredArrayUsingPredicate:predicate];

nil、Nil、NULL、NSNull的区别

NULL、nil、Nil在Objective-C中是一样的,都是(void *)0

  • NULL是宏,是对于C语言指针而使用的,表示空指针
  • nil是宏,对Objective-C中的对象使用,表示对象为空
  • Nil是宏,是对Objective-C中的类使用的,表示类指向空
  • NSNull是类类型,用于表示空的占位对象,是继承与NSObject的类型。

向一个nil对象发送消息

向一个nil对象发送消息是有效的,只不过在运行时不会有任何的作用,更不会crash

  • 如果方法返回值是一个对象,那么发送给nil的消息将返回nil
  • 如果返回值为指针类型,将返回 0
  • 如果返回值为结构体,将返回 0 ,结构体中的各个字段的值都是0
  • 如果返回值不是上述几种类型,则返回值是未定义的

self. 与 self-> 的区别

self是一直指向当前对象的指针,在实例方法与类方法中有所不同

  • self. 是调用get和set方法
  • self->是直接访问成员变量,只可以在类内部调用,不可在外部调用

init和initWithObject的区别

后者可以给属性赋值

@property的本质

@property = ivar(实例变量) + getter(取方法) + setter(存方法)

ivar、getter、setter是如何生成并添加到类中:

由编译器自动合成的,通过@synthesize关键字指定,若不指定,默认为@synthesize propertyName = _propertyName;若手动实现了getter/setter方法,则不会自动合成。

现在编译器默认添加@synthesize propertyName = _propertyName,因此不需要手动添加,除非需要修改成员变量名。

声明属性方式问题分析

如下声明一个属性,会有哪些问题呢?

@property (copy) NSMutableArray *array;
  • 没有指定为nonatomic,因此就是默认的atomic原子操作,会影响性能。该属性使用了同步锁,会在创建时候生成一些额外的代码用于编写多线程程序,会带来性能问题,通过声明为nonatomic可以节省这些虽小但不必要的开销。一般都会声明为nonatomic,因为atomic并不能保证线程的绝对安全,如需保证线程安全,还需要其他方式来处理。
  • 使用copy,所以得到的实际类型是NSArray,如果对其进行增、删、改等操作,会crash。

在protocol和category中如何声明属性@property

protocol

在protocol中使用@property只会生成setter和getter方法声明,我们使用属性的目的是希望遵守协议的对象能实现该属性

category

在category中使用@property也只是会生成setter和getter方法声明,如果真的需要给category增加属性的实现,需要借助运行时的两个函数

- (void)setPropertyName:(NSString *)propertyName{
    objc_setAssociatedObject(self, @selector(setPropertyName:), propertyName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)propertyName{
    return objc_getAssociatedObject(self, @selector(setPropertyName:));
}

如何为class定义一个对外只读对内可读写的属性

在头文件中将属性定义为 readonly,在.m文件中将属性重新定义为 readwrite

编译时和运行时对象类型的判断

如语句,obj是何类型?

NSString *obj = [[NSData alloc]init];

编译时是NSString,运行时是 NSData 类型

isa指针问题

isa:是一个Class类型的指针。每个实例对象都有个isa的指针,他指向对象的类,而Class里也有一个isa的指针,指向metaClass(元类)。元类保存了类方法的列表。当类方法被调用时候,会从本身查找类方法的实现,如果没有,元类会向他的父类查找该方法。

元类也是类,他也是对象。元类也有isa指针,他的isa指针最终指向的是一个根元类(root metaClass),根元类的isa指针指向本身,这样形成了一个封闭的内循环。

那metaClass又是什么呢?

metaClass其实是Class对象的类,为这个Class类存储类方法,当一个类发送消息时,就去这个类对应的metaClass查找那个消息,每个Class都有不同的metaClass,所有的metaClass都使用基类的metaClass作为他们的类。

@synthesize和@dynamic分别有什么作用

@property 有两个对应的词,分别是@synthesize和@dynamic,默认的是 @sythesize var = _var

  • @sythesize 表示如果没有手动实现setter和getter方法,那么编译器会自动加上这两个方法
  • @dynamic 表示属性的setter和getter方法需要手动实现,不会自动生成。编译时候不会出现问题,但是在运行时程序会crash。

使用实例:

@implementation Person
{
    NSInteger _age;
}
@dynamic age;

- (void)setAge:(NSInteger)age{
    _age = age;
}

- (NSInteger)age{
    return _age;
}
@end

日期格式化

  • 日期转字符串
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    formatter.dateFormat = @"yyyy-MM-dd";
    formatter.timeZone = [NSTimeZone defaultTimeZone];
    formatter.locale = [NSLocale autoupdatingCurrentLocale];
    NSDate *date = [NSDate date];
    NSString *dateStr = [formatter stringFromDate:date];
  • 字符串转日期
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    formatter.dateFormat = @"yyyy-MM-dd";
    formatter.timeZone = [NSTimeZone defaultTimeZone];
    formatter.locale = [NSLocale autoupdatingCurrentLocale];
    NSDate *date = [formatter dateFromString:dateStr];

获取当前时间的年月日

- (NSInteger)year {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:self] year];
}

- (NSInteger)month {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitMonth fromDate:self] month];
}

- (NSInteger)day {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:self] day];
}

- (NSInteger)hour {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitHour fromDate:self] hour];
}

- (NSInteger)minute {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitMinute fromDate:self] minute];
}

- (NSInteger)second {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitSecond fromDate:self] second];
}

- (NSInteger)nanosecond {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitSecond fromDate:self] nanosecond];
}

- (NSInteger)weekday {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekday fromDate:self] weekday];
}

- (NSInteger)weekdayOrdinal {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekdayOrdinal fromDate:self] weekdayOrdinal];
}

- (NSInteger)weekOfMonth {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekOfMonth fromDate:self] weekOfMonth];
}

- (NSInteger)weekOfYear {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitWeekOfYear fromDate:self] weekOfYear];
}

- (NSInteger)yearForWeekOfYear {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitYearForWeekOfYear fromDate:self] yearForWeekOfYear];
}

- (NSInteger)quarter {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitQuarter fromDate:self] quarter];
}

- (BOOL)isLeapMonth {
    return [[[NSCalendar currentCalendar] components:NSCalendarUnitQuarter fromDate:self] isLeapMonth];
}

使用performSelector传入3个以上参数

系统提供的performSelector的方法

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

分为两种情况进行处理:

  • 参数都为对象类型

    这个简单,只需通过数组或字典存入对象类型就行

  • 参数有的不是对象类型

    以有个参数为结构体举例:

typedef struct {
    int a;
    int b;
}MyStruct;

@interface MyObject : NSObject

@property(nonatomic,assign) MyStruct myStruct;

@end

@implementation MyObject

- (instancetype)init{
    if (self = [super init]) {

    }
    return self;
}

@end

使用

    MyObject *myObj = [[MyObject alloc]init];
    myObj.myStruct = (MyStruct){1,2};
    [self performSelector:@selector(sel) withObject:myObj];

对self和super的理解

self 是类的隐藏的参数,指向当前调用方法的类,另一个隐藏参数是 _cmd,代表当前类方法的 selector。

而 super ,并不是一个隐藏的参数,它只是一个“编译器指示符”,它和self指向的相同的消息接受者。不同的是,super会告诉编译器,当调用方法时,要去调用父类的方法,而不是本类的。

这个是和Objective-C的运行时机制有关的

当使用self调用方法时,会从当前类的方法列表中开始找,如果没有,继续在父类方法中找。当使用super时,则从父类的方法列表中开始找,然后调用父类的这个方法。

经典面试题:

下面代码输出什么?

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

结果:

NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son

NSMutableArray 和 NSArray 使用分析

  • 当数组在运行时需要不断变化时,使用NSMutableArray;初始化之后不需要改变,使用NSArray
  • NSArray只是表明该数组在运行时不会发生改变,即不能往数组中添加或删除数据,但不表明数组内的元素的内容不发生改变
  • NSArray是线程安全的,NSMutableArray不是线程安全的,使用多线程时需注意

结构体中能定义OC对象吗

不能,因为结构体中只能是类型的声明,无法进行空间分配。

创建一个对象需要哪些步骤

  • 开辟内存空间
  • 初始化参数
  • 返回内存地址值

id 和 instancetype 有什么区别

  • id类型,万能指针,能作为参数,方法的返回类型
  • instancetype ,只能作为方法的返回类型,并且返回类型是当前定义类的类类型

Foundation和Core Foundation对象有什么区别

  • Foundation对象时OC的,CoreFoundation对象是C对象

  • 数据类型之间的转换

    ARC下:bridge_retained(持有对象所有权 F-> CF),bridge_transfer(释放对象所有权 CF->F)

    非ARC下:__bridge

ARC的处理原理

ARC是Objective-C编译器的特性,不是运行时特性或者垃圾回收机制,所做的只不过是在代码编译时自动在合适的位置插入release或autorelease,只要没有强指针指向对象,对象就会释放。

扩展

对象调用release时,只会将引用计数器 -1 ,当对象的引用计数器为0时,才会调用dealloc释放对象的内存。

MRC和ARC混编

在文件的 Compiler Flags上添加参数

  • MRC -fno-objc-arc
  • ARC -fobjc-arc

weak关键字和assign有何不同

在ARC中,有可能出现循环引用时,让其中一端使用weak。

区别:

  • weak在属性所指对象遭到销毁时,属性值清空(置为nil),而assign只对“纯量类型”进行简单的复制操作
  • weak只可用于修饰OC对象,而assign可用于非OC对象

举例说明:

在release之后再次使用该对象调用方法,使用assign修饰的会crash,使用weak修饰的不会。

自动释放池

使用方法:

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    //code
    [pool drain];

注:只可在非ARC环境中使用

    @autoreleasepool{
        
    }

如何实现:

autoreleasepool以一个对象数组的形式实现,主要通过三个函数完成

objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease

autorrelease分别执行push、pop操作。销毁对象时执行release操作。

何时销毁:

简单来看,是在“}”执行完成之后销毁,实际上是Autorelease对象在当前的runloop迭代结束时释放,而他能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

即:通过Observer监听RunLoop的状态,一旦监听到RunLoop即将进入睡眠等待状态,就释放自动释放池(kCFRunLoopBeforeWaiting)

内存池

界面线程,也就是主线程维护着自己的内存池,用户自己创建的数据线程,需要创建该线程的内存池。如何创建????

内存管理研究的对象

野指针: 指针变量没有进行初始化或指向的空间已经被释放

  • 使用野指针调用对象方法,会报异常,程序crash
  • 通常再调用完release方法之后,把保存对象指针的地址清空,赋值为nil,找OC中没有空指针异常,所以[nil retain]调用方法不会有异常

内存泄漏

  • Person *p = [Person new],对象提前赋值nil或者清空,在栈区的p已经被释放,而堆区new产生的对象(僵尸对象)还没有释放,就会造成内存泄漏
  • 在MRC手动引用计数器模式下,造成内存泄漏的情况 1)没有配对释放,不符合内存管理原则 2)对象提前赋值nil或者清空,导致release不起作用

僵尸对象

堆中已经释放的对象,处理方式如下图

空指针:指针赋值为空,nil

重写setter方法

  • 判断是否是统一对象
  • release旧对象
  • retain新对象

内存缓存器的缓存策略

FIFO

先进先出

1、新访问的数据插入队列尾部,数据在队列中顺序移动

2、淘汰队列头部的数据

LRU(Least Recently Used)最近最少使用

根据数据的历史记录来进行淘汰数据,核心思想是“如果最近被访问过,那么将来被访问的几率就会高”。

1、将新数据插入到链表头部

2、每当缓存命中,及缓存数据被访问,则将数据移到链表头部

3、当链表满的时候,将链表尾部的数据丢弃

LFU(Least Frequently Used)访问频率

根据数据的历史访问记录来淘汰数据,核心思想是“如果数据过去被访问多次,那么将来被访问的频率也很高”。

每个数据块都会有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块按照时间排序。

1、新加入的数据插入到队列尾部 2、队列中的数据被访问后,引用计数增加,队列重新排序 3、当需要淘汰数据时,将已经排序的列表最后的数据块删除

扩展:

这种方式会存在一些情况下会有问题

  • 某些数据前期访问量特别大,但后期基本不访问了,仍会存在缓存中
  • 某些数据间隔访问,访问次数永远为1 ,虽然访问量大,但因为每次都在队尾,所以无法缓存

缓存策略参考链接

内存循环引用的出现场景

  • 定时器 NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用(self->timer->self)。另外,若timer一直处于validate的状态,则其引用计数将始终大于0,因此在不适用timer时,一定要调用invalidate方法。

  • block的使用 block在copy时会对block内部用到的对象进行强引用(ARC)或者retainCount+1(非ARC)。 如:某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,就出现了循环:self->block->self或者self->block->_ivar(成员变量)

解决方案:

1)如果要在block中直接使用外部强指针,使用一下代码

__weak typeof(self) weakSelf = self; 

2)如果在block内部使用延时操作还是用弱指针的话会取不到该弱指针,需要在block内部在将弱指针强引用一下

__strong typeof(self) strongSelf = weakSelf; 

UIViewController的一些方法及其执行顺序

方法说明

  • viewWillAppear:视图即将可见时调用,默认情况下不执行任何操作
  • viewDidAppear:视图已完全过渡到屏幕上时调用
  • viewWillDisappear:视图被驳回时调用,覆盖或以其他方式隐藏。
  • viewDidDisappear:视图被驳回后调用,覆盖或以其他方式隐藏
  • loadView 当没有正在使用nib视图页面时,子类会创建自己的自定义视图层。不可以直接调用
  • viewDidload:在视图加载后被调用,如果实在代码中创建的试图加载器,会在loadView方法后调用,如果是从nib视图页面输出,会在视图设置好后被调用
  • initWithNibName:bundle: 载入nib档案来初始化;loadView载入视图;viewDidload在载入视图至内存后会呼叫的方法;viewDidUnlload在视图从内存中释放之后呼叫的方法(当内存过低时,释放一些不需要的视图时调用)
  • didReceiveMemoryWarning 收到系统传来的内存警告通知执行该方法
  • sholdAutorotateToInterfaceOrientation是否支持不同方向的旋转视图
  • willAnimateRotationToInterfaceOrientation 在进行旋转视图前执行的方法(用于调整旋转视图)

代码的执行顺序

视图加载时

1、alloc

创建对象,分配空间

2、init(initWithNibName)

初始化对象,初始化数据

3、loadView

从nib载入视图,无需干涉,除非没有使用xib文件创建视图

4、viewDidLoad

载入视图完成,可进行自定义数据以及动态从创建其他控件

5、viewWillAppear

视图即将出现在屏幕之前

6、viewDidAppear 视图已在屏幕上渲染完成

视图移除时

1、viewWillDisapear

视图将从屏幕上移除之前

2、viewDidDisappear

视图已经被从屏幕上移除

3、dealloc

视图被销毁,对在init和viewDidLoad中创建的对象进行释放

keywindow

一个窗口当前能接受键盘和非触摸事件时,便被认为时主窗口。

触摸事件被投递到触摸发生的窗口,没有相应坐标值的事件被投递到主窗口。

同一时刻只有一个窗口是主窗口。

如何封装view

  • 添加所需的子控件
  • 接受数据模型数据设置子控件的数据和位置
  • 把不需要暴露的方法封装起来

APP启动过程

1、执行main函数 2、UIApplicationMain

  • 创建UIApplication对象
  • 创建UIApplication的delegate对象

3、delegate对象开始处理监听系统事件

4、调用代理方法application:didFinishLaunchingWithOptions:,创建UIWindow及其rootViewController

UIButton 和 UITableView的层级结构

都是继承结构

  • UIButton->UIControl -> UIView -> UIResponder -> NSObject
  • UITableView -> UIScrollView -> UIView -> UIResponder -> NSObject

设置UIScrollView的contentSize

一般情况下可以在viewDidLoad中进行设置,但是在autolayout下,系统会在viewDidAppear之前根据subview的contraint重新计算scrollView的contentSize,使得前面设置的值会被覆盖掉,导致无效。

解决方案:

  • 去除autolayout选项,自己手动设置contentSize
  • 如果使用autolayout,自己设置subview的constraint,让系统自动根据constraint计算contentSize
  • 在viewDidAppear中手动设置contentSize

对UIView、UIWindow和CALayer的理解

UIView

属于UIKit框架,负责渲染矩形区域的内容,为矩形区域添加动画,响应区域的触摸事件,布局和管理一个或多个子视图

UIWindow

属于UIKit框架,是一种特殊的UIView,通常在一个程序中只会有一个UIWindow,但可以创建多个UIWindow同时添加到程序中。主要有三个作用:

  • 作为容器,包含APP所要显示的所有视图
  • 传递触摸消息到程序中view和其他对象
  • 与UIViewController协同工作,方便完成设备方向旋转的支持

CALayer

属于QuartzCore框架,用来绘制内容,对内容进行动画处理,依赖UIView进行显示,不能处理用户事件。UIView依赖CALayer提供内容,CALayer依赖UIView的容器显示绘制内容。

UITableView的性能优化

  • 使用不透明视图,不透明的视图可以提高渲染的速度。将cell及其子视图的opaque属性设置为YES
  • 不重复创建不必要的cell
  • 减少动画的使用。最好不使用insertRowsAtIndexPaths:withRowAnimation:,而是直接调用reloadData方法。
  • 尽量减少cell中添加的视图的数目
  • cell中包含图片较多时,使用自定义的cell速度会比使用默认的要快。继承UITabbleViewCell,重写drawRect方法
- (void)drawRect:(CGRect)rect { 
if(image) {
[image drawAtPoint:imagePoint]; 
self.image = nil; 
} else { 
[placeHolder drawAtPoint:imagePoint];
}
[text drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation];
}
  • 不需要与用户交互时,使用CALayer绘制显示内容
  • 异步绘制,使用多线程,让子线程去做一些事情
  • 提前计算并缓存好高度,因为heightForRowAtIndexPath调用会非常频繁

layoutSubview何时调用

  • 初始化init方法时不会触发
  • 滚动UIScrollView时触发
  • 旋转屏幕时触发
  • 改变View的时触发,frame发生了改变

UIScrollView如何实现

frame就是他的contentSize,bounds为可视范围,通过改变bounds来改变可视范围。

解决手势冲突问题: 让UIScrollView遵守UIGestureRecognizerDelegate协议,实现方法,在方法中添加对手势的处理

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(nonnull UIGestureRecognizer *)otherGestureRecognizer{
    
    return YES;
}