0%

信号量原理篇

源码版本:libdispatch-1173.40.5

信号量的实现主要是创建,销毁,wait和signal,四个方法。

信号量结构体:

1
2
3
4
5
6
7
8
9
DISPATCH_CLASS_DECL(semaphore, OBJECT);
struct dispatch_semaphore_s {
DISPATCH_OBJECT_HEADER(semaphore);
long volatile dsema_value;
long dsema_orig;
_dispatch_sema4_t dsema_sema;
};

typedef semaphore_t _dispatch_sema4_t;

信号量的创建—dispatch_semaphore_create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;

// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. Therefore it is bogus to
// initialize the semaphore with a negative value.
if (value < 0) {
return DISPATCH_BAD_INPUT;
}

dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s));
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_default_queue(false);
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
dsema->dsema_orig = value;
return dsema;
}

可以看到不能传入一个小于0的值。信号量有几个重要的属性:

dsema_value:记录当前计数值,后续的wait和signal函数会改变该值。

dsema_sema:信号量 (真正干事情的)

阅读全文 »

信号量使用篇

操作GCD信号量的函数

在iOS中有三个函数可以操作GCD信号量:

dispatch_semaphore_t dispatch_semaphore_create(long value); 创建一个信号量。

long dispatch_semaphore_signal(dispatch_semaphore_t dsema); 发送一个信号.

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 等待一个信号。

函数说明

  1. dispatch_semaphore_t semaphoreTask1 = dispatch_semaphore_create(0);
    创建一个信号量,并赋予初始值(必须传一个>=0的值)。eg:传入2则表示同时最多两个线程可以访问临界区。

  2. long signalRs = dispatch_semaphore_signal(semaphoreTask1);
    发出一个信号,将信号量加1。如果之前的信号量小于0,说明有等待dispatch semaphore的计数值增加的线程,那么该函数在返回前将唤醒最先处于等待状态的线程。

    返回值:This function returns non-zero if a thread is woken. Otherwise, zero is returned。如果有线程被唤醒则返回一个非0值,否则返回0。

  3. long waitRs = dispatch_semaphore_wait(semaphoreTask1, DISPATCH_TIME_FOREVER);
    将信号量-1。减去1后如果得到的值<0,则该函数在返回前将一直等待信号的发出,线程会被阻塞。减去1后如果得到的值>=0,则正常返回0,不阻塞线程。

    timeout:指定等待的时间。如果线程要被阻塞该值表明将阻塞该线程多久。传DISPATCH_TIME_FOREVER意味着该线程永久等待一个信号的发出。 传一个其他值如:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)),表明将阻塞该线程2s,2s后函数将返回,线程继续执行(不一定就是执行临界区的代码,可以判断如果返回的是非0表明是等待超时了可以做其他处理)。

    返回值:Returns zero on success, or non-zero if the timeout occurred。

eg:使用信号量控制多线程添加数组元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)testSemaphoreLock {
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *arr = [[NSMutableArray alloc] init];
self.semaphoreLock = dispatch_semaphore_create(1);
for (int i = 0; i < 10; i++) {
dispatch_async(globalQueue, ^{
NSLog(@"线程:%@ 执行 i = %d", [NSThread currentThread], i);
long waitRs = dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
NSLog(@"线程:%@,dispatch_semaphore_wait:%ld", [NSThread currentThread], waitRs);
NSNumber *number = [NSNumber numberWithInt:i];
NSLog(@"线程:%@,将要添加:%@", [NSThread currentThread], number);
[arr addObject:number];
long signalRs = dispatch_semaphore_signal(self.semaphoreLock);
NSLog(@"线程:%@,dispatch_semaphore_signal:%ld", [NSThread currentThread], signalRs);
});
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"count:%ld, %@",arr.count, arr);
});
}
阅读全文 »

源码版本:objc4-781,objc-accessors.mm文件中。

原子操作是指操作是不可分割的,要么发生,要么不发生。事物只会处于原始状态和成功状态两种中的一种,不会处于一种半完成状态。

OC 中的atomic修饰符

源码实现:

getter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}

// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;

// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot); //+1
slotlock.unlock();

// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value); //注册到自动释放池中,延迟-1
}

对于none atomic属性,getter方法只是简单的返回对象地址。

对于atomic属性,getter方法会加锁,并返回一个注册到自动释放池里的对象地址。

setter 方法:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}

id oldValue;
id *slot = (id*) ((char*)self + offset);

if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue); //retain新值
}

if (!atomic) {
oldValue = *slot; //获取旧值
*slot = newValue; //赋予新值
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot; //
*slot = newValue;
slotlock.unlock();
}

objc_release(oldValue); //release 旧值
}

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}


void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
阅读全文 »

整了8个小时,基本上算整明白了。

环境:

NSObject.mm源码 版本为objc4-781。

对代码

1
2
3
4
5
6
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}

进行

1
clang -rewrite-objc main.m

得到

1
2
3
4
5
6
7
8
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_lys08y0137d41lbysrxxd0h80000gn_T_main_8ccfab_mi_0);
}
return 0;
}

看一下:

阅读全文 »

本文基于Exposing NSMutableArray 理解和扩展。

C语言中的数组可以说是最基本的数据结构了,其他一些数据结构比如队列,循环队列等都可以基于普通C数组的进一步封装来实现。

普通C数组

数组占据的是一段连续的内存空间,并根据顺序存储数据。

优点:时间效率很高

由于数组中的内存是连续的,因此可以根据下标读写任何元素,时间复杂度为O(1)。

缺点:空间效率较低

创建数组时需要先指定数组的容量大小,然后根据大小分配内存。即使只在数组中存储一个元素也需要为所有的数据预先分配内存。因此它的空间效率不是很高,经常会有空闲区域没有被使用。

另一个明显的缺点是:在下标 0 处插入或删除一个元素时,需要移动其它所有的元素。当数据量很大时,性能就变得很差。

循环队列

阅读全文 »

先上测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
ZAEImageView *img = [ZAEImageView new];
BOOL isMember1 = [img isMemberOfClass:ZAEImageView.class];
BOOL isMember2 = [img isMemberOfClass:UIImageView.class];
BOOL isMember3 = [ZAEImageView isMemberOfClass:ZAEImageView.class];
BOOL isMember4 = [ZAEImageView isMemberOfClass:UIImageView.class];
BOOL isMember5 = [ZAEImageView isMemberOfClass:object_getClass(ZAEImageView.class)];
BOOL isMember6 = [ZAEImageView isMemberOfClass:object_getClass(UIImageView.class)];
BOOL isKind1 = [img isKindOfClass:ZAEImageView.class];
BOOL isKind2 = [img isKindOfClass:UIImageView.class];
BOOL isKind3 = [ZAEImageView isKindOfClass:ZAEImageView.class];
BOOL isKind4 = [ZAEImageView isKindOfClass:UIImageView.class];
BOOL isKind5 = [ZAEImageView isKindOfClass:object_getClass(ZAEImageView.class)];
BOOL isKind6 = [ZAEImageView isKindOfClass:object_getClass(UIImageView.class)];

结果:

结果

OC类型自省的函数如下:

1
2
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;

它们声明在@protocol NSObject中,NSObject类实现了NSObject协议@interface NSObject <NSObject>.

可以看到它们都是实例方法,但是为什么可以发送给类对象?和元类有关.

- (BOOL)isMemberOfClass:(Class)aClass;

Returns a Boolean value that indicates whether the receiver is an instance of a given class.

阅读全文 »

很多时候我们需要一个较复杂场景的Demo工程来验证点东西或者研究某个技术,直接在公司项目工程中实验运行又太慢,并且还可能受到其他代码的影响。而Xcode自身提供的工程模板又太过简单,导致每次都要浪费很多时间编写重复的代码来搭建环境。为了提高效率,我们可以自定义工程模板和文件模板。

如下图,系统提供的默认模板其实都在Xcode.app中,自定义工程模板和文件模板最好的办法就是参考系统的模板。

系统的工程模板位置:

1
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS

系统的文件模板位置:

1
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates

以上是系统默认的模板存储路径,对于自定义的模板则需要存放在其他路径:

