分类: IOS技术

IOS技术

问一下大家的 iPad Pro 2020 12.9 照片滑动卡么?

具体表现是:

1 、照片缩略图页面上下滑动经常特别卡,严重的时候感觉带有抖动

2 、有的照片点去要一秒左右才从模糊变清楚

3 、每张照片左滑右滑上一张下一张也经常卡

7 条回复    2021-04-02 17:09:04 +08:00
BlackCode
    1

BlackCode   3 小时 43 分钟前 via iPhone

“一秒左右才从模糊变清楚”

是不是开了照片的“优化 iPhone 存储空间”

zach
    2

zach   3 小时 33 分钟前

@BlackCode iCloud 照片是关闭的,现在怀疑是 iOS14.4.2 的原因,刚才试了下 app store 上下滑动也是卡卡的,整个系统都不流畅,照片尤其严重
tedeastside
    3

tedeastside   1 小时 53 分钟前 via iPhone

开了 60hz 吗
paopaosa
    4

paopaosa   1 小时 44 分钟前

重启过吗?
terrychanin
    5

terrychanin   1 小时 39 分钟前

实测没有这种情况,建议重启试试
littlewing
    6

littlewing   1 小时 29 分钟前 via iPhone

先说一下你有几百 G 的照片吧
Ciicing
    7

Ciicing   41 分钟前

@zach 商店我的 2020 11 款也会偶尔卡一下

Mac 上有哪些好用的 PDF 阅读软件啊?

非常讨厌 WPS,除了 WPS 外其他都可以推荐…
20 条回复    2021-04-02 17:43:58 +08:00
CenN
    1

CenN   2 小时 37 分钟前

我觉得预览挺好用的
TimePPT
    2

TimePPT   2 小时 36 分钟前

PDF Expert
66beta
    3

66beta   2 小时 35 分钟前

预览
chrome
EasonC
    4

EasonC   2 小时 34 分钟前 via iPhone

marginnote3
bear2000
    5

bear2000   2 小时 28 分钟前

自带的预览*好用
IgniteWhite
    6

IgniteWhite   1 小时 53 分钟前 via iPhone

楼上推荐的都不错。补充:Skim,这款方便配合 Vim 写 LaTeX 用,能实时更新,反向搜索
Zien
    7

Zien   1 小时 32 分钟前 via iPhone

系统自带的,或者 chrome 的 kami 插件(批注)和划词翻译带的插件( pdf 划词翻译)
littlewing
    8

littlewing   1 小时 24 分钟前 via iPhone

预览.app
sungnix
    9

sungnix   59 分钟前

PDF Guru 挺不错,不编辑的话用免费版足够了。
w2ex2019
    10

w2ex2019   57 分钟前

PDF Expert +1

AllenHua
    11

AllenHua   55 分钟前

Preview.app
ftu
    12

ftu   53 分钟前

一般用 PDF Expert
shoujiaxin
    13

shoujiaxin   44 分钟前 via iPhone

Preview,PDF Expert 太吃内存了
sunkpfly
    14

sunkpfly   38 分钟前

PDF Expert +2
clyecao
    15

clyecao   27 分钟前

chrome(+ kami) + 1
ExplorerLog
    16

ExplorerLog   17 分钟前

firefox
Johnoo
    17

Johnoo   15 分钟前

PDF Expert +10086 ,关键是还能修改矢量 PDF
gxy2825
    18

gxy2825   14 分钟前

PDF Reader Pro
Roykira
    19

Roykira   13 分钟前

*近发现 marginnote3 还不错。
with
    20

with   1 分钟前

PDF Guru +1

iOS开发-NSThread子线程autoreleasepool的问题

前言
对于 NSThread 开启的子线程,我们需要在 main 函数中创建一个autoreleasepool,当我们从其他线程跳转到该线程执行时,对象是如何释放的呢?主线程是由于runloop的循环,在beforeWait时,触发主线程的autoreleasepool的pop和push操作来释放的,而子线程并没有自动添加这些observer,那么如何释放的?

探索
跳转到我们线程执行任务的方法如下,使用了performSelector:系列的方法。

1 OBJC_EXTERN void _objc_autoreleasePoolPrint(void);
2 @implementation GrowingDispatchManager
3
4 + (void)dispatchInGrowingThread:(void (^_Nullable)(void))block {
5     if ([[NSThread currentThread] isEqual:[GrowingThread sharedThread]]) {
6         block();
7     } else {
8         [GrowingDispatchManager performSelector:@selector(dispatchBlock:)
9                                        onThread:[GrowingThread sharedThread]
10                                      withObject:block
11                                   waitUntilDone:NO];
12     }
13 }
14
15 + (void)dispatchBlock:(void (^_Nullable)(void))block {
16
17     NSLog(@”runloop %@”,[NSRunLoop currentRunLoop]);
18 //    _objc_autoreleasePoolPrint();
19     if (block) {
20         block();
21     }
22 //    _objc_autoreleasePoolPrint();
23 }

打印当前runloop,确定是没有像主线程那样添加observer,如下:

1 2021-02-03 23:14:42.112259+0800 Example[5075:57255] runloop <CFRunLoop 0x600000dbdb00 [0x7fff80617cb0]>{wakeup port = 0x9203, stopped = false, ignoreWakeUps = false,
2 current mode = kCFRunLoopDefaultMode,
3 common modes = <CFBasicHash 0x600003fe25b0 [0x7fff80617cb0]>{type = mutable set, count = 1,
4 entries =>
5
6 }
7 ,
8 common mode items = <CFBasicHash 0x600003fe2bb0 [0x7fff80617cb0]>{type = mutable set, count = 1,
9 entries =>
10
11 }
12 ,
13 modes = <CFBasicHash 0x600003fe27f0 [0x7fff80617cb0]>{type = mutable set, count = 1,
14 entries =>
15
16
17 FBasicHash 0x600003fe25e0 [0x7fff80617cb0]>{type = mutable set, count = 2,
18 entries =>
19
20
21 }
22 ,
23
24 entries =>
25 }
26 ,
27
28
29
30 },
31
32 }
33 }

