0%

什么是Block

block又叫块对象, 是MacOS X 10.6 以及 iOS4.0 平台下都可以使用的功能,block不是oc的实现, 而是c语言的实现; 与其他编程语言中的闭包

如何定义一个块对象

1
^(返回值类型)(参数列) {表达式主体}

无参数无返回值

1
2
3
4
void(^testBlock)(void) = ^(void){
NSLog(@"无参数无返回值");
}
testBlock();

无参数有返回值

1
2
3
4
int(^testRetunValueBlock)(void) = ^(int){
return 100;
}
int value = testRetunValueBlock();

有参数无返回值

1
2
3
4
void(^ testParamsBlcok) (NSString *) = ^(NSString *string){
NSLog(@"%@",string);
};
testParamsBlcok(@"hhh");

有参数有返回值

1
2
3
4
NSString * (^tempBlock)(NSString *) = ^(NSString *string){
return [NSString stringWithFormat:@"你最帅,%@",string];
};
NSLog(@"%@",tempBlock(@"两好三坏"));

使用typeof定义

1
2
3
4
typedef int (^MyBlock)(int , int);
@property (nonatomic,copy) MyBlock myBlockOne;
self.testBlock = ^int (int ,int){
}

Block中变量行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
void myFunc(int m, void (^b)(void) {
printf("%d: ",m);
b();
}
int glob = 1000;
int main (void) {
void (^block)(void);
static int s = 20;
int a = 20;
block = ^{ print("%d, %d, %d \n",glob, s, a);};
myFunc(1,block);
s = 0;
a = 0;
glob = 5000;
myFunc(2,block);
testblock = ^{ print("%d, %d, %d \n",glob, s, a);};
myFunc(3,testblock);
return 0 ;
}
//输入结果:
1: 1000, 20,20
2: 5000, 0, 20
3: 5000, 0, 0

第一个block定义后, block中包含外部变量 globmain函数中局部变量,a,s;然后将block 作为参数传递给myFunc. 然后改变glob,a,s的值后再次调用myFunc. 在testBlock定以后, 调用myFunc; 对比上面三个输出, block 只在代码块中保存自动变量的值,block 复制了block内部会使用到的变量;即使变量的值发生了变化, block在使用的时候也不会知道这个值变化; 那如果我们想知道这个值得变化或者想要修改这个变量该怎么办呢, 我们可以使用 __block对想要使用的变量进行修饰, 如果我们使用了__block修饰,name就是引用了这个变量的地址; 而且block只能访问,不能修改全局变量; [TOC] block使用变量相当于拷贝了一份变量在堆内存中,要注意对变量的影响

之前iOS的一个 git 仓库看到一个面试问题, 一个 NSObject 对象占用多少内存, 看到这个面试题以后我想不是4个就是8个嘛, 因为之前我打印过 64位设备下 NSString 对象的内存大小就是8; 可是答案却有一点出乎意料, 是16, 于是我就找了些资料进行了一下深入的研究, 果然…..

阅读全文 »

在 OC 开发中, 数组是我们经常会用到的数据结构;而对数据进行遍历是很常见的操作, OC也为我们提供一下几种常用的操作:

  • for 循环,普通遍历
  • for-in,快速 遍历
  • Block
  • 枚举器NSEnumerator

之前大部分情况下都是用for循环进行普通遍历, 但前几天我使用 for-in 去遍历一个数据的时候却报了异常;

代码如下:

1
2
3
4
5
6
7
8
9
NSMutableArray *testArray = [NSMutableArray arrayWithObjects:@"耐克",@"阿迪达斯",@"李宁",@"匡威",nil];
for(NSString *temp in testArray)
{
if{[temp isEqualToString:@"阿迪达斯"])
{
NSLog(@"我不喜欢");
[testArray removeObjective:temp];
}
}

报错信息如下:

1
Collection <__NSArrayM: 0x100601400> was mutated while being enumerated.

但是如果我们去使用 for 进行普通遍历, 然后对数组进行修改或者删除就不会有这个错误;

快速枚举的过程中不允许改变容器中的对象, 即: 不允许删除或者增加枚举容器中的对象, 因为对于一个既定的枚举器来说, 其内部的对象都已经对其进行了枚举排序, 如果你私自添加或者删除对象, 那么其既定的枚举序列就发生了改变, 而这种改变时系统不能识别的, 所以 OC中不允许这么做. 但有时候我们也必须在快速枚举的过程中删除一些枚举容器中的对象, 这时候, 我们就需要在删除完一个对象的时候, 对其进行break.

iOS 中 KVO, 是key-value-observing 的缩写, 是Objective-C 对观察者设计模式的一种实现,类似观察者设计模式的还有NSNotificationCenter,不过一个是一对一(KVO),一个是一对多(NSNotificationCenter) ; 一般继承自NSObject 的对象都支持KVO. 日常开发中我们常常会监听数据模型的变化, 从而达到根据数据模型的修改对视图进行更新的要求;

KVO 常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
注册监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者在回调函数中会被回传
监听的属性路径为keyPath支持点语法的嵌套
监听类型为options支持按位或来监听多个事件类型
监听上下文context主要用于在多个监听器对象监听相同keyPath时进行区分
添加监听器只会保留监听器对象的地址,不会增加引用,也不会在对象释放后置空,因此需要自己持有监听对象的强引用,该参数也会在回调函数中回传
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
eg:
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
/*
删除监听器
监听器对象为observer,被监听对象为消息的发送者即方法的调用者,应与addObserver方法匹配
监听的属性路径为keyPath,应与addObserver方法的keyPath匹配
监听上下文context,应与addObserver方法的context匹配
*/
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
/*
监听器对象的监听回调方法
keyPath即为监听的属性路径
object为被监听的对象
change保存被监听的值产生的变化
context为监听上下文,由add方法回传
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;

举个栗子 🌰

假如我的一个数据模型(student)的属性(age)发生了变化, 我需要在对应的视图进行修改这个属性的显示;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@property (nonatomic, strong) StudentModel  *student;
- (void)viewDidLoad {
  [super viewDidLoad];
  _student = [[Student alloc] init];
  _student.age = 10;
  [_student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
  [self requestData];
}
- (void)requestData{
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       NSInteger age = [(NSDictionary *)responseObject[@"age"] integerValue];
       _student.age = age;
   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
   }];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
  if ([keyPath isEqualToString:@"age"] && object == _student){
    NSLog(@"当前年龄是: %ld",_student.age);
}
}
- (void)dealloc{
[_student removeObserver:self forKeyPath:@"age"];
}

实现原理 🚀

Apple 使用 isa 混写技术实现 KVO , 当观察对象 testClass的时候, KVO 会动态创建一个继承自testClass的类 NSKVONotifying_testClass 并重写监听属性的 setter 方法,在调用 setter 的前后, 会观察对象属性的更改情况;子类拥有自己的 set 实现,

1
2
3
[self willChangeValueForKey:@"value"];
_value = value;
[self didChangeValueForKey:@"value"];

所以, 通过 set方法进行赋值, 通过 KVC setValue:forKey:两种方式都可以对 KVO 生效; 但是如果想通过对成员变量直接赋值的话, 需要手动添加 KVO 才可以生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   QKTestObject *obj = [[QKTestObject alloc] init];
QKTestObserver *observer = QKTestObserver.alloc.init;
// 通过 KVO 监听 obj 的变化
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
// 通过 setter 修改 value, 监听生效
// obj.value = 2;
// 通过 kvo 修改 value , 监听生效
// [obj setValue:@4 forKey:@"value"];
// 通过成员变量赋值. 未执行监听方法中的输出
/*
成员变量需要手动 添加 KVO , 监听才会生效
[self willChangeValueForKey:@"value"];
_value += 1;
[self didChangeValueForKey:@"value"];
*/
[obj changeValue];

代码地址:

扩展

用途

  • 声明私有属性
  • 声明私有成员变量
  • 声明私有方法

特点

注意与 category 的区别 * 编译时决议 * 只以声明的形式存在,多数情况下寄生在宿主类的. m中 * 不能为系统类添加扩展

代理

  • 准确的来说是一种软件设计模式, 代理模式
  • iOS 中, 系统为我们提供了@ protocol 形式
  • 代理是一对一的
  • 协议有必须要实现的(@require), 不惜要实现的(option)
  • 一般用 weak 来规避循环引用

通知

  • 使用观察者模式来实现用于跨层传递消息的机制
  • 传递方式 一对多 通知

分类的好处

用分类声明一些私有方法;
分类一些体积庞大的类文件,eg: AppDelegate, 组织代码更灵活
把 framework 的私有方法公开

分类的特点

  • 运行时决议, 在 runtime 添加到类中, 在编辑的时候分类会将分类方法作为一个数组,动态的添加到宿主类中, 如果存在同名的扩展方法, 那么最后一个参与编译的方法生效并覆盖之前的同名方法(但是还是实际存在的,不过优先级没有分类中的高), 但是可以通过一些代码决定调用最后一个参与编译的方法还是调用其他同名方法memcopy(array->lists, addedLists,addedCount *sizeof*array()->lists[0])) 覆盖宿主 方法的原因,;
  • 为系统类添加分类
  • 名字相同的分类会引起编译报错