自定义工程模板的路径需要存放在:~/Library/Developer/Xcode/Templates/Project Templates/Application/.

自定义文件模板的路径需要存放在:~/Library/Developer/Xcode/Templates/File Templates/.

阅读全文 »

数组截取的一些方法:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
let arr = [1, 2, 3, 4, 5]
//startIndex:0, endIndex:5
print("startIndex:\(arr.startIndex), endIndex:\(arr.endIndex)")

let slice1 = arr.dropFirst(2)
//startIndex:2, endIndex:5
print("startIndex:\(slice1.startIndex), endIndex:\(slice1.endIndex)")

let slice2 = arr.dropFirst().dropLast()
//startIndex:1, endIndex:4
print("startIndex:\(slice2.startIndex), endIndex:\(slice2.endIndex)")

let rs = arr.dropFirst().prefix(upTo: 3)
print(rs) //[2, 3]

let rs1 = arr.dropFirst().prefix(3)
print(rs1) //[2, 3, 4]

let fib = [1, 1, 2, 3, 5, 8, 13, 21]
let slfib = fib[1...5]
//startIndex:1, endIndex:6
print("startIndex:\(slfib.startIndex), endIndex:\(slfib.endIndex)")
print(slfib) //[1, 2, 3, 5, 8]

/**
prefix(upTo end: Int)
表示从集合的索引startIndex开始截取到索引n之间的元素,不包含索引n元素。n取值范围:[startIndex, endIndex],传startIndex则返回空数组,传endIndex则返回整个数组。
*/
let prefixArr0 = arr.prefix(upTo: arr.startIndex)
print(prefixArr0) //[]

let prefixArr = arr.prefix(upTo: 3)
print(prefixArr) //[1, 2, 3]

let prefixArr1 = arr.prefix(upTo: arr.endIndex)
print(prefixArr1) //[1, 2, 3, 4, 5]

let prefixArr2 = slfib.prefix(upTo: 3) //根据上面说明这里只能取[1, 6],取0会崩溃
print(prefixArr2) //[1, 2]

//prefix(_ maxLength: Int),表示截取集合前n个元素,n取值范围:n>=0,当n = 0时,返回空数组;当n>数组个数时,则返回全部。
let preArr2 = arr.prefix(2)
print(preArr2) //[1, 2]

/**
prefix(through position: Int)
表示从集合的索引startIndex开始截取到索引n之间的元素,包含索引n元素。n取值范围:[startIndex, endIndex)。
*/
let preArr3 = arr.prefix(through: 4)
print(preArr3) //[1, 2, 3, 4, 5]

//suffix(from start: Int):从集合的索引n元素开始截取到末尾,包含索引n元素,n取值范围:[startIndex, endIndex],当n为endIndex时,结果为空数组。
let suffixArr0 = arr.suffix(from: arr.startIndex)
print(suffixArr0) //[1, 2, 3, 4, 5]

let suffixArr00 = arr.suffix(from: arr.endIndex)
print(suffixArr00) //[]

let suffixArr = arr.suffix(from: 3)
print(suffixArr) //[4, 5]

let suffixArr2 = slfib.suffix(from: 3) //这里参数也不能取0,因为startIndex==1。
print(suffixArr2) //[3, 5, 8]

//suffix(_ maxLength: Int):截取集合的最后n个元素,n取值范围:n>=0,当n = 0时,返回空数组;当n>数组元素个数时,则返回全部。
let suffixArr1 = arr.suffix(0)
print(suffixArr1) //[]

//使用range时,参数取值范围为[0, 数组个数],且start<=end。当start 或 end 等于count时,右边只能用"<"或省略end了。不能等于,否则越界了。
let rangeArr = arr[3...3]
print(rangeArr) //[4]

let rangeArr1 = arr[2..<4]
print(rangeArr1) //[3, 4]

let rangeArr2 = arr[2...]
print(rangeArr2) //[3, 4, 5]

let rangeArr3 = arr[..<3]
print(rangeArr3) //[1, 2, 3]

let rangeArr4 = arr[5..<5]
print(rangeArr4) //[]