那么如何释放的呢,通过_objc_autoreleasePoolPrint()打印自动释放池堆栈,确定对象没有堆积,是进行释放过了。

常驻线程实现代码如下:

2 // GrowingThread.m
3 // GrowingAnalytics
4
5 #import “GrowingThread.h”
6
7 @interface GrowingThread () {
8     dispatch_group_t _waitGroup;
9 }
10
11 @property (nonatomic, strong, readwrite) NSRunLoop *runLoop;
12
13 @end
14
15 @implementation GrowingThread
16
17 + (instancetype)sharedThread {
18     static GrowingThread *thread;
19     static dispatch_once_t onceToken;
20     dispatch_once(&onceToken, ^{
21         thread = [[GrowingThread alloc] init];
22         thread.name = @”com.growing.thread”;
23         [thread start];
24     });
25     return thread;
26 }
27
28 – (instancetype)init {
29     self = [super init];
30     if (self) {
31         _waitGroup = dispatch_group_create();
32         dispatch_group_enter(_waitGroup);
33     }
34     return self;
35 }
36
37 – (void)main {
38     @autoreleasepool {
39         _runLoop = [NSRunLoop currentRunLoop];
40         dispatch_group_leave(_waitGroup);
41
42         // Add an empty run loop source to prevent runloop from spinning.
43         CFRunLoopSourceContext sourceCtx = {.version = 0,
44                                             .info = NULL,
45                                             .retain = NULL,
46                                             .release = NULL,
47                                             .copyDescription = NULL,
48                                             .equal = NULL,
49                                             .hash = NULL,
50                                             .schedule = NULL,
51                                             .cancel = NULL,
52                                             .perform = NULL};
53         CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx);
54         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
55         CFRelease(source);
56
57         while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
58         }
59         assert(NO);
60     }
61 }
62
63 – (NSRunLoop *)runLoop;
64 {
65     dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
66     return _runLoop;
67 }
68
69 @end

通过断点调试,发现堆栈中确实有调用对应的NSPushAutoreleasePool和NSPopAutoreleasePool

%title插图%num
打印执行时的堆栈:

1 (lldb) bt
2 * thread #9, name = ‘com.growing.thread’, stop reason = breakpoint 2.1
3     frame #0: 0x0000000108cf5c07 GrowingAnalytics`+[GrowingDispatchManager dispatchBlock:](self=GrowingDispatchManager, _cmd=”dispatchBlock:”, block=0x0000000108cfb330) at GrowingDispatchManager.m:46:1
4     frame #1: 0x00007fff25781c30 Foundation`__NSThreadPerformPerform + 259
5     frame #2: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
6     frame #3: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
7     frame #4: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
8     frame #5: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
9     frame #6: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
10   * frame #7: 0x00007fff2576b86f Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
11     frame #8: 0x0000000108d41012 GrowingAnalytics`-[GrowingThread main](self=0x0000600001285900, _cmd=”main”) at GrowingThread.m:72:16
12     frame #9: 0x00007fff257817a7 Foundation`__NSThread__start__ + 1047
13     frame #10: 0x00007fff52466e65 libsystem_pthread.dylib`_pthread_start + 148
14     frame #11: 0x00007fff5246283b libsystem_pthread.dylib`thread_start + 15

至此,对象为何释放的原因找到了,如果不是runMode:beforeDate:方法自动加上了自动释放池,就内存泄漏了,所以需要在执行的方法dispatchBlock中保险起见,都加上@autoreleasepool才是上策,这里也是忽略了这点,险些bug背锅。。。

iOS开发-逆向注入SDK之iOS越狱

越狱
这里采用使用 iphone 5S, iOS 12.4.9 为例,进行 非完美越狱

非完美越狱:重启手机越狱失效,需要再进行越狱,越狱也不麻烦,点几下就行了,不过不关机就行了哈

i4助手
安装 i4助手(爱思助手) https://www.i4.cn/ 是个很好的软件,如果你的手机变砖了,正好可以使用它刷机。不过被密码锁定的手机不行。

如果你的iphone已经开机就黑,选择合适的固件版本,进行刷机

%title插图%num

这里对其功能不做进一步说明,低版本可以直接完美越狱,我们这里仅使用它来方便访问越狱文件以及安装app。

使用unc0ver越狱并配置
unc0ver 可以方便的快速越狱你的手机,不过是非完美越狱,由于安装的时候,手机还没有越狱,官网的使用方法中,使用 iOS App Signer 的方式可行。

%title插图%num

这里以iOS开发者的角度阐述为以下几步:

安装好你的xcode,配置好环境,登录你的开发者账号。
创建一个Project,选择类型为App,这里叫它 unc0ver。
配置好你的证书,如下

%title插图%num

从钥匙串中导出你对应的p12证书,并且拖出Profile文件。(这里Profile文件部分同学可能不知道拖,见下图)
a. 鼠标移至感叹号,点击

%title插图%num

b. 长按图标,拖置桌面

%title插图%num

从 unc0ver 官网下载 unc0ver_Release_x.x.x.ipa
打开 iOS App Signer,配置ipa,p12以及选择自定义的Profile

%title插图%num

生成新的ipa,通过i4助手进行安装。

%title插图%num

如果需要信任,则设置->通用->描述文件与设备管理中信任即可。
打开 unc0ver,点击 Jailbreak 进行越狱,期间弹出 REBEL 不要管它,叉掉,继续。提示你重启就重启,该允许的允许,然后继续打开 unc0ver 继续,直到 unc0ver 完成越狱。
这个方法需要 开发证书 配置文件 重签名。网上部分安装方法,直接安装的话,会提示 entitlements 不匹配,无法进行下一步了。

越狱好后,会多出Cydia和Substitude,同时 unc0ver 显示 Re-Jailbreak

%title插图%num

安装好unc0ver之后,就需要配置 Cydia 了

配置你的Cydia
点击 Cydia->软件源->编辑->添加 添加源 build.frida.re ,apt.cydiami.com,并进行更新。
搜索 SSH ,选择 OpenSSH 进行安装
搜索 frida ,由于是 5S,我们选择 Frida for pre-A12 devices
搜索 AFC ,安装 AFC2 iOS12系统文件访问
至此,越狱完成,可以访问系统文件了。

