图片的解压缩:
imageNamed: 方法的特点在于可以缓存已经加载的图片;使用时,先根据文件名在系统缓存中寻找图片,如果找到了就返回;如果没有,就在 Bundle 内查找到文件名,找到后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码。当 UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存去。在图片解码后,App 第一次退到后台和收到内存警告时,该图片的缓存才会被清空,其他情况下缓存会一直存在。
imageWithContentsOfFile: 该方法直接返回图片,不会缓存。而且其解码依然要等到第一次显示该图片的时候。
/**将生成的image图片赋值给imageView的时候,一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化,在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction,将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作,Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层**/
位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点。我们在应用中经常用到的 JPEG 和 PNG 图片就是位图。
像素格式则是用来描述每个像素的组成格式,它包括以下信息:
- Bits per component :一个像素中每个独立的颜色分量使用的 bit 数;
- Bits per pixel :一个像素使用的总 bit 数;rgba -> Oxffffffff -> 1 * 4bits
- Bytes per row :位图中的每一行使用的字节数。width * Bits per pixel -> width * 4
不管是 JPEG 还是 PNG 图片,都是一种压缩的位图图形格式。只不过 PNG 图片是无损压缩,并且支持 alpha 通道,而 JPEG 图片则是有损压缩,可以指定 0-100% 的压缩比。
将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这里就必须用到图片的解压缩。
解压缩后的图片大小 = 图片的像素宽 30 * 图片的像素高 30 * 每个像素所占的字节数 4
解压缩的原理就是对图片进行重新绘制
/** width:像素宽 height:像素高 bitsPerComponent: 像素的每个颜色分量使用的 bit 数,在 RGB 颜色空间下指定 8 即可 bytesPerRow:位图的每一行使用的字节数,大小至少为 width * bytes per pixel 字节(width * 4), 我们指定 0 时,系统会为我们自动计算 space :就是我们前面提到的颜色空间,一般使用 RGB 即可 bitmapInfo: 位图的布局信息: typedef CF_OPTIONS(uint32_t, CGBitmapInfo) { kCGBitmapAlphaInfoMask = 0x1F, kCGBitmapFloatInfoMask = 0xF00, kCGBitmapFloatComponents = (1 << 8), /*颜色分量是否为浮点数*/ kCGBitmapByteOrderMask = kCGImageByteOrderMask, kCGBitmapByteOrderDefault = (0 << 12), kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,字节顺序使用 16 位的主机字节 --- kCGBitmapByteOrder16Host 兼容大小端 kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,字节顺序使用 32 位的主机字节 --- kCGBitmapByteOrder32Host 兼容大小端 kCGBitmapByteOrder16Big = kCGImageByteOrder16Big,字节顺序使用 16 位的主机字节 --- kCGBitmapByteOrder16Host 兼容大小端 kCGBitmapByteOrder32Big = kCGImageByteOrder32Big字节顺序使用 32 位的主机字节 --- kCGBitmapByteOrder32Host 兼容大小端 }; alpha 的信息由枚举值 CGImageAlphaInfo 来表示: typedef CF_ENUM(uint32_t, CGImageAlphaInfo) { kCGImageAlphaNone, /* For example, RGB. */ kCGImageAlphaPremultipliedLast, /* For example, premultiplied RGBA */ kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */图片包含 alpha kCGImageAlphaLast, /* For example, non-premultiplied RGBA */ kCGImageAlphaFirst, /* For example, non-premultiplied ARGB */ kCGImageAlphaNoneSkipLast, /* For example, RBGX. */ kCGImageAlphaNoneSkipFirst, /* For example, XRGB. */图片不包含 alpha kCGImageAlphaOnly /* No color data, alpha data only */ }; */ CGBitmapContextCreate(void * __nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo) CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
yyimage中图片解压缩方式为:
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask; BOOL hasAlpha = NO; if (alphaInfo == kCGImageAlphaPremultipliedLast || alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaLast || alphaInfo == kCGImageAlphaFirst) { hasAlpha = YES; } // BGRA8888 (premultiplied) or BGRX8888 // same as UIGraphicsBeginImageContext() and -[UIView drawRect:] CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst; CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); //将原始位图绘制到上下文中
CGImageRef newImage = CGBitmapContextCreateImage(context);// 创建一张新的解压缩后的位图 CFRelease(context);
performSelector:
performSelector响应Objective-C动态性,将方法的绑定延迟到运行时,因此编译阶段不会检测方法有效性,即方法不存在也不会提示报错。反之因为此特性,performSelector也广泛用于动态化和组件化的模块中。
在ARC环境下,编译器并不知道将要调用的选择子是什么,有没有返回值,返回值是什么,所以ARC不能判断返回值是否能释放,因此ARC做了一个比较谨慎的做法:只添加retain
,不加release
。因此在有返回值或参数的时候可能导致内存泄漏。
避免内存泄漏的处理方式为及时调用:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
Tagpoint:
Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate.
Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。
所以,它的内存并不存储在堆中,也不需要malloc和free。
对于64位程序,我们的数据类型的长度是跟CPU的长度有关的。导致了 一些对象占用的内存会翻倍。
维护程序中的对象需要 分配内存,维护引用计数,管理生命周期,使用对象给程序的运行增加了负担。
将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。
以 0x127 为例 去掉 tag27(假设27为标记) 0x1 就是number = 1 的值。
APNS流程图:
1、应用程序注册APNS消息推送。
2、iOS从APNS Server获取device token,应用程序接收device token。
3、应用程序将device token发送给程序的PUSH服务端程序。
4、服务端程序向APNS服务发送消息。
5、APNS服务将消息发送给iPhone应用程序。
青花瓷抓包原理:
Charles 作为一个“中间人代理”,当浏览器和服务器通信时,Charles接收服务器的证书,但动态生成一张证书发送给浏览器,
也就是说Charles作为中间代理在浏览器和服务器之间通信,所以通信的数据可以被Charles拦截并解密。
由于Charles更改了证书,浏览器校验不通过会给出安全警告,必须安装Charles的证书后才能进行正常访问。
客户端信任了 Charles 自己制作的证书,然后导致 Charles 拿到 CA 证书和对称加密的公开密钥。
静态库:
1 注意理解:无论是.a
静态库还.framework
静态库,我们需要的都是二进制文件+.h
+其它资源文件的形式,不同的是,.a
本身就是二进制文件,需要我们自己配上.h
和其它文件才能使用,而.framework
本身已经包含了.h
和其它文件,可以直接使用。
2 图片资源的处理:两种静态库,一般都是把图片文件单独的放在一个.bundle
文件中,一般.bundle
的名字和.a
或.framework
的名字相同。.bundle
文件很好弄,新建一个文件夹,把它改名为.bundle
就可以了,右键,显示包内容可以向其中添加图片资源。
3 category
是我们实际开发项目中经常用到的,把 category
打成静态库是没有问题的,但是在用这个静态库的工程中,调用 category
中的方法时会有找不到该方法的运行时错误(selector not recognized),解决办法是:在使用静态库的工程中配置 other linker flags
的值为-ObjC
。
4 如果一个静态库很复杂,需要暴露的.h
比较多的话,就可以在静态库的内部创建一个.h
文件(一般这个.h
文件的名字和静态库的名字相同),然后把所有需要暴露出来的.h 文件都集中放在这个.h 文件中,而那些原本需要暴露的.h
都不需要再暴露了,只需要把.h
暴露出来就可以了。
setObject:forKey: && setValue:forKey:
-setValue:forKey:是KVC提供的方法,可对绝大部分对象进行操作(包括自定义对象),-setObject: forKey是NSMutableDictionary提供的方法,只能操作可变字典;
-setValue:forKey:当value为nil时,对于字典相当于删除key-value对,-setObject: forKey当object为nil时将直接导致程序crash,因此调用之前需要判空;
NSCache: NSDictionary:
1.当系统资源将要耗尽的时候,NSCache可以自动删减缓存,而且线性删减"最久未使用的"对象,NSCache是不是很强大。但是NSDictionary就需要在系统发出"低内存"通知时手工删减缓存,还需要自己编写相应优先删减内存等一系列逻辑。
2.NSCache是线程安全的,可以在供多个线程同时访问NSCache但是NSDictionary就不具备此优势
3.NSCache对象不拷贝键,因为很多时候键都是由不支持拷贝操作的对象来充当的,因此NSCache不会自动拷贝
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
NSCache: 如果设置了totalCostLimit
必然存储缓存的方法调用必然带上了cost
,否则totalCostLimit
是无用的。
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
cache对象的代理 , 用来即将清理cache的时候得到通知.
这里面有几种情况会导致该方法执行:
- 手动移除(removeObjectForKey)
- 缓存超过设定的上线
- App不活跃
- 系统内存爆炸
dyld:
由于 iOS
系统中 UIKit
/ Foundation
等库每个应用都会通过 dyld
加载到内存中 , 因此 , 为了节约空间 , 苹果将这些系统库放在了一个地方 : 动态库共享缓存区 (dyld shared cache) .
类似 NSLog
的函数实现地址 , 并不会也不可能会在我们自己的工程的 Mach-O
中
在工程编译时 , 所产生的 Mach-O
可执行文件中会预留出一段空间 , 这个空间其实就是符号表 , 存放在 _DATA
数据段中
编译时 : 工程中所有引用了共享缓存区中的系统库方法 , 其指向的地址设置成符号地址
运行时 : 当 dyld
将应用进程加载到内存中时 , 根据 load commands
中列出的需要加载哪些库文件 , 去做绑定的操作, dyld
就会去找到 Foundation
中 NSLog
的真实地址写到 _DATA
段的符号表中 NSLog
的符号上面
+load():
load
方法在 runtime
库开始运行时调用.
一个类的 load
方法在所有的父类 load
方法调用之后.
分类的 load
方法在类的 load
方法之后.
不同分类之间的 load
方法调用顺序和编译顺序有关.
Category & Extension:
category:
typedef struct category_t { const char *name; // 类名 classref_t cls; // 类 struct method_list_t *instanceMethods; // 实例方法列表 struct method_list_t *classMethods; // 类方法列表
struct protocol_list_t *protocols; // 协议列表
struct property_list_t *instanceProperties; // 属性列表
} category_t;
category可以添加实例方法,类方法,甚至可以实现协议,添加属性,但无法添加实例变量。
category结构体本身命名,而且有static来修饰,在同一个编译单元里我们的category名不能重复
所有的category文件,都会被编译成名为OBJC$_CATEGORY_xxClass$_categoryname的struct对象,对结构体成员变量进行初始化和赋值操作。
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { &_OBJC_$_CATEGORY_xxClass$_categoryname };
编辑器会在objc_catlist section中的category_t 数组保存该category对应的struct对象
在app的启动阶段,调用_read_images,拿到 category_t 结构体数组(category_t的顺序由compile sources编译顺序决定),通过attachCategoryMethods反向拼接数组中的实例方法,通过attachMethodLists将拼接好的方法放到类的方法列表前面。造成方法覆盖的假象。
在追加方法和属性后,才会走+load().+load()的执行顺序是先类本身,再category,而category的+load执行顺序是根据编译顺序决定的。而追加的方法执行的顺序按照编译顺序决定
关联对象都由AssociationsManager管理,而AssociationsManager定义如下:
class AssociationsManager { static OSSpinLock _lock; static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap. public: AssociationsManager() { OSSpinLockLock(&_lock); } ~AssociationsManager() { OSSpinLockUnlock(&_lock); } AssociationsHashMap &associations() { if (_map == NULL) _map = new AssociationsHashMap(); return *_map; } };
AssociationsHashMap的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
调用_object_remove_assocations做关联对象的清理工作。
extension:
extension可以声明方法,还可以声明属性、成员变量。extension一般用于声明私有方法,私有属性,私有成员变量。
extension在编译期决议,它就是类的一部分。头文件里的@interface以及具体实现文件里的@implement一起形成一个完整的类。
你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension,除非创建子类再添加extension。
viewController.m文件中通常寄生这么个东西,其实这就是一个extension:
@interface ViewController () @end
通过addFile->select Objective-C File 来创建extension:如ClassA_extension.h,在ClassA或者其子类中的.m文件 #import "ClassA_extension.h" 就可以使用extension的方法和属性
Static & const & extern & #define
static
修饰局部变量
1.让局部变量只初始化一次
2.局部变量在程序中只有一份内存
3.并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)
修饰全局变量
1.全局变量的作用域仅限于当前文件
2.避免重复定义全局变量
const
1.没有const修饰的指针
指针p和*p都能被修改
2.const修饰的*p
被const修饰的*p只能被赋值一次,以后不能赋值,否则编译器报错
3.const修饰的p
被const修饰的p只能存一次地址,以后再也不能其它存地址了,否则编译器报错
4.const在声明字符串的用法(一般我们定义一个常量又不想被修改应该这样:)
NSString *const FYNName =@"jack";
extern
只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量
static和const联合使用
1.用static声明局部变量,使其变为静态存储方式(静态数据区),作用域不变;用static声明 外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
2.const将一个变量变成常量
3.初始化一个全局变量或static变量时,只能用常量赋值,不能用变量赋值!
static和const. & #define
宏定义的是常量,常量都放在常量区,只会生成一份内存。
1. static const修饰变量只有一份内存
2.宏定义,只是简单的替换,宏定义的是变量的时候每次使用都需要创建一份内存
3. 编译时刻:宏是预编译(编译之前处理),const是编译阶段。
4.编译检查:宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。
5.宏的好处:宏能定义一些函数,方法。 const不能。
6.宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换。
load & initialize
load:
当类被引用进项目的时候(类文件加载到系统中)就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
- 1.当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
- 2.当子类未实现load方法时,不会调用父类load方法
- 3.类中的load方法执行顺序要优先于类别(Category)
- 4.当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)
- 5.当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致
- 1.父类的initialize方法会比子类先执行
- 2.当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法.
- 3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)
Method & SEL & IMP
Selector
typedef struct objc_selector *SEL
叫做选择子或者选择器,选择子代表方法在 Runtime 期间的标识符。为 SEL 类型,虽然 SEL 是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。
不同类中相同名字的方法所对应的方法选择子是相同的.
const char *sel_getName(SEL sel) {
return sel ? (const char *)sel : "<null selector>";
}
Implementation(IMP):
typedef id (*IMP)(id, SEL, ...)
代表函数指针,即函数执行的入口。该函数使用标准的 C 调用。第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;第二个参数代表方法的选择子;... 代表可选参数,前面的 id 代表返回值。
Method
typedef struct objc_method *Method
Method 对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个 objc_method 结构体指针,objc_method 的定义为:
struct objc_method {
SEL method_name;// 方法的名字
char *method_types;// 方法的签名
IMP method_imp;// 方法的实现
};
Method method = class_getInstanceMethod(currentClass, @selector(resume));// 从SEL获取method
method_getImplementation(method);// 从method获取imp
对称加密和非对称加密:
对称加密算法有DES、3DES、AES
非对称 RSA
内存的几大区域:
堆区:由程序员申请,管理和内存回收。数据储存的结构是链表。
全局区/静态区:储存全局初始化和未初始化变量和静态变量。
文字常量区:主要储存字符串常量。
程序代码区:存放程序的二进制代码。
self.intSource = 0; dispatch_async(queue1, ^{ for (int i = 0; i < 10000; i++) { self.intSource = self.intSource + 1; } }); dispatch_async(queue2, ^{ for (int i = 0; i < 10000; i++) { self.intSource = self.intSource + 1; } });
if (obj) { SyncData* data = id2data(obj, ACQUIRE);// 一个简单的Hash算法,然后将传入的对象地址,通过地址找到下标映射到不同的SyncList上。而SyncList是一个维护SyncData的链表,每个SyncList都单独维护操作自己的散列锁lock。 assert(data); data->mutex.lock(); } typedef struct SyncData { struct SyncData* nextData; DisguisedPtr<objc_object> object; int32_t threadCount; // number of THREADS using this block recursive_mutex_t mutex;// 一把递归锁 } SyncData;
- 预编译:主要处理以“#”开始的预编译指令。
- 编译:
- 词法分析:将字符序列分割成一系列的记号。
- 语法分析:根据产生的记号进行语法分析生成语法树。
- 语义分析:分析语法树的语义,进行类型的匹配、转换、标识等。
- 中间代码生成:源码级优化器将语法树转换成中间代码,然后进行源码级优化,比如把 1+2 优化为 3。中间代码使得编译器被分为前端和后端,不同的平台可以利用不同的编译器后端将中间代码转换为机器代码,实现跨平台。
- 目标代码生成:此后的过程属于编译器后端,代码生成器将中间代码转换成目标代码(汇编代码),其后目标代码优化器对目标代码进行优化,比如调整寻址方式、使用位移代替乘法、删除多余指令、调整指令顺序等。
- 汇编:汇编器将汇编代码转变成机器指令。
- 静态链接:链接器将各个已经编译成机器指令的目标文件链接起来,经过重定位过后输出一个可执行文件。
- 装载:装载可执行文件、装载其依赖的共享对象。
- 动态链接:动态链接器将可执行文件和共享对象中需要重定位的位置进行修正。
- 最后,进程的控制权转交给程序入口,程序终于运行起来了。
静态库和动态库:
内联函数是为了减少函数调用的开销,编译器在编译阶段把函数体内的代码复制到函数调用处。类似于宏定义,但可以进行语法检查。
网络层的优化:
减少DNS请求,使用HttpDNS获取原始ip地址进行请求,在header请求头中增加host字段,指定为原始请求地址
数据传输使用gzip
使用断点续传,否则网络不稳定时可能多次传输相同的内容
结合ETag 和 Last-Modified 减少数据重复传输
网络请求本地缓存
避免频繁发起请求
请求失败缓存后重发请求
2G、3G、4G、wifi下设置不同的超时时间
取消系统默认kvo并手动触发:
- (void)setAge:(NSString *)age {
if (age > 18 ) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
// 如果监测到键值为age,则指定为非自动监听对象
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
SideTables:
struct SideTable { spinlock_t slock;//操作SideTable时用到的锁 RefcountMap refcnts;//引用计数器的值,这是一个map weak_table_t weak_table;//存放weak指针的哈希表 };
struct weak_table_t { weak_entry_t *weak_entries; //保存了所有指向指定对象的weak指针 weak_entries的对象 size_t num_entries; // weak对象的存储空间 uintptr_t mask; //参与判断引用计数辅助量 uintptr_t max_hash_displacement; //hash key 最大偏移值 };
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //指向当前weak修饰的对象
union { struct { weak_referrer_t *referrers;// weak指针对象 uintptr_t out_of_line : 1; uintptr_t num_refs : PTR_MINUS_1; uintptr_t mask; uintptr_t max_hash_displacement; }; struct { // out_of_line=0 is LSB of one of these (don't care which) weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; }; }
}
RefcountMap其实是个C++的Map。为什么Hash以后还需要个Map?其实苹果采用的是分块化的方法。
内存中对象的数量实在是太庞大了我们通过第一个Hash表只是过滤了第一次,然后我们还需要再通过这个Map才能精确的定位到我们要找的对象的引用计数器。
weak和strong都是Object-C的修饰词,
strong是通过runtime维护的一个自动计数表结构。
weak是有Runtime维护的weak哈希表,用于存储指向某个对象的所有weak指针,Key是所指对象的地址,Value是weak指针的地址数组。
AssociationsManager => AssociationsHashMap { object的内存地址: ObjectAssociationMap { key: ObjcAssociation(policy, new_value)// 如果value不为nil,根据policy决定是否进行retain操作,如果value为nil,根据policy决定是否进行release操作 } }
Autoreleasepool:
Autoreleasepool是由多个AutoreleasePoolPage以双向链表的形式连接起来的, Autoreleasepool的基本原理:在每个自动释放池创建的时候,会在当前的AutoreleasePoolPage中设置一个标记位,在此期间,当有对象调用autorelsease时,会把对象添加到AutoreleasePoolPage中,若当前页添加满了,会初始化一个新页,然后用双向量表链接起来,并把新初始化的这一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到标志位。
ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:
void *context = objc_autoreleasePoolPush(); // {}中的代码objc_autoreleasePoolPop(context);
而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。
每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),
objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参
根据传入的哨兵对象地址找到哨兵对象所处的page
从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page
嵌套的AutoreleasePool就非常简单了,pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已
class AutoreleasePoolPage { magic_t const magic; id *next;//下一个存放autorelease对象的地址 pthread_t const thread; //AutoreleasePoolPage 所在的线程 AutoreleasePoolPage * const parent;//父节点 AutoreleasePoolPage *child;//子节点 uint32_t const depth;//深度,也可以理解为当前page在链表中的位置 uint32_t hiwat; }
内省:
- isMemberOfClass //对象是否是某个类型的对象
- isKindOfClass //对象是否是某个类型或某个类型子类的对象
- isSubclassOfClass //某个类对象是否是另一个类型的子类
- isAncestorOfObject //某个类对象是否是另一个类型的父类
- respondsToSelector //是否能响应某个方法
- conformsToProtocol //是否遵循某个协议
反射:
- JSON与模型之间的相互转换
- Method Swizzling
- KVO的实现原理
- 实现NSCoding的自动归档和自动解档
启动和优化:
iOS的启动流程
1、根据 info.plist 里的设置加载闪屏,建立沙箱,对权限进行检查等
2、加载可执行文件
3、加载动态链接库,进行 rebase 指针调整和 bind 符号绑定
4、Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;
5、初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。
6、执行 main 函数
7、Application 初始化,到 applicationDidFinishLaunchingWithOptions 执行完
8、初始化帧渲染,到 viewDidAppear 执行完,用户可见可操作。
启动优化
1、减少动态库的加载
2、去除掉无用的类和C++全局变量的数量
3、尽量让load方法中的内容放到首屏渲染之后再去执行,或者使用initialize替换
4、去除在首屏展现之前非必要的功能
5、检查首屏展现之前主线程的耗时方法,将没必要的耗时方法滞后或者延迟执行
crash和解决方案:
- unrecognized selector sent to instance 方法找不到
- 数组越界,插入空值
[NSDictionary initWithObjects:forKeys:]
使用此方法初始化字典时,objects和keys的数量不一致时- NSMutableDictionary,
setObject:forKey:
或者removeObjectForKey:
时,key为nil setValue:forUndefinedKey:
,使用KVC对对象进行存取值时传入错误的key或者对不可变字典进行赋值- NSUserDefaults 存储时key为nil
- 对字符串操作时,传递的下标超出范围,判断是否存在前缀,后缀子串时,子串为空
- 使用C字符串初始化字符串时,传入null
- 对可变集合或字符串使用copy修饰并进行修改操作
- 在空间未添加到父元素上之前,就使用autoLayout进行布局
- KVO在对象销毁时,没有移除KVO或者多次移除KVO
- 野指针访问
- 死锁
1-9都可以利用Runtime进行拦截,然后进行一些逻辑处理,防止crash
NSInvalidArgumentException
向容器加入nil,引起的崩溃。
SIGSEGV
访问没有被开辟的内存或者已经被释放的内存。
NSRangeException
数组越界,字符串截取越界
SIGABRT
异常 这是一个让程序终止的标识,会在断言、app内部、操作系统用终止方法抛出。
卡顿和解决方案:
主线程中进行IO或其他耗时操作,解决:把耗时操作放到子线程中操作
GCD并发队列短时间内创建大量任务,解决:使用线程池
文本计算,解决:把计算放在子线程中避免阻塞主线程
大量图像的绘制,解决:在子线程中对图片进行解码之后再展示
dispatch_once
用原子性操作block执行完成标记位static dispatch_once_t onceToken,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。
dispatch_once
常被用于创建单例、swizzeld method等功能。
AutoLayout:
instrument:
Time Profiler:CPU分析工具分析代码的执行时间。
Core Animation:离屏渲染,图层混合等GPU耗时。
1、"Color Blended Layers":图层混合
2、"Color Hits Green and Misses Red":图层缓存
3、"Color Offscreen-Rendered Yellow":离屏渲染
Leaks:内存检测,内存泄漏检测工具。
Allocation: 内存分配情况
Zombie: 野指针检测
MVC & MVP & MVVM:
MVC:
view负责接收用户事件,并告诉控制器事件来源,控制器负责通知model进行请求数据,然后model进行数据更新,view内部监听model更新而刷新view本身
MVP
1. 各部分之间的通信,都是双向的。
2. View 与 Model 不发生联系,都通过 Presenter 传递。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
MVVM
将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
6大设计原则,23种设计模式:
1、单一职责原则:一个类只承担一个职责
2、开放封闭原则:如果要修改代码,尽量用继承或组合的方式来扩展类的功能,而不是直接修改类的代码。这是解决if-else多的好方式
3、里式替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能
4、最少知道原则:一定要做到:低耦合、高内聚
5、接口隔离原则:不要对外暴露没有实际意义的接口
6、依赖倒置原则:高层模块不应该依赖于底层模块,而应该依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。应该面向接口编程,不该面向实现类编程。
1、工厂方法模式:
凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。
2、单例模式:
3、建造者模式:
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。
4、适配器模式:
使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
drawRect && layoutSubviews:
2、该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
3、通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
4、直接调用setNeedsDisplay
drawRect方法使用注意点:
2、若使用calayer绘图,只能在drawInContext: 中(类似于drawRect)绘制,或者在delegate中的相应方法绘制。同样也是调用setNeedDisplay等间接调用以上方法
3、若要实时画图,不能使用gestureRecognizer,只能使用touchbegan等方法来调用setNeedsDisplay实时刷新屏幕
2、addSubview会触发layoutSubviews。
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
4、滚动一个UIScrollView会触发layoutSubviews。
5、旋转Screen会触发父UIView上的layoutSubviews事件。
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。