let rangeArr5 = arr[5...]
print(rangeArr5) //[]

//从头部开始删除n个元素,当n>数组元素个数时表示删除全部,结果返回空数组。
let dropFirstArr = arr.dropFirst(3)
print(dropFirstArr) //[4, 5]

//从尾部开始删除n个元素,当n>数组元素个数时表示删除全部,结果返回空数组。
let dropLastArr = arr.dropLast(3)
print(dropLastArr) //[1, 2]

上述操作之后得到的都是数组切片类型,而不是数组类型。这里简单介绍一下ArraySlice 类型,因为上面有些方法是跟索引相关的,比如方法prefix(upTo end: Int),而ArraySlice 类型的startIndex并不总是从0开始,所以得到的结果可能出乎你的预料。

ArraySlice 类型官方文档:

The ArraySlice type makes it fast and efficient for you to perform operations on sections of a larger array. Instead of copying over the elements of a slice to new storage, an ArraySliceinstance presents a view onto the storage of a larger array. And because ArraySlice presents the same interface as Array, you can generally perform the same operations on a slice as you could on the original array.

Unlike Array and ContiguousArray, the starting index for an ArraySlice instance isn’t always zero. Slices maintain the same indices of the larger array for the same elements, so the starting index of a slice depends on how it was created, letting you perform index-based operations on either a full array or a slice.

ArraySliceArray, ContiguousArray, or ArraySlice 实例的一个切片。但是它并不会将切片中的元素拷贝到一个新的存储空间,而是依然指向原始数组。

注意:endIndex始终指向集合最后一个元素的后一个位置。系统的很多方法涉及到索引时都会跟startIndex和endIndex相关。而不是0和数组个数。

ArraySlice的startIndex并不总是从0开始,而是取决于它的创建方式。下面的例子中可以看到slice1的startIndex是2而不是0。

阅读全文 »

之前买了个基金:天添金稳健型,刚开始觉得还挺不错的。但几天过后才发现并不是那么划算,今天就来分析一下:

成立以来年化4.53%,购买费率0,赎回费率:持有<360日则0.1%,>=360日则0.05%。即使减去0.1%的赎回费率,也有4.43%,看起来挺不错的。

实际上4.53%是成立以来年化,它的近三年的累计收益率为13.38%,平均每年为4.46%小于成立以来的4.53%,说明该基金没以前那么好了,走势有下降的趋势。再看一下近1年累计收益率只有3.8%,减去手续费0.05%,就只有3.75%了。也就是你放一年只有3.75%的收益率,这个收益率其实还稍稍低于其他的定期基金。虽然写着随时申赎但是如果没满一年的话是很亏的。

比如只持有1个月那么你的月收益率为3.8% / 12 = 0.3167%,再扣除0.1%的赎回费率那么就只有0.2167%。年化一下就是2.6%,已经非常低了。如果你只持有10天,那么收益刚好抵消手续费,你被白嫖。如果小于10天那就是在亏钱了。

所以这里的随时申赎其实就是一个诱饵,如果不加分析很容易上钩。当然也不是说该基金在骗人,而是不划算。想要达到它说的4.53%,以R2较低风险来看,基本上要长期持有3年左右。说是随时申赎实际上是不能“随时申赎”的。

上述的分析告诉了我们:

1.买基金不能只看成立以来的年化,要看走势图里的1月,3月,1年,3年。

2.有手续费的基金如果收益率不高的话就不要买了。就像上面的基金满一年扣除手续费后还不如定期的基金了。

个人将年化3.65%作为一个参考值,也就是1万元1天收益1元。

阅读全文 »

基金的一些概念

如下图:

日涨幅

(当日净值 - 昨日净值)/ 昨日净值 x 100%

比如要计算4.30的日涨幅,那么当日净值需要取4.30的净值,昨日净值取的就是4.29的净值。

eg:

4月29日的单位净值为1.8470,4月30日的单位净值为1.8450,这样4月30日的日涨幅就是:

(1.8450 - 1.8470)/ 1.8470 x 100% = -0.108284% ≈ -0.11%,负数说明亏了。

昨日收益

阅读全文 »