%title插图%num

iOS开发-NSNotification源码原理学习

文章目录
问题
1. 实现原理
对于addObserver方法,为什么需要object参数?
都传入null对象会怎么样
addObserver源码逻辑
2.通知的发送时同步的,还是异步的
3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
4.NSNotificationQueue和runloop的关系
5.如何保证通知接收的线程在主线程
6.多次添加同一个通知会是什么结果?多次移除通知呢?
7.下面的方式能接收到通知吗?为什么
问题
苹果并没有开源相关代码,但是可以读下 GNUStep 的源码

或者这里下载
链接:https://pan.baidu.com/s/1F25GgeLxqKjeo10Zgfr2OQ
密码:qpka

从下列问题触发,探索下NSNotification的实现原理

实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)
通知的发送时同步的,还是异步的
NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
NSNotificationQueue是异步还是同步发送?在哪个线程响应
NSNotificationQueue和runloop的关系
如何保证通知接收的线程在主线程
页面销毁时不移除通知会崩溃吗
多次添加同一个通知会是什么结果?多次移除通知呢
下面的方式能接收到通知吗?为什么

1 // 发送通知
2 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@”TestNotification” object:@1];
3 // 接收通知
4 [NSNotificationCenter.defaultCenter postNotificationName:@”TestNotification” object:nil];

1. 实现原理
对于addObserver:selector:name:object:会创建一个observation

1 typedef
2   id
3   SEL
4   struct Obs
5   int
6   struct NCTbl
7 } Observation;

对于Observation持有observer

在iOS SDK 8之前:不是一个类似OC中的weak类型,持有的相当与一个__unsafe_unretain指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS。
在iOS SDK 8之后:持有的是weak类型指针,对nil对象performSelector不再会崩溃
name和Observation是映射关系,observer和sel包含在Observation结构体中。

此外,NSNotification维护了GSIMapTable表的结构,用于存储Observation,分别是nameless,name,cache,nameless存储没有传入名字的通知,named存储传入了名字的通知,cache用于快速缓存.

1 #define CHUNKSIZE 128
2 #define CACHESIZE 16
3 typedef struct NCTbl {
4   Observation *wildcard; /* Get ALL messages. */
5   GSIMapTable nameless; /* Get messages for any name. */
6   GSIMapTable named; /* Getting named messages only. */
7   unsigned lockCount; /* Count recursive operations. */
8   NSRecursiveLock *_lock; /* Lock out other threads. */
9   Observation *freeList;
10   Observation **chunks;
11   unsigned numChunks;
12   GSIMapTable cache[CACHESIZE];
13   unsigned short chunkIndex;
14   unsigned short cacheIndex;
15 } NCTable;

这里值得注意nameless和named的结构,虽然同为hash表

1
2 在nameless表中:
3 GSIMapTable的结构如下
4 object : Observation
5 object : Observation
6 object : Observation
7
8 —————————-
9 在named表中:
10 GSIMapTable结构如下:
11 name : maptable
12 name : maptable
13 name : maptable
14
15 maptable的结构如下
16 object : Observation
17 object : Observation
18 object : Observation

关于GSIMap结构