分类中可以添加哪些内容

  • 可以添加实例方法
  • 类方法
  • 协议
  • 属性 只声明了 set get , 并没有在分类中添加实例变量
  • 添加分类的实例变量, 需要通过关联对象进行设置

分类的结构体构成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//分类文件
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 protocol_list_t *instanceProperties;
method_list_t *methodsForMeta(bool isMeta) {
if(isMeta) return classMethods;
else return instanceMethods;
}
protocol_list_t *propertiesForMeta(bool isMeta) {
if(isMeta) return nil;
else return instanceProperties;
}
}

分类的加载调用栈

  1. _objec_init
    入口调用
  2. map_2_images
    数据镜像映射之类的吧, 我也不怎么懂
  3. map_images_nolock
    数据镜像映射之类的吧, 我也不怎么懂
  4. read_images
    objec 源代码中, map_images_noloc会调用此方法, 将读取到的数据镜像,将解析到的信息(方法和协议)传递给remethodizeClass(猜的), 添加到类和类的元类上
  5. remethodizeClass
    整合class 中的所有分类, 进行操作;
    此过程会将分类的属性,协议,方法都添加到类中;
    下面会有详细的说明关于分类是如何加载的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static void remethodizeClass(Class cls)
    {
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
    isMeta = cls->isMetaClass();
    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
    if (PrintConnecting) {
    _objc_inform("CLASS: attaching categories to class '%s' %s",
    cls->nameForLogging(), isMeta ? "(meta)" : "");
    }
    attachCategories(cls, cats, true /*flush caches*/);
    free(cats);
    }
    }

