问题答案 链接
进程
线程
一个线程同一时间只能做一件事(串行)
一个进程可以同时做多个事情(并行)
多线程原理:
CPU同时只能处理一条线程,
CPU在多条线程间快速调度,
调度时间足够快,造成了在并发执行的假象。
隐式创建子线程:
[self performSelectorInBackground : withObject: ];
主线程:
[self performSelectorOnMainThread: withObject: waitUntilDone:];
锁 @synchronized(obj){ } obj:唯一 资源抢夺时使用
atomic:原子属性 为setter方法自动加锁 线程安全
noatomic:适合内存小的移动设备 一般用这个
线程间的通信
[self performSelectorOnMainThread: withObject: waitUntilDone: modes:];
队列 (FIFO先进先出)
dispatch_sync 在当前线程执行 同步
dispatch_async 在新的线程执行 具备新建线程的能力 异步
并发: 允许多个任务同时执行 与异步函数搭配使用(队列) dispatch_queue_concurrent
串行 :一个任务结束再执行下一个任务 (队列) dispatch_queue_serial
同步:异步: 是否具备开启新线程的能力
global_queue 默认全局并发队列
main_queue 主队列 在主线程执行(无论同步异步)
各种组合 除主队列下,异步执行的都会开启新的线程
特殊:主队列中,同步执行会卡死(如果同时在主线程执行,互相等死)
使用sync函数往当前串行队列中添加任务,会卡住当前串行队列
一次性代码:
dispatch_once (全局的 整个程序中只执行一次)
快速迭代:
dispatch_apply (放入并发队列 几乎同时遍历)
队列组:
dispatch_group_async
dispatch_group_notify (group中的任务都结束再进入notify)
合成图片:
开启图形上下文,UIGraphicsBeginImageContext(CGSizeMake())
然后 [image drawInRect:]
获取合成图片 UIGraphicsGetImgaeFromCurrentImageContext()
结束上下文 UIGraphicsEndImageContext
可以不用block形式:
dispatch_async_f (函数形式) 可以传值为一个函数
单例模式封装宏:
使用GCD线程安全
如果使用 ==nil方式 使用@synchronized(obj){ }(大括号里是==nil的部分)
也可:XMGSingletonH(name) + (instancetype)shareed##name (保持方法名不雷同)
NSBlockOperation 在主线程执行
NSBlockOperation addExecutionBlock 在子线程执行
[NSOperationQueue mainQueue] 主队列
[[NSOperationQueue alloc]init] 其他队列
自定义Operation 需要完成的任务写入.m的方法 -(void)main{}
suspended 挂起
canceled 取消
这两个操作会在当前已经开始线程的任务结束后再挂起或者取消后面的线程任务
自定义operation内在main方法内,在耗时方法之间添加判断 if(self.isCancled) return; 可以即时取消,否则不能停止。
[op3 addDependency:op1]; 依赖 op3 依赖op1 1先3后 (在添加到队列之前设置依赖)
还可跨队列依赖
op.completeBlock = ^{} 监听
什么时候用nsoperation 实现 用gcd实现不了的情况
区别:operation可管理并发数,设置优先级,设置依赖,设置暂停,取消;有kvo可监听状态是否执行,取消,结束;
通知在子线程可以触发吗
通知和线程的关系(可添加通知队列)
1.在哪个线程发起通知,就在哪个线程接收通知
2.默认发送接收是同步的,即通知发送后,在通知接收方法完成前,通知发送方法之后的代码会等待执行,(若想不优先通知接收方法,可以用以下方法通过设置postStyle)
[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle];
方式一:
-(void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
方式二:
-(id
通知和runloop的关系
postingStyle:传值不同则runloop处理时机不同
NSPostWhenIdle:runloop空闲时处理通知回调方法
NSPostASAP:runloop能处理时就处理
NSPostNow:runloop立刻处理
图片缓存 存入library cache (注意:NSCacheDirectory 不可document)
Documents:只有用户生成的文件、其他数据及其他程序不能重新创建的文件,应该保存在
Library:存储程序的默认设置或其它状态信息,这个目录下有两个子目录:Caches 和 Preferences。
Caches:此目录下文件不会在应用退出删除,可以重新下载或者重新生成的数据应该保存在 /Library/Caches 目录下面。举个例子,比如杂志、新闻、地图应用使用的数据库缓存文件和可下载内容应该保存到这个文件夹。
Preferences:目录包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。
tmp:只是临时使用的数据应该保存到 /tmp 文件夹。尽管 iCloud 不会备份这些文件,但在应用在使用完这些数据之后要注意随时删除,避免占用用户设备的空间。iPhone在重启时,会丢弃所有的tmp文件。
RunLoop (主线程)
运行循环 (持续运行 处理事件 节省CPU资源(没事则休息))
UIApplicationMain 函数一直没有返回 保持程序持续运行
每个线程都有一个RunLoop
子线程RunLoop不用创建 而是调用 [NSRunLoop currentRunLoop];获取
RunLoop{ mode [sourceobservertimer ]}
mode:默认5个 kCFRunLoopDefaultMode主线程 UITrackingRunLoopMode界面触摸滑动 NSRunLoopCommonMode通用占位并不是一种mode 等
1.timer:CFRunLoopTimerRef 相当于定时器NSTimer
[[NSRunLoop CurrentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]//只在主线程下运行timer 但是拖动的时候不运行 使用NSRunLoopCommenModes 可以 其包括default和 tracking
[NStimer scheduledTimerWithTimeInterval:];会自动添加进当前runloop NSDefaultRunLoopMode
2.source:输入源
函数调用栈分:source0(非基于Port) source1(基于Port,接收分发系统事件)
3.observer:观察者
监听RunLoop状态 CFRunLoopAddObserver
整体过程:
数据持久化
FMDB的事务:大量数据同时插入时使用。
insert into语句很耗时,普通时,以文件的形式存在磁盘,每次访问会打开文件夹,大量数据时会很慢;以事务形式提交,开始事务后,进行大量的操作语句都保存在内存中,当提交时才全部写入数据库,只打开一次,如果操作错误还可以回滚。
AOP 面向切片编程
method swizzling 方法替换 基于runtime知识
Aspects 方法勾取 (暂时无法勾类方法 自行处理)
排序
冒泡排序:从左到右,数组中相邻的两个元素进行比较,将较大的放到后面。
for(int i=0,i for(int j=0,j if (a[j]>a[j+1]){交换j,j+1元素位置}//把大的移到后面 } } 选择排序:从第一个位置开始比较,找出最小的,和第一个位置互换,开始下一轮。 for(int i =0,i for(int j=i+1,j if (a[i]>a[j]){交换i,j元素位置}//把大的移到后面 } } 交换元素位置方法: 1)临时变量交换(最差):int temp=a; a=b; b=temp; 2)相互加减:a=a+b; b=a-b; a=a-b; 3)按位异或^(最好):a=a^b; b=a^b; a=a^b; 加密 哈希(散列)函数:MD5,SHA 对称加密算法:AES(速度快,安全级别高),DES(速度快,加密大量数据),3DES(基于DES,一块数据用三种不同密钥加密三次,加密强度大),RC2,RC4,RC5 非对称加密算法:RSA,ECC,D-H MD5对输入信息生成32个字符(128位散列值),加密后结果不可逆,同样数据加密结果一致。 MD5加密容易破解,用加盐(salt)的方式,即在要加密字符的固定位置插入随机串再进行加密,也不安全,容易找到规律 AES 只有一个密钥,加解密都用它,不安全,因为会传递密钥(明文)。加密速度快效率高,但是成本高,每人需要使用不同的加密密钥,管理成本高。 RSA加密与签名 非对称 公钥私钥是一对。算法强度复杂,安全性高,保密性好。 公钥:加密, 验证 。 公开让大家都可以来加密数据 私钥:解密, 签名 。 签名为了唯一标记 服务端 不可互换(原理可换,但彼此要求不同,且为了安全且高效,不可互换) 公钥在客户端,用于加密。 如:A->B发送数据,A B都产生一对公钥私钥,A把公钥给B,B把公钥给A,A->B,A用B的公钥加密发送给B,B用自己的私钥解密,其他收到消息的都无法解密,只有B有私钥可以解密。 .p12 二进制编码密钥 .cer 二进制编码证书,公钥 .pem base64编码密钥 iOS上传应用 fairPlay签名 Apple ID 是应用的唯一识别符,由team id 和 bundle id组成。 Certificates证书 创建它需要上传签名证书申请Certificate Signing Request,可通过keychain创建此CSR文件,其中会包含一个私钥。 之后在开发者网站创建Certificate。 (开发、分发、通知证书)APNS:正、测 APNS证书:他的CSR必须和AppleID联系生成APNS,下载后在Keychain中打开,以.p12文件导出,上传到推送服务器。此文件知道应该推送到哪个APP Devices 100个 Provisioning profiles 配置文件 将Apple ID,证书,设备联系起来,在开发者网站创建配置文件,在Xcode内下载。 使用:在Xcode中,添加证书,更新配置文件,选择想要的配置文件。 数组 链表 数组:数组元素在内存上连续存放,通过下标查找元素速度快,插入、删除需要移动大量元素,比较适用于元素很少变化的情况 链表:链表元素在内存上不是顺序存储,链表需从第一个开始一直找到需要的元素位置查找慢,插入,删除只需对元素指针重新赋值,效率高。 哈希表:具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势 Hash 哈希 散列表 哈希表是一种特殊的数据结构,在数组和链表的基础上演化来,具有两者的优点。可快速定位到想要查找的记录,而不是与表中记录的关键字进行比较来进行查找。函数映射思想,将记录的存储位置与记录的关键字关联起来,从而快速定位查找。 如:字符串数组中,查找某个字符串。 数组遍历,时间复杂度O(n);hash查找,时间复杂度O(1)。hash表通过映射函数f:key->address,将记录映射到表中的存储位置。 哈希算法:如MD5,SHA等。将任意长度原数据映射为固定长度的二进制串,映射规则为哈希算法(直接定址法,平方取中法,折叠法,除留取余法) 哈希值:映射结果的二进制值为哈希值 特点:1)结果不可逆,不可反推出原数据 2)输入数据敏感,原数据修改很小结果也会大不同 3)哈希冲突概率很小,不同原数据的哈希值相同概率非常小 4)效率高,查找,插入,删除时间复杂度O(1),需要一秒内查找上千条数据,不需遍历,明显比树快,首选哈希表 优点:4) 缺点:基于数组,难于扩展。哈希表被填满时,性能下降快,所以必须清楚要存储的数据量或者定期把数据移到更大的哈希表中。 hash表空间远大于实际存储数据,造成空间浪费,但是空间过小会造成哈希冲突。因此hash表的大小的确定,在不知道存储个数的情况下,需要动态维护hash表容量,此时可能需要重新计算hash地址。 哈希冲突:因为我们是用数组大小对哈希值进行取模,有可能不同键值所得到的索引值相同,这里就是冲突。(即不同的值存在相同索引下的位置造成冲突) 解决:1)开放定址法:哈希算法映射出的哈希值相同时,如果此位置已经存值,则向下寻找为空的位置存放 2)链地址法:采用数组与链表相结合的数据结构,将相同hash地址的记录存入一张线性表中,表头序号为计算得到的hash地址,数组元素为链表结构,存入地址相同的记录,查找时找到位置,再便利链表找到需要的记录。 应用:找出两文件中的重复元素,出现次数最多元素,滤重。(iOS自带NSMutableSet/NSSet滤重) iOS消息转发机制 调用方法不存在,可以给当前对象添加该方法 或者可以在运行期给类添加该方法(类的消息转发机制) 消息转发是运行时进行,两阶段: 1)检查接收者,看是否可通过runtime动态添加一个方法 2)完整的消息转发机制,先检查是否有其他对象可以处理该消息,若没有,就把该消息的全部信息封装到NSInvocation对象中,看此对象是否可以处理,若还是如法处理,就查看继承树的类是否能够处理,如果到NSObject之前都无法处理该消息,那么最后会调用NSObject类的doesNotRecognizeSelector来抛出异常,表示嗲用的方法不对 1.动态方法解析 对象收到无法处理的方法,会调用下面的方法 // 类方法专用 + (BOOL)resolveClassMethod:(SEL)sel // 对象方法专用 (BOOL)resolveInstanceMethod:(SEL)sel 在此方法中,需给对象所属类动态添加一个方法,并返回YES,表明可以处理 2.第一步后,方法还是无法处理,则会调用下面方法,查询是否有其他对象可以处理该消息 (id)forwardingTargetForSelector:(SEL)aSelector 重定向方法,需返回一个能处理该消息的对象 3.前两步后,还是无法处理消息,那么会做最后尝试,先调用methodSignatureForSelector:获取签名,然后forwardInvocation:进行处理(继承树中),这一步可直接转发给其他对象,同第二步,但很少人这样做(因为消息处理越靠后则处理消息的成本越大,性能开销越大)所以这种方式下会改变消息内容,比如增加参数,改变选择子等。 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector - (void)forwardInvocation:(NSInvocation *)anInvocatio category和extension extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。 但是category则完全不一样,它是在运行期决议的。 就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。 category中使用@property 使用@property可以让编译器自动帮我们生成实例变量的setter/getter方法的声明,方法实现需要在分类中自己实现,如果主类的成员变量在.h,则直接在分类实现setter,getter方法即可;如果主类的成员变量在.m,则需要使用关联对象实现。 @protocol中使用@property 在@protocol中使用@property在编译阶段,编译器会自动帮我们加上setter/getter的声明,但是实现还是需要采用该协议的类去写。因此在协议中使用@property主要是希望采用该协议的类能够实现该属性 响应链 iOS手指按下到松开经历了什么 点击屏幕,封装为IOHIDEvent对象(IOKit.framework)——match port转发SpringBoard(SpringBoard.app)——转发给当前APP的主线程(xxx.app进程)——RunLoop的Source1触发,监听特定的match port消息( APPDelegate)——Source0回调,sendEvent:(UIApplication)——根据UIEvent的UITouch确定hitTest view 和pointInside:withEvent:和gestureRecognizers(UIWindow)——两者识别后不再收到此UITouch事件——gestureRecognizers识别后target的action触发,事件响应;hitTest view不响应四个方法(began,moved,ended,cancelled),UITouch会沿着responderChain向上传递,直到UIApplication,被吃掉——事件响应完毕,主线程的RunLoop开始睡眠,等待下一个事件。 在iOS中只有继承了UIResponder的对象才能接收并处理事件称为响应者对象 UIApplication,UIViewController,UIView都继承自UIResponder,因此他们都是响应者对象, 都能够接收并处理事件。(button) 第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。 iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。 UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。 UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。 注: hitTest:withEvent:未找到第一响应者,或第一响应者及响应者链上到UIWindow和UIApplication都不能处理,则此事件会被丢弃; hitTest:withEvent:会忽略的有(hidden=YES,userInteractionEnabled=YES,alpha<0.01以及子视图超过父视图部分被触摸)(父视图的clipsToBounds 属性为NO)。 delegate调用过程 const static extern const是只读的意思,只在声明中使用 static规定作用域和存储方式。对于全局变量,作用域为在此文件中可见,对于static函数也是在当前模块内函数可见;对于局部变量,static规定其为静态存储方式,每次调用的初始值为上次调用的值,调用结束后存储空间不释放。即让局部变量只初始化一次,在程序中只有一份内存,延长局部变量的生命周期,程序结束才销毁。 static const,既是只读的,又是只在当前模块中可见的,两者的合集。例如:定义一个只能在当前文件访问的全局常量 static NSString * const test = @"abc"; extern 主要用来修饰全局变量,原理为先在本文件中查找,查不到再到其他文件查找。例如:常把extern和const联合使用在项目中创建一个文件,这个文件文件中包含整个项目中都能访问的全局常量。extern NSString * const NOTIFICATION_NAME; static修饰全局变量后,再用extern修饰,全局变量的作用域依然是此文件内使用,不会因extern改变。 MRC下的实现 重写setter getter方法 @property(nonatomic,strong)NSString*name; //一旦重写了getter.setter方法, //必须使用@synthesize variable = _variable来区分属性名与方法名 @synthesize name=_name;//对应方法 给成员变量重新命名 1)如果指定了成员变量的名称,会生成一个指定的名称的成员变量, 2)如果这个成员已经存在了就不再生成了. 3)如果是 @synthesize foo,没有指定成员变量的名称会自动生成一个属性同名的成员变量 不会自动合成成员变量的情况:1)同时重写了 setter 和 getter 时 2)重写了只读属性的 getter 时 3)使用了 @dynamic 时 4)在 @protocol 中定义的所有属性 5)在 category 中定义的所有属性 6)重载的属性(当你在子类中重载了父类中的属性,你必须 使用 @synthesize 来手动合成ivar) 手动重写setter和getter方法,则系统不会生成成员变量,两种处理1)手动创建成员变量,.m文件添加_foo的成员变量 2)使用@synthesize foo = _foo; ,关联 @property 与 ivar -(void)setName:(NSString*)name{ if(_name!=name){ [_name release]; _name=[name retain]; } } -(NSString*)name{ return _name; } 生命周期 浅拷贝 深拷贝 浅拷贝:不拷贝对象本身,只拷贝指向对象的指针 深拷贝:直接拷贝整个对象内存到另一个内存 “=”号赋值的对象,基本都是浅复制,内存地址一样。 NSObject对象要使用copy,mutableCopy,类必须实现NSCopying 或NSMutableCopying协议,只是一般系统容器类已经实现了这些方法。 原则:“修改新(旧)对象,不影响旧(新)对象” copy:创建的是不可变副本 mutableCopy:创建的是可变副本 使用copy,mutableCopy是浅拷贝还是深拷贝?是否会创建新对象?遵守原则,由程序运行环境造成。 copy的内存管理: 浅不产生新对象,所以系统会的以前的对象进行一次retain 深会产生新对象,系统不对对以前的对象进行retain 用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。 copy与block的配合使用: block默认存储在栈中,栈中的block访问到的外界对象,不进行retain block若在堆中,block访问了外界的对象,会对外界对象进行一次retain 因block执行时间不确定,若block里的外界对象被提前释放,这时block执行了,会造成野指针,崩溃,所以一般用copy关键字修饰block。 使用copy保存block,可保住block中,避免在block调用时,外界的对象已经被释放。 copy strong retain weak assign 区别在于是否开辟内存?是否对内存地址有引用计数增加?property修饰符是在被赋值时起作用 eg:NSMuatbleString 非容器可变变量(还有3种情况) copy:开辟内存,并指向(指向新内存),引用计数不加 strong:地址不变,并指向,引用计数+1 weak:地址不变,指向,但是不会增加计数,为0时weak会将内存值设为nil 注:初始化和设为nil都可以将指针所指向的数据地址引用计数减少1 (引用计数为0则释放) 结论:copy会重新开辟新的内存来保存一份相同的数据。被赋值对象和原值修改互不影响。strong和weak虽然都指向原来数据地址,原值修改的时候storng和weak会随之变化。区别是前者会对数据地址进行引用计数+1防止原地址值被释放,但后者不会,当其他值都不在指向值地址时,值地址被释放,weak的值也就是为nil了。我们称会对数据地址增加引用计数的为强引用,不改变引用计数的为弱引用 assign:同weak,指向但计数不增加,为0时不会将内存值设为nil,内存未被重写前依旧可以输出, 但一旦被重写将崩溃 retain:同strong,ARC之后带出了strong和weak,之前只有retain,copy,assign eg:NSMutableArray 容器可变变量 结论:容器可变变量中容器本身和非容器可变变量是一样的,copy深拷贝,strongMArr,weakMArr和assign都是浅拷贝 容器可变变量中的数据在拷贝的时候都是浅拷贝 eg:NSString 非容器不变变量 结论:非容器不可变量除了copy其他特性同非容器可变变量,copy是浅拷贝。因为不可变量的值不会改变,所以不用再开辟地址 eg:NSArray 不可变容器变量 结论:容器本身都是浅拷贝包括copy,同NSString,容器里面的数据都是浅拷贝,同NSMutableArray 结论: 可变变量中,copy是重新开辟一个内存,strong,weak,assgin后三者不开辟内存,只是指针指向原来保存值的内存的位置,storng指向后会对该内存引用计数+1,而weak,assgin不会。weak,assgin会在引用保存值的内存引用计数为0的时候值为空,并且weak会将内存值设为nil,assign不会,assign在内存没有被重写前依旧可以输出,但一旦被重写将出现奔溃 不可变变量中,因为值本身不可被改变,copy没必要开辟出一块内存存放和原来内存一模一样的值,所以内存管理系统默认都是浅拷贝。其他和可变变量一样,如weak修饰的变量同样会在内存引用计数为0时变为nil。 容器本身遵守上面准则,但容器内部的每个值都是浅拷贝 delegate应使用weak或assign 不用strong 防止循环引用。c.delegate=b时,若用strong修饰,其实是对b的引用计数+1。 SDWebImage 1.提供一个UIImageView的category用来加载网络图片,并对网络图片的缓存进行管理 2.异步下载网络图片 3.采用异步方式,使用memory+disk来缓存网络图片,自动管理缓存 4.同一个URL不会被重复下载(把对应的事件以字典的方式存储到内存,放置重复发送下载图片的请求),失效URL不会被无限重试(失效URL被加入列表,所以不会重复请求) 5.耗时操作在子线程,不堵塞主线程 1)先去内存缓存查找, 2)没有再根据URL生成key,去磁盘缓存查找(磁盘查找用NSOperation操作,根据key在磁盘目录下查找图片文件,如查到加入内存,空闲内存过小先清内存),(保存一周会清理)key是图片地址即:图片URL(图片缓存实质是用图片的URL进行MD5加密后+. +URL的扩展名组合作为图片缓存的文件名) 3)还没有再去下载,采用多线程异步下载,使用NSOperation设置最大并发数, 4)下载完成后对图片进行解码处理,在NSOperationQueue内处理, 5)解码完成回调代理,主线程展示图片,内存缓存硬盘缓存同时保存(写入硬盘也要有用NSInvocationOperation), 6)初始化是注册各种通知内存警告或退到后台时清理内存缓存,应用结束清理过期图片 1.入口setImageWithURL:placeholderImage:options:会先把placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。 2.进入SDWebImageManager-downloadWithURL:delegate:options:userInfo:交给 SDImageCache从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo: 3.先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调imageCache:didFindImage:forKey:userInfo:到 SDWebImageManager。 4.SDWebImageManagerDelegate 回调webImageManager:didFinishWithImage:到UIImageView+WebCache 等前端展示图片。 5.如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。 6.根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation进行的操作,所以回主线程进行结果回调notifyDelegate: 7.如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate回调imageCache:didFindImage:forKey:userInfo:进而回调展示图片。 8.如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo: 9.共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。 10.图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。 11.connection:didReceiveData:中利用 ImageIO 做了按图片下载进度加载效果。 12.connectionDidFinishLoading:数据下载完成后交给 SDWebImageDecoder 做图片解码处理。 13.图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。 14.在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo:回调给 SDWebImageDownloader。 15.imageDownloader:didFinishWithImage:回调给 SDWebImageManager 告知图片下载完成。 16.通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。 17.将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。 18.SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。 19.SDWebImage 也提供了UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。 20.SDWebImagePrefetcher 可以预先下载图片,方便后续使用。 曝光量统计,偏移量 - (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath]; cell.textLabel.text=_dataArray[indexPath.row]; if (tableView.contentOffset.y == 0 && cell.frame.origin.y < self.view.frame.size.height) {//刚加载出来时 NSLog(@"+++++++ %@",_dataArray[indexPath.row]); }else if(tableView.contentOffset.y!=0) {//上下滑动时 NSLog(@"+++++++ %@",_dataArray[indexPath.row]); } returncell; } 预加载问题,触发时机 collectionView:(UICollectionView*)collectionView willDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath*)indexPath或者scrollViewDidScroll中判断是否还有更多数据,否则再去预加载 注:如果已经触发预加载,上次网络正在请求时,又一次触发请求则取消第二次,防止重复请求。 应用间跳转实现 遵守哪些协议 A->B A跳转到B B可以被其他app调起的条件:手机安装B;B注册了自己的URL Scheme协议(唯一标识)(info.plist中URL types里添加) A可以调起其他app的条件:手机安装A;A添加了被跳转方的scheme白名单(info.plist中LSAplicationQueriesScheme里添加);A添加了调起代码 马甲包上架 为代码虚拟化,字符串加密,代码符号逻辑混淆,防范编译,防调试防篡改等深度混淆整体加固加密保护, 防止应用程序遭到逆向破解,二次打包签名。防止使用逆向编译工具反编译出核心代码后被窃取,程序逻辑破解,恶意篡改代码及文件进行盗版和植入广告等二次打包和重签名行为,API接口暴露,拦截请求恶意攻击服务端,篡改接口数据重复请求 OC与Swift的区别 Swift:容易阅读,结构简单,文件没有了.h.m,类型安全,速度快,运行性能高 swift是oc上提出的,有oc没有的类,如:元组,有泛型 swift定义的常量与变量是没有值的,所以引入了optional可选概念,若没有值,就用optional,比OC中指针设为nil安全 swift的?和!用来标记变量是否可选,!表示可选变量必须保证转换能够成功,?转换不成功不报错,会设为nil,如果转化成功要使用该变量需要后面加!修饰。 swift let声明的常量不可变,var声明的变量可以改变 swift中继承里写final防止被重写,as类型的转换 OC:OC动态性语言,将数据类型的确定由编译时推迟到运行时,swift是静态行语言 bugly里面经常崩溃的点 如何避免 一次性避免 小广播语音上传压缩 加密 蓝牙开发 一般来说我们使用的iPhone都是做centralManager的,蓝牙模块是peripheral的,所以我们是want datas,需要接受数据。 1.判断状态为powerOn,然后执行扫描 2.停止扫描,连接外设 3.连接成功,寻找服务 4.在服务里寻找特征 5.为特征添加通知 5.通知添加成功,那么就可以实时的读取value[也就是说只要外设发送数据[一般外设的频率为10Hz],代理就会调用此方法]。 6.处理接收到的value,[hex值,得转换] 之后就自由发挥了,在这期间都是通过代理来实现的,也就是说你只需要处理你想要做的事情,代理会帮你调用方法。[别忘了添加代理] 注意: 不是每个特征都可以通过回调函数查看写入状态的。如:immediateAlertService的alertLevelCharacteristic,不能使用普通CBCharacteristicWriteType.WithRespons来写入,只能用CBCharacteristicWriteType.WithOutRespons,这样就可以通过回调函数查看每一个Characteristic的状态检查。 iOS中当发生读写交互时,系统才会和外设进行绑定操作。 想获取蓝夜的广播包didDiscoverPeripheral:advertisementData:方法的advertisementData并非广播包,应让硬件将数据写入kCBAdvDataManufacturerData字段里。