对于addObserver方法,为什么需要object参数?
– (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

就是addObserver其实不用传入name也可以,传入object,当postNotification方法同样发出这个object时,就会触发通知方法。

例如这样的写法:

1 – (void)viewDidLoad {
2     [super viewDidLoad];
3     // Do any additional setup after loading the view.
4     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
5     MyObject *obj = [MyObject new];
6     [center addObserver:self selector:@selector(doAction:) name:nil object:obj];
7     dispatch_async(dispatch_get_global_queue(0, 0), ^{
8         [center postNotificationName:@”TEST” object:obj];
9     });
10 }
11
12 – (void)doAction:(NSNotification*)sender {
13     NSLog(@”%s %@ %@”,__FUNCTION__,sender,[NSThread currentThread]);
14 }

参考 https://www.jianshu.com/p/83770200d476

都传入null对象会怎么样
你可能也注意到了,addObserver方法name和object都可以为空,这表示将会把observer赋值为 wildcard,他将会监听所有的通知。

addObserver源码逻辑
addObserver的逻辑如下

1. 根据传入的selector和observer创建Observation,并存入maptable中,如果已存在,则是从cache中取。
2. 如果name存在,则向named的maptable表中插入元素,key为name,value为GSIMapTable,GSIMapTable中存key为object,value为Observation,转入5。如果不存在则进入3
3. 如果object存在,则向nameless的maptable表中插入元素,key为object,value为Observation。如果不存在,则进入4,否则转入5
4. name和object都为空,则Observation->next=wildcard,将老的wildcard赋值为next指针,然Observation对象置为wildcard,wildcard = Observation
5.结束

2.通知的发送时同步的,还是异步的
postNotificationName的底层实现是

– (void) _postAndRelease: (NSNotification*)notification
由于内部会读取TABLE

lockNCTable(TABLE);
… //找到对应的observer对象
unlockNCTable(TABLE);

…//执行performSelector方法

1 lockNCTable(TABLE);
2 … //找到对应的observer对象
3 unlockNCTable(TABLE);
4
5 …//执行performSelector方法
6
7 lockNCTable(TABLE);
8 GSIArrayEmpty(a); //释放临时创建的数组对象 – 用于存储observer的
9 unlockNCTable(TABLE);

同步异步这个问题,由于TABLE资源的问题,同一个线程会按顺序执行,自然是同步的。

3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
由于是使用的performSelector方法,没有进行转线程,默认是postNotification方法的线程。

1
2 [o->observer performSelector: o->selector
3                                 withObject: notification];
4

对于异步发送消息,可以使用NSNotificationQueue,queue顾明意思,我们是需要将NSNotification放入queue中执行的。

有三种状态

1 typedef NS_ENUM(NSUInteger, NSPostingStyle) {
2     NSPostWhenIdle = 1,      // 当runloop处于空闲状态时post
3     NSPostASAP = 2,    // 当当前runloop完成之后立即post
4     NSPostNow = 3    // 立即post
5 };

 

1 NSNotification *noti = [NSNotification notificationWithName:@”111″ object:nil];
2 [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];

参考文章 https://www.jianshu.com/p/356f7af4f2ee

4.NSNotificationQueue和runloop的关系
NSNotificationQueue的执行是依赖于runloop的,它的三种模式各自的执行时机不一样。

1 typedef NS_ENUM(NSUInteger, NSPostingStyle) {
2     NSPostWhenIdle = 1,      // 当runloop处于空闲状态时post
3     NSPostASAP = 2,    // 当当前runloop完成之后立即post
4     NSPostNow = 3    // 立即post
5 };

例如

1 void asyncQueueNotiInRunloop() {
2     dispatch_async(dispatch_get_global_queue(0, 0), ^{
3         NSLog(@”1″);
4         NSLog(@”%@”, [NSThread currentThread]);
5
6         //NSPostWhenIdle
7         //NSPostASAP
8         //NSPostNow
9         NSNotification *notification = [NSNotification notificationWithName:@”TEST” object:nil];
10         [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:@[NSDefaultRunLoopMode]];
11         // run runloop
12         [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
13         CFRunLoopRun();
14         NSLog(@”3″);
15     });
16 }

如果去掉run runloop部分的代码,则无法触发通知

5.如何保证通知接收的线程在主线程
由于通知的发出使用performSeletor实现,如果需要保证接收的线程在主线程,可以:

保证主线程发出
接收到通知后跳转到主线程,苹果建议使用NSMachPort进行消息转发到主线程。
https://blog.csdn.net/shengpeng3344/article/details/90206265

使用block接口addObserverForName:object:queue:usingBlock:

1 – (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
2     // The return value is retained by the system, and should be held onto by the caller in
3     // order to remove the observer with removeObserver: later, to stop observation.

页面销毁时不移除通知会崩溃吗?
对于Observation持有observer

在iOS SDK 8之前:不是一个类似OC中的weak类型,持有的相当与一个__unsafe_unretain指针对象,当对象释放时,会访问已经释放的对象,造成BAD_ACCESS。
在iOS SDK 8之后:持有的是weak类型指针,对nil对象performSelector不再会崩溃
所以说不一定会崩溃,但是根据代码严谨是需要remove的

6.多次添加同一个通知会是什么结果?多次移除通知呢?
由于源码中并不会进行重复过滤,所以添加同一个通知,等于就是添加了2次,回调也会触发两次。

为什么会触发两次呢,因为- (void) postNotificationName: (NSString*)name object: (id)object的逻辑是这样的:

1. 查找所有是wildcard类型的Observation,加入数组array,即既不监听name也不监听object
2. 查找指定object但未指定name的Observation,加入数组array
3. 查找指定了相同name和object的Observation,加入数组array,当name一致但object不一致时,也不会加入到数组array,但如果传入的object!=nil,则会将name对应的maptable中,所有key为nil的Observation也加入数组。
4. 遍历array,执行performSelector
5. 清空array

第三步的意思是:如果发出一个通知,方法中传入了对象object,那么那些只监听通知name,object设置为nil的当然也可以收到,object匹配了的也可以收到,object不匹配的则收不到。

关于多次移除,并没有问题,因为会去map中查找,找到才会删除。当name和object都为nil时,会移除所有关于该observer的WILDCARD

7.下面的方式能接收到通知吗?为什么

1
2 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@”TestNotification” object:@1];
3
4 [NSNotificationCenter.defaultCenter postNotificationName:@”TestNotification” object:nil];

根据postNotification的实现,会找到key为TestNotification的maptable,再从中选择key为nil的observation,所以是找不到以@1为key的observation的

iOS开发-ViewController的生命周期相关

文章目录
ViewController生命周期
加载流程
didReceiveMemoryWarning
View的layoutSubviews
Runloop相关
view的drawRect:方法
ViewController生命周期
加载流程

%title插图%num

1 1.init或者initWithCoder:(NSCoder *)aDecoder:(如果使用storyboard或者xib)
2 2.loadView:加载view
3 3.viewDidLoad:view加载完毕
4 4.viewWillAppear:控制器的view将要显示
5 5.viewWillLayoutSubviews:控制器的view将要布局子控件
6 6.viewDidLayoutSubviews:控制器的view布局子控件完成
7 这期间系统可能会多次调用viewWillLayoutSubviews、viewDidLayoutSubviews俩个方法
8
9 7.viewDidAppear:控制器的view完全显示
10 8.viewWillDisappear:控制器的view即将消失的时候
11 这期间系统也会调用viewWillLayoutSubviews 、viewDidLayoutSubviews 两个方法
12
13 9.viewDidDisappear:控制器的view完全消失的时候

didReceiveMemoryWarning

Discussion Your app never calls this method directly. Instead, this
method is called when the system determines that the amount of
available memory is low.

You can override this method to release any additional memory used by
your view controller. If you do, your implementation of this method
must call the super implementation at some point.

当app收到内存警告的时候会发消息给视图控制器。
app从来不会直接调用这个方法,而是当系统确定可用内存不足的时候采取调用。
如果你想覆写这个方法来释放一些控制器使用的额外内存,你应该在你的实现方法中调用父类的实现方法。

View的layoutSubviews
init初始化不会触发layoutSubviews。
addSubview会触发layoutSubviews。
改变一个UIView的frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。
滚动一个UIScrollView引发UIView的重新布局会触发layoutSubviews。
旋转Screen会触发父UIView上的layoutSubviews事件。
直接调用setNeedsLayout 或者 layoutIfNeeded。

Runloop相关
在非主页面加载时

1 * thread #1, queue = ‘com.apple.main-thread’, stop reason = breakpoint 1.1
2   * frame #0: 0x0000000100529264 GSWatermarkView`-[GSWaterMarkView layoutSubviews](self=0x000000011d801410, _cmd=”layoutSubviews”) at GSWaterMarkView.m:110
3     frame #1: 0x00000001a77db5b0 UIKitCore`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2156
4     frame #2: 0x00000001a2fe7af0 libobjc.A.dylib`-[NSObject performSelector:withObject:] + 68
5     frame #3: 0x00000001a9d81c0c QuartzCore`-[CALayer layoutSublayers] + 292
6     frame #4: 0x00000001a9d81f14 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 484
7     frame #5: 0x00000001a9d953fc QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 140
8     frame #6: 0x00000001a9cda184 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double) + 296
9     frame #7: 0x00000001a9d05228 QuartzCore`CA::Transaction::commit() + 684
10     frame #8: 0x00000001a7362d6c UIKitCore`_afterCACommitHandler + 144
11     frame #9: 0x00000001a324bf5c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
12     frame #10: 0x00000001a3246bfc CoreFoundation`__CFRunLoopDoObservers + 420
13     frame #11: 0x00000001a32471ac CoreFoundation`__CFRunLoopRun + 1292
14     frame #12: 0x00000001a3246978 CoreFoundation`CFRunLoopRunSpecific + 480
15     frame #13: 0x00000001ad376534 GraphicsServices`GSEventRunModal + 108
16     frame #14: 0x00000001a7338f0c UIKitCore`UIApplicationMain + 1940
17     frame #15: 0x000000010052814c GSWatermarkView`main(argc=1, argv=0x000000016f8df940) at main.m:14
18     frame #16: 0x00000001a30c6f04 libdyld.dylib`start + 4

在初始界面加载时

1 * thread #1, queue = ‘com.apple.main-thread’, stop reason = breakpoint 1.1
2   * frame #0: 0x0000000100b05264 GSWatermarkView`-[GSWaterMarkView layoutSubviews](self=0x000000010130f210, _cmd=”layoutSubviews”) at GSWaterMarkView.m:110
3     frame #1: 0x00000001a77db5b0 UIKitCore`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2156
4     frame #2: 0x00000001a2fe7af0 libobjc.A.dylib`-[NSObject performSelector:withObject:] + 68
5     frame #3: 0x00000001a9d81c0c QuartzCore`-[CALayer layoutSublayers] + 292
6     frame #4: 0x00000001a9d81f14 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 484
7     frame #5: 0x00000001a9d953fc QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 140
8     frame #6: 0x00000001a9cda184 QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double) + 296
9     frame #7: 0x00000001a9d05228 QuartzCore`CA::Transaction::commit() + 684
10     frame #8: 0x00000001a7350d20 UIKitCore`__34-[UIApplication _firstCommitBlock]_block_invoke_2 + 84
11     frame #9: 0x00000001a324c95c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 28
12     frame #10: 0x00000001a324c0e0 CoreFoundation`__CFRunLoopDoBlocks + 268
13     frame #11: 0x00000001a32470e0 CoreFoundation`__CFRunLoopRun + 1088
14     frame #12: 0x00000001a3246978 CoreFoundation`CFRunLoopRunSpecific + 480
15     frame #13: 0x00000001ad376534 GraphicsServices`GSEventRunModal + 108
16     frame #14: 0x00000001a7338f0c UIKitCore`UIApplicationMain + 1940
17     frame #15: 0x0000000100b0414c GSWatermarkView`main(argc=1, argv=0x000000016f303940) at main.m:14
18     frame #16: 0x00000001a30c6f04 libdyld.dylib`start + 4

可以看出在app启动时,初始界面View的layoutSubviews由__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__触发,后续的界面由__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__触发

view的drawRect:方法
直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:,但是有个前提条件是rect大小不能为0。
drawRect的调用时机是在viewWillAppear和viewDidAppear之间。且在View的layoutSubviews之后

1 2020-03-03 14:05:58.384185+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController loadView]]
2 2020-03-03 14:05:58.384252+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController viewDidLoad]]
3 2020-03-03 14:05:58.385593+0800 GSWatermarkView[1318:1216023]  GSWaterMarkView [-[GSWaterMarkView didMoveToSuperview]]
4 2020-03-03 14:05:58.385686+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController viewWillAppear:]]
5 2020-03-03 14:05:58.387915+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController viewWillLayoutSubviews]]
6 2020-03-03 14:05:58.387956+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController viewDidLayoutSubviews]]
7 2020-03-03 14:05:58.387975+0800 GSWatermarkView[1318:1216023]  GSWaterMarkView [-[GSWaterMarkView layoutSubviews]]
8 2020-03-03 14:05:58.388046+0800 GSWatermarkView[1318:1216023]  GSWaterMarkView [-[GSWaterMarkView drawRect:]]
9 2020-03-03 14:05:58.427785+0800 GSWatermarkView[1318:1216023]  ViewController [-[ViewController viewDidAppear:]]