分类是如何加载的

remethodizeClass

方法覆盖

我们上面说了一些category原理, 如果存在同名方法的话, 宿主类中的方法会被覆盖掉, 那么我们如何调用宿主类中被覆盖的方法呢?
刚才说了, 并不是被覆盖掉了, 而是优先级的问题, 我们只要顺着方法列表找到最后一个对应名字的方法就可以啦~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];
if (currentClass) {
unsigned int methodCount;
Method *methodList = class_copyMethodList(currentClass, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
for (NSInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
encoding:NSUTF8StringEncoding];
if ([@"printName" isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
}
}
typedef void (*fn)(id,SEL);
if (lastImp != NULL) {
fn f = (fn)lastImp;
f(my,lastSel);
}
free(methodList);
}

通过关联对象给分类添加成员变量

关联对象由AssociationsManager管理并在 AssociationsHashMap中存储;
AssociationsHashMap 是一个全局容器, 通过一个映射表来操作到具体不同的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "MyClass.h"
@interface MyClass (Category1)
@property(nonatomic,copy) NSString *name;
@end
#import "MyClass+Category1.h"
#import <objc/runtime.h>
@implementation MyClass (Category1)
+ (void)load
{
NSLog(@"%@",@"load in Category1");
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
"name",
name,
OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
@end

关联对象实现原理
关联对象实现原理

CALayer -> content显示内容, 实际是合成了一个个位图; 用来展示 我们平常所说的掉帧也是因为位图合成后未来得及显示绘制造成的 view 提供内容, 负责处理触摸事件,参与视图响应链 layer, 负责内容上的显示, contents; 之所以这样设计是因为单一元件负责单元任务, 即单一原则;

事件传递:

1
2
3
4
//返回响应事件的视图
-(UIView *)hitTest:(CGPoint)point WithEvent:(UIEvent *)event;
//判断是否在响应区域内
-(BOOL)pointInside(CGPoint)point WithEvent:(UIEvent *)event;

事件传递流程:

如何寻找合适的响应者来处理事件: 1. 判断主窗口是否可以接受触摸事件 2. 判断触摸点是否在自己的区域内( pointInside: withEvent:) 3. 子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤) 事件传递给一个控件就会调用hitTest: withEvent:, 通过这个特性, 可以重写hitTest: withEvent方法返回指定的 view 用来接收事件的处理; eg: 在实际中响应事件是的viewB1,虽然点击的位置在 viewA2ViewB1的重复处, 但是由于 view B1 的视图索引在 viewA2 之上, 所以响应事件的是 ``viewB1`;

阅读全文 »

之前面试的时候会有很多人问道 UITableviewCellde 重用机制, 大部分回答都是

在缓存池初中通过 dequeueReusableCellWithIdentifier:CellIdentifier 方法取出可重用的 cell, 在一个 cell 离开屏幕显示的范围内, 会自动被加入到缓存池中,然后在缓存池中取出一个 cell, 放到当前屏幕上;用重用机制会节省性能,避免出现一些因为网络因素而造成的卡顿现象。

复用机制避免了大量的初始化过程, 我们可以节约很大一部分资源; 下面我们来试着模仿 tableview 创建一个复用视图吧~

实例化一个重用池;

新建一个继承自NSObject 的类, reusePool; 在reusePool 中定义三个方法,

  • 获取重用 view -(UIView *)getReuseTableView;
  • 添加 view 到重用池中 -(void)addViewForReusePool:(UIView *)view;
  • 重置重用池 -(void)reset;

在类的.m文件中, 实现刚才定义的几个方法, 并定义两个属性, wattingQueueusingQueue, 作为存放视图的容器; 实现刚才定义的几个方法

阅读全文 »

遇到一个需求, 需要从后台返回的消息或者交易列表中根据日期进行分组排序, 界面类似 根据日期或者消息类型,等等进行排序; 数据格式类似下面这种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[
{
"date":"2018-12-12",
"titleInfo":"xxxxx",
},
{
"date":"2018-12-12",
"titleInfo":"xxxxx",
},
{
"date":"2018-12-12",
"titleInfo":"xxxxx",
},
{
"date":"2018-12-12",
"titleInfo":"xxxxx",
},
]
阅读全文 »

喜欢看美剧和电影, 大二以后一直在人人看美剧和电影, 偶尔从射手下载字母来看生肉;好景不长,因为版权问题人人和射手被勒令关停, 我现在还依稀记得当年人人影视和射手字幕网被关停时候的心痛; 买了个 Nas , 发现 Docker 可以安装人人影视客户端, 我说着还得了, 咱赶紧弄一个呀; 网上研究了一下怎么安装, 花了十几分钟装好了; 美美哒,仿佛又回到了看美剧的时候啦; 哈哈哈

安装 Docker

  • 安装Docker, 在群辉的套件中心安装 Docker, 直接安装就好了

安装人人影视

打开 Docker
选择注册表, 输入auska, 下载安装docker-rrshareweb 下载完成后对容器进行设置, 勾选使用最高权限执行容器

  1. 在 fileStation docker 文件夹中新建子文件夹, eg:rrshare
  2. 给 人人影视容器 设置卷,点击添加文件夹, 选择刚刚创建的 rrshare; 然后选择一个文件夹用来存放下载后的资源, 我选择的是 video
  3. 设置端口, 可以默认, 也可以自定义一个

  • 设置环境改变量
    通过 ssh 登录 Nas, 在终端执行 cat 命令获取UGID 和 PGID, 选择 admin的就可以1204,100

到这就完成安装了,不要忘记点击应用 然后在浏览器中输入你的群辉 IP, 端口号是刚才设置的端口号; 如果需要解锁, 默认解锁码是123456; tip: 如果不能下载,先停止容器类, 将 P/UGID 设置成 0;