调用sizeToFit,会触发drawRect的调用。
通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。
app启动时,添加到初始界面的堆栈,还是CALayer触发

iOS开发-逆向注入SDK之MonkeyDev注入打包

文章目录
MonkeyDev
使用
注入SDK
MonkeyDev
强大的工具集,MonkeyDev ,使用它行了!

安装教程见 Wiki

使用
拿着之前通过 frida砸壳 的 ipa包,先创建一个 MonkeyDev 工程。这里以 qqmusic 为例

%title插图%num
将砸壳的 ipa 放到 qqmusic/TargetApp/ 下,然后拖拽至工程文件中。

设置主工程的证书,dylib的不用设置

%title插图%num

提示 Showing All Messages Signing for “qqmusicDylib” requires a development team. Select a development team in the Signing & Capabilities editor. ,此时选择 qqmusicDylib->Build Settings->Add User-Defined Setting

%title插图%num

添加 CODE_SIGNING_ALLOWED 为 NO,再运行即可。

file not found: /usr/lib/libstdc++.dylib 问题

原因是新版本xcode去掉了libstdc++这个库,从老版本复制过来即可,这里直接使用别人的 https://github.com/devdawei/libstdc-

注入SDK
可以运行之后,我们在工程中初始化pod,

1 修改pod文件,注释use_frameworks!
2 # platform :ios, ‘9.0’
3
4 target ‘qqmusic’ do
5   # Comment the next line if you don’t want to use dynamic frameworks
6   # use_frameworks!
7   pod ‘GrowingAnalytics-cdp/Autotracker’
8   # Pods for qqmusic
9
10 end
11
12 target ‘qqmusicDylib’ do
13   # Comment the next line if you don’t want to use dynamic frameworks
14   # use_frameworks!
15   pod ‘GrowingAnalytics-cdp/Autotracker’
16   # Pods for qqmusicDylib
17
18 end

查找对应的appdelegate类,使用class-dump命令
class-dump -H xxx.app -o yourDir/Headers
1
发现其 AppDelegate 类叫 XXXXAppDelegate

然后使用logos注入SDK初始化代码,使用文档查看官网 http://iphonedevwiki.net/index.php/Logos

%title插图%num

1 // See http://iphonedevwiki.net/index.php/Logos
2
3 #import <UIKit/UIKit.h>
4 #import “GrowingAutotracker.h”
5
6 static NSString *const kGrowingProjectId = @”91eaf9b283361032″;
7
8 %hook XXXXAppDelegate
9
10 – (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
11         BOOL result = %orig;
12     GrowingTrackConfiguration *configuration = [GrowingTrackConfiguration configurationWithProjectId:kGrowingProjectId];
13     configuration.debugEnabled = YES;
14     configuration.impressionScale = 1.0;
15     configuration.dataCollectionServerHost = @”https://run.mocky.io/v3/08999138-a180-431d-a136-051f3c6bd306″;
16     [GrowingAutotracker startWithConfiguration:configuration launchOptions:launchOptions];
17         return result;
18 }
19
20 %end

然后再编译运行,至此,已经可以在App中调试SDK,并有相关日志输出了。

mac开发-10.15检测屏幕录制权限

在Mac os 10.15之后,屏幕录制权限需要获取才能正确录屏,否则只能录制桌面背景以及自身app的影像。即可以截屏,但截不到其他app的内容。
文章目录
屏幕录制权限检测
屏幕录制授权申请
CGWindowListCreateImage
隐私页面跳转
清除某个App的权限记录
清除某个隐私权限的全部内容
遇到的问题
屏幕录制权限检测
对于Mac os 10.15的屏幕录制权限检测,使用如下方法为*佳:

1 – (BOOL)canRecordScreen
2 {
3     if (@available(macOS 10.15, *)) {
4         CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
5         NSUInteger numberOfWindows = CFArrayGetCount(windowList);
6         NSUInteger numberOfWindowsWithName = 0;
7         for (int idx = 0; idx < numberOfWindows; idx++) {
8             NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
9             NSString *windowName = windowInfo[(id)kCGWindowName];
10             if (windowName) {
11                 numberOfWindowsWithName++;
12             } else {
13                 //no kCGWindowName detected -> not enabled
14                 break; //breaking early, numberOfWindowsWithName not increased
15             }
16
17         }
18         CFRelease(windowList);
19         return numberOfWindows == numberOfWindowsWithName;
20     }
21     return YES;
22 }

另外一种方法是下面这种?,在权限没有设置的时候没问题,但是在权限设置后,会导致mac os 10.15下的系统崩溃,直接回到登陆界面。所以没有使用这个

1 – (BOOL)canRecord{
2     CGDisplayStreamRef stream = CGDisplayStreamCreate(CGMainDisplayID() , 1, 1, kCVPixelFormatType_32ABGR, nil, ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef  _Nullable frameSurface, CGDisplayStreamUpdateRef  _Nullable updateRef) {
3
4     });
5
6     BOOL canRecord = stream != NULL;
7     if (stream) {
8         CFRelease(stream);
9     }
10
11     NSLog(@”canRecord : %ld”,canRecord);
12     return canRecord;
13 }

屏幕录制授权申请
通过截屏都可以向系统获取权限

1 – (void)showScreenRecordingPrompt{
2
3   /* macos 10.14 and lower do not require screen recording permission to get window titles */
4   if(@available(macos 10.15, *)) {
5     /*
6      To minimize the intrusion just make a 1px image of the upper left corner
7      This way there is no real possibilty to access any private data
8      */
9     CGImageRef c = CGWindowListCreateImage(
10                                                     CGRectMake(0, 0, 1, 1),
11                                                     kCGWindowListOptionOnScreenOnly,
12                                                     kCGNullWindowID,
13                                                     kCGWindowImageDefault);
14
15       CFRelease(screenshot);
16
17 }

截取主屏幕全部的内容

1 CGRect mainRect = CGDisplayBounds(CGMainDisplayID());
2 CGImageRef desktopImage = CGWindowListCreateImage(mainRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageBestResolution | kCGWindowImageShouldBeOpaque);
3 NSImage* image = [[NSImage alloc]initWithCGImage:desktopImage size:_screenAsBtn.frame.size];
4 CGImageRelease(desktopImage);

截取某个子程序的图片

1 [_windowList removeAllObjects];
2 CFArrayRef windowListArray = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
3 NSArray *windows = CFBridgingRelease(CGWindowListCreateDescriptionFromArray(windowListArray));
4 for(NSMutableDictionary* dic in windows){
5     NSString* layerStr = [dic objectForKey:(__bridge id)kCGWindowLayer];
6     CGRect bounds;
7     CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)[dic objectForKey:@”kCGWindowBounds”], &bounds);
8     NSRectFromCGRect(bounds);
9     if ([layerStr intValue] == 0 &&(bounds.size.width>10 && bounds.size.height>10)) {
10         [_windowList addObject:dic];
11     }
12 }
13 CFRelease(windowListArray);
14 //下面是for循环中内容,取每张图片
15 NSMutableDictionary* entry = [_windowList objectAtIndex:i];
16 int ownerPID = [[entry objectForKey:(__bridge id)kCGWindowNumber] intValue];
17 NSString* name =[entry objectForKey:(__bridge id)kCGWindowName];
18
19 NSString* owerName = [entry objectForKey:(__bridge id)kCGWindowOwnerName];
20 CGImageRef imageRef = CGWindowListCreateImage(CGRectNull,  kCGWindowListOptionIncludingWindow, ownerPID, kCGWindowImageShouldBeOpaque);
21 NSImage* image = [[NSImage alloc]initWithCGImage:imageRef size:NSZeroSize];
22 CGImageRelease(imageRef);

隐私页面跳转

1 – (void)openSetting{
2     NSString *urlString = @”x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture”;
3     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
4 }

清除某个App的权限记录
使用tccutil reset All com.xxx.xxx来清除,com.xxx.xxx为bundle id

清除某个隐私权限的全部内容
重置摄像头访问: tccutil reset Camera
重置麦克风访问: tccutil reset Microphone
重置屏幕录制:tccutil reset ScreenCapture

遇到的问题
遇到权限一直要获取,获取后提示关闭该程序,进入程序后,又需要获取的死循环。
这里我重启mac就好了,不知道是10.15系统原因否。

iOS学习笔记-APP之间数据共享空间AppGroups

文章目录
创建AppGroups
项目中配置
代码中使用
NSUserDefaults
NSFileManager
CoreData使用
AppGroupManager
APP之间共享数据的方式约以下几种:

URL Scheme 跳转
App Groups 共享一块存储空间
KeyChain共享数据(keychain access group) 账号密码共用,不会随App删除
UIDocumentInteractionController 文档分享,例如文件在QQ中打开的功能
粘贴板UIPasteboard 复制粘贴
App Groups 用于两个app之间共享文件,开扩了一块共同的存储区域!

此外扩展( Extension )也需要使用App Groups 的相关知识

此方法只能使用于同一个开发者账号,如果不同开发者账号请考虑剪切板 UIPasteboard

若需要存储共有的账号密码,请使用KeyChain

创建AppGroups
登陆开发者账号
https://developer.apple.com/account/
选择Certificates,IDS & Profiles

%title插图%num

选择Identifiers,添加一个

%title插图%num
选择App Groups

%title插图%num
然后填写描述和group标识

然后 continue

每个app id需要打开App Groups功能,并配置好

项目中配置
打开你的xcode项目工程,先选中根目录,在target中选中你的工程–>Capabilites

%title插图%num

代码中使用
NSUserDefaults

1 //初始化一个供App Groups使用的NSUserDefaults对象
2 NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@”group.company.appGroupName”];
3
4 //写入数据
5 [userDefaults setValue:@”value” forKey:@”key”];
6
7 //读取数据
8 NSLog(@”%@”, [userDefaults valueForKey:@”key”]);

NSFileManager

1 //获取分组的共享目录
2 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@”group.company.appGroupName”];
3 NSURL *fileURL = [groupURL URLByAppendingPathComponent:@”demo.txt”];
4
5 //写入文件
6 [@”abc” writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
7
8 //读取文件
9 NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
10 NSLog(@”str = %@”, str);

利用NSFileManager我们可以存取各种文件,数据库文件(.db),json文件,framework等等;我们可以复制各种文件到APPGroup的存取区域,在另一个app中拿出来

1 static NSString * const group = @”group.company.appGroupName”;
2
3 – (BOOL)saveDataFromPath:(NSString *)originPath toFile:(NSString *)filename;
4 {
5 self.url = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:group];
6 NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
7
8 return [[NSFileManager defaultManager] copyItemAtPath:originPath toPath:[fileURL path] error:nil];

在*块代码中,NSFileManager是以NSURL来操作的,同样,我们可以以path进行操作,这是没问题的,path可由URL的[fileURL path]方式取到

CoreData使用

1 //获取分组的共享项目
2 NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@”group.cn.vimfung.ShareExtensionDemo”];
3 NSURL *storeURL = [containerURL URLByAppendingPathComponent:@”DataModel.sqlite”];
4
5 //初始化持久化存储调度器
6 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@”DataModel” withExtension:@”momd”];
7
8 NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
9 NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
10
11 [coordinator addPersistentStoreWithType:NSSQLiteStoreType
12                           configuration:nil
13                                     URL:storeURL
14                                 options:nil
15                                   error:nil];
16
17 //创建受控对象上下文
18 NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
19
20 [context performBlockAndWait:^{
21     [context setPersistentStoreCoordinator:coordinator];
22 }];

我没有深入用过,所以这里不做详细阐述;

另外添上封装的AppGroupManager类,如需要可以使用:

1 //
2 //  AppGroupManager.h
3 //  MHJFamilyV1
4 //
5 //  Created by tangmi on 16/8/4.
6 //  Copyright © 2016年 mhjmac. All rights reserved.
7 //
8
9 #import <Foundation/Foundation.h>
10
11 @interface AppGroupManager : NSObject
12
13 ///your apple appgroup id   eg:”com.company.testname”
14 @property (nonatomic, copy) NSString *identifier;
15
16 ///appgroup file’s url
17 @property (nonatomic, strong) NSURL *url;
18
19 #pragma init
20 ///singleton method
21 + (instancetype)defaultManager;
22 ///init
23 – (instancetype)initWithGroupIdentifier:(NSString*)group;
24
25 #pragma write normal data – string dic data array
26 ///写入jsonStr字符串到Json文件中
27 – (BOOL)writeToFile:(NSString *)filename withJson:(NSString *)jsonStr;
28 ///是将字典转换成字符串存入Json文件
29 – (BOOL)writeToFile:(NSString *)filename withDictionary:(NSDictionary *)dic;
30
31 – (BOOL)writeToFile:(NSString *)filename withData:(NSData *)data;
32
33 – (BOOL)writeToFile:(NSString *)filename withArray:(NSArray *)array;
34
35 #pragma file
36 ///copy file
37 – (BOOL)saveDataFromPath:(NSString *)originPath toFile:(NSString *)filename;
38 ///根据data类型写入对应类型的文件中
39 – (BOOL)saveData:(NSData *)data toFile:(NSString *)filename;
40 ///get data (image,file…) from “(appgroup url)/filename”
41 – (NSData *)dataFromFile:(NSString *)filename;
42 ///delete File
43 – (BOOL)deleteFile:(NSString *)filename;
44
45 #pragma read
46 ///read json string from file that name is filename
47 – (NSString *)jsonFromFile:(NSString *)filename;
48 ///as same as upon, just turn string to dictionary
49 – (NSDictionary *)dictionaryFormFile:(NSString *)filename;
50
51
52 #pragma other
53 ///judge file is exist;
54 – (BOOL)isExistFile:(NSString *)filename;
55
56 @end

AppGroupManager.m

1 //
2 //  AppGroupManager.m
3 //  MHJFamilyV1
4 //
5 //  Created by tangmi on 16/8/4.
6 //  Copyright © 2016年 mhjmac. All rights reserved.
7 //
8
9 #import “AppGroupManager.h”
10
11 static NSString * const mhjgroup = @”group.company.appGroupName”;  //默认的appgroup
12
13 @implementation AppGroupManager
14
15 #pragma mark init
16
17 static AppGroupManager *manager = nil;
18
19 + (instancetype)defaultManager
20 {
21     static dispatch_once_t onceToken;
22     dispatch_once(&onceToken, ^{
23         manager = [[AppGroupManager alloc] initWithGroupIdentifier:mhjgroup];
24     });
25     return manager;
26 }
27
28 – (instancetype)initWithGroupIdentifier:(NSString*)group
29 {
30     if (self = [super init]) {
31         _identifier = group;
32         _url = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:group];
33     }
34     return self;
35 }
36
37 #pragma mark write normal data – string dic data array
38
39 – (BOOL)writeToFile:(NSString *)filename withJson:(NSString*)jsonStr
40 {
41     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
42
43     return [jsonStr writeToURL:fileURL atomically:YES encoding:NSUTF8StringEncoding error:nil];
44
45 }
46
47 – (BOOL)writeToFile:(NSString *)filename withDictionary:(NSDictionary*)dic
48 {
49     NSString *jsonStr = [AppGroupManager dictionaryToJson:dic];
50     return [self writeToFile:filename withJson:jsonStr];
51 }
52
53 – (BOOL)writeToFile:(NSString *)filename withData:(NSData *)data
54 {
55     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
56     return [data writeToURL:fileURL atomically:YES];
57 }
58
59 – (BOOL)writeToFile:(NSString *)filename withArray:(NSArray *)array
60 {
61     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
62     return [array writeToURL:fileURL atomically:YES];
63 }
64
65 #pragma file save
66
67 – (BOOL)saveDataFromPath:(NSString *)originPath toFile:(NSString *)filename;
68 {
69     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
70     return [[NSFileManager defaultManager] copyItemAtPath:originPath toPath:[fileURL path] error:nil];
71 }
72
73 – (BOOL)saveData:(NSData *)data toFile:(NSString *)filename
74 {
75     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
76     return [[NSFileManager defaultManager] createFileAtPath:[fileURL path] contents:data attributes:nil];
77 }
78
79 – (NSData *)dataFromFile:(NSString *)filename
80 {
81     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
82     return [[NSFileManager defaultManager] contentsAtPath:[fileURL path]];
83 }
84
85 – (BOOL)deleteFile:(NSString *)filename;
86 {
87     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
88     return [[NSFileManager defaultManager] removeItemAtPath:[fileURL path] error:nil];
89 }
90
91 #pragma mark read
92
93 – (NSString*)jsonFromFile:(NSString *)filename
94 {
95     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
96     BOOL isExist = [[NSFileManager defaultManager] isExecutableFileAtPath:[fileURL path]];
97     if (isExist) {
98         NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
99         return  str;
100     }else{
101         //文件不存在
102         return nil;
103     }
104
105
106 }
107
108 – (NSDictionary *)dictionaryFormFile:(NSString *)filename
109 {
110     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
111     NSString *str = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:nil];
112     if (str) {
113         return [AppGroupManager dictionaryWithJsonString:str];
114     }else{
115         return nil;
116     }
117
118 }
119
120 #pragma mark other
121
122 – (BOOL)isExistFile:(NSString *)filename;
123 {
124     NSURL *fileURL = [self.url URLByAppendingPathComponent:filename];
125     NSString *path = [fileURL path];
126     BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:path];
127     return isExist;
128 }
129
130 #pragma mark private methods
131
132 + (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
133     if (jsonString == nil) {
134         return nil;
135     }
136     NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
137     NSError *err;
138     NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
139                                                         options:NSJSONReadingMutableContainers
140                                                           error:&err];
141
142     if(err) {
143         NSLog(@”json解析失败:%@”,err);
144         return nil;
145     }
146     return dic;
147 }
148
149
150 + (NSString*)dictionaryToJson:(NSDictionary *)dic
151 {
152     NSError *parseError = nil;
153     NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
154
155     return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
156
157 }
158
159 @end

使用的话应该以AppGroupManager为工具再封装一层使用于自己项目逻辑的类

IOS AR技术开发

这几天了解了下AR的技术开发,关于AR网上也有一些指导博客,我这里由于也是刚开始接触AR,所以不加以多说,只是总一个个人总结。

关于AR我*次感觉有意思是QQ在2017年城市内抢红包感受到的。当时觉得这样的技术应该会普及起来,但是没想到原来AR技术都开始好几年了,只是*近才火起来。

%title插图%num%title插图%num

关于AR网上很少有IOS直接开发的,国内的平台有亮风台、EasyAR等。我就是在这两个平台上学习的,QQ的抢红包听说就是亮风台公司给他们策划开发的。亮风台比EasyAR公司要大点。但是由于个人是IOS开发,而亮风台的开发都是基于Unity3d技术的,所以才看的EasyAR的官方文档。

如果想初步学习,或者说了解AR的话EasyAR确实是个不错的平台,但是对IOS纯开发还是欠缺很多文档,所以我也只是根据下载的demo修改了下识别图片或视频。网上有使用Unity3d直接修改显示的模型的,我找了好久Unity3d的demo中也没有发现在哪里可以修改,如果有哪位大神看到,希望可以指点下。

上文的球形图就很明显给出3d效果,这就是AR的有意思的部分,你用这个技术去拍手机上的图片时就会在照相机中看到手机屏外放着一个球,这样的技术确实吸引人。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速