CoreFoundation and fork()

I’ll quote the Leopard CoreFoundation Framework Release Notes. I don’t know if they’re still online, since Apple tends to replace rather than extend the Core Foundation release notes.

CoreFoundation and fork()

Due to the behavior of fork(), CoreFoundation cannot be used on the child-side of fork(). If you fork(), you must follow that with an exec*() call of some sort, and you should not use CoreFoundation APIs within the child, before the exec*(). The applies to all higher-level APIs which use CoreFoundation, and since you cannot know what those higher-level APIs are doing, and whether they are using CoreFoundation APIs, you should not use any higher-level APIs either. This includes use of the daemon() function.

Additionally, per POSIX, only async-cancel-safe functions are safe to use on the child side of fork(), so even use of lower-level libSystem/BSD/UNIX APIs should be kept to a minimum, and ideally to only async-cancel-safe functions.

This has always been true, and there have been notes made of this on various Cocoa developer mailling lists in the past. But CoreFoundation is taking some stronger measures now to “enforce” this limitation, so we thought it would be worthwhile to add a release note to call this out as well. A message is written to stderr when something uses API which is definitely known not to be safe in CoreFoundation after fork(). If file descriptor 2 has been closed, however, you will get no message or notice, which is too bad. We tried to make processes terminate in a very recognizable way, and did for a while and that was very handy, but backwards binary compatibility prevented us from doing so.

In other words, it has never been safe to do much of anything on the child side of a fork() other than exec a new program.

Besides the general POSIX prohibition, an oft-mentioned explanation is: a) pretty much all Cocoa programs are multithreaded these days, what with GCD and the like. B) when you fork(), only the calling thread survives into the child process; the other threads just vanish. Since those threads could have been manipulating shared resources, the child process can’t rely on having sane state. For example, the malloc() implementation may have a lock to protect shared structures and that lock could have been held by one of the now-gone threads at the time of the fork(). So, if the remaining thread tries to allocate memory, it may hang indefinitely. Other shared data structures may simply be in a corrupted state. Etc.

mac上默认的python版本为2.7.10版本,需要升级到python3 版本,通过brew升级

mac上默认的python版本为2.7.10版本,需要升级到python3 版本,通过brew升级

  • $brew install python3

提示错误

$ brew install python3 Warning: python3 3.6.3 is already installed, it's just not linked. You can use `brew link python3` to link this version. $ brew link python3 Linking /usr/local/Cellar/python3/3.6.3... Error: Permission denied @ dir_s_mkdir

发现/usr/local/下没有路径/usr/local/Frameworks
需要新建该路径,并修改权限

解决:

$ sudo mkdir /usr/local/Frameworks $ sudo chown $(whoami):admin /usr/local/Frameworks
成功:

$ brew link python3 Linking /usr/local/Cellar/python3/3.6.3... 1 symlinks created

Base64是一种用64个字符来表示任意二进制数据的方法

Base64是一种用64个字符来表示任意二进制数据的方法。

用记事本打开exejpgpdf这些文件时,我们都会看到一大堆乱码,因为二进制文件包含很多无法显示和打印的字符,所以,如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法。Base64是一种最常见的二进制编码方法。

Base64的原理很简单,首先,准备一个包含64个字符的数组:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:

base64-encode

这样我们得到4个数字作为索引,然后查表,获得相应的4个字符,就是编码后的字符串。

所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。

如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。

Python内置的base64可以直接进行base64的编解码:

>>> import base64
>>> base64.b64encode('binary\x00string')
'YmluYXJ5AHN0cmluZw=='
>>> base64.b64decode('YmluYXJ5AHN0cmluZw==')
'binary\x00string'

由于标准的Base64编码后可能出现字符+/,在URL中就不能直接作为参数,所以又有一种”url safe”的base64编码,其实就是把字符+/分别变成-_

>>> base64.b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd++//'
>>> base64.urlsafe_b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd--__'
>>> base64.urlsafe_b64decode('abcd--__')
'i\xb7\x1d\xfb\xef\xff'

还可以自己定义64个字符的排列顺序,这样就可以自定义Base64编码,不过,通常情况下完全没有必要。

Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。

Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。

由于=字符也可能出现在Base64编码中,但=用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=去掉:

# 标准Base64:
'abcd' -> 'YWJjZA=='
# 自动去掉=:
'abcd' -> 'YWJjZA'

去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

请写一个能处理去掉=的base64解码函数:

>>> base64.b64decode('YWJjZA==')
'abcd'
>>> base64.b64decode('YWJjZA')
Traceback (most recent call last):
  ...
TypeError: Incorrect padding
>>> safe_b64decode('YWJjZA')
'abcd'

小结

Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。

iOS进程通信

OS X是MacOS与NeXTSTEP的结合。OC是Smalltalk类面向对象编程与C的结合。iCloud则是苹果移动服务与云平台的结合。

上述都是一些亮点,但是不得不说苹果技术中的进程通讯走的是“反人类”的道路。

由于不是根据每个节点上最优原则进行设计,苹果的进程间通信解决方案更显得混乱扎堆。结果是,大量重叠,不兼容的IPC技术在各个抽象层随处可见。(除了GCD还有剪贴板)

  • Mach Ports
  • Distributed Notifications
  • Distributed Objects
  • AppleEvents & AppleScript
  • Pasteboard
  • XPC

从低级内核抽象到高级,面向对象的API,它们都有各自特殊的表现以及安全特性。但是基础层面来看,它们都是从不同上下文段传递或者获取数据的机制。

分述

Mach Ports

所有的进程间通讯最终落实依赖的还是Mach内核API提供的功能。

Mach端口是轻量并且强大的而又缺少相关文档晦涩使用的(天使与恶魔)。

通过一个Mach端口发送一个消息调用一次mach_msg_send方法,但是这里需要做一些配置来构建待发送的消息:

  1. natural_t data;
  2. mach_port_t port;
  3. struct {
  4. mach_msg_header_t header;
  5. mach_msg_body_t body;
  6. mach_msg_type_descriptor_t type;
  7. } message;
  8. message.header = (mach_msg_header_t) {
  9. .msgh_remote_port = port,
  10. .msgh_local_port = MACH_PORT_NULL,
  11. .msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
  12. .msgh_size = sizeof(message)
  13. };
  14. message.body = (mach_msg_body_t) {
  15. .msgh_descriptor_count = 1
  16. };
  17. message.type = (mach_msg_type_descriptor_t) {
  18. .pad1 = data,
  19. .pad2 = sizeof(data)
  20. };
  21. mach_msg_return_t error = mach_msg_send(&message.header);
  22. if (error == MACH_MSG_SUCCESS) {
  23. // …
  24. }

(消息)接收端稍微轻松点,因为消息只需要被声明而不用初始化:

  1. mach_port_t port;
  2. struct {
  3. mach_msg_header_t header;
  4. mach_msg_body_t body;
  5. mach_msg_type_descriptor_t type;
  6. mach_msg_trailer_t trailer;
  7. } message;
  8. mach_msg_return_t error = mach_msg_receive(&message.header);
  9. if (error == MACH_MSG_SUCCESS) {
  10. natural_t data = message.type.pad1;
  11. // …
  12. }

还算不错的是,Core FoundationFoundation为Mach端口提供了高级API。在内核基础上封装的CFMachPort / NSMachPort可以用做runloop源,尽管CFMachPort / NSMachPort有利于的是两个不同端口之间的通讯同步。

CFMessagePort确实非常适合用于简单的一对一通讯。简简单单几行代码,一个本地端口就被附属到runloop源上,只要获取到消息就执行回调。

  1. static CFDataRef Callback(CFMessagePortRef port,
  2. SInt32 messageID,
  3. CFDataRef data,
  4. void *info)
  5. {
  6. // …
  7. }
  8. CFMessagePortRef localPort =
  9. CFMessagePortCreateLocal(nil,
  10. CFSTR(“com.example.app.port.server”),
  11. Callback,
  12. nil,
  13. nil);
  14. CFRunLoopSourceRef runLoopSource =
  15. CFMessagePortCreateRunLoopSource(nil, localPort, 0);
  16. CFRunLoopAddSource(CFRunLoopGetCurrent(),
  17. runLoopSource,
  18. kCFRunLoopCommonModes);

若要进行发送数据同样也十分直截了当。只要完成指定远端的端口,装载数据,还有设置发送与接收的超时时间的操作。剩下就由CFMessagePortSendRequest来接管了。

  1. CFDataRef data;
  2. SInt32 messageID = 0x1111; // Arbitrary
  3. CFTimeInterval timeout = 10.0;
  4. CFMessagePortRef remotePort =
  5. CFMessagePortCreateRemote(nil,
  6. CFSTR(“com.example.app.port.client”));
  7. SInt32 status =
  8. CFMessagePortSendRequest(remotePort,
  9. messageID,
  10. data,
  11. timeout,
  12. timeout,
  13. NULL,
  14. NULL);
  15. if (status == kCFMessagePortSuccess) {
  16. // …
  17. }

Distributed Notifications

在Cocoa中有很多种两个对象进行通信的途径。

当然也能进行直接消息传递。也有像目标-动作,代理,回调这些解耦,一对一的设计模式。KVO允许让很多对象订阅一个事件,但是它把这些对象都联系起来了。另一方面通知让消息全局广播,并且让有监听该广播的对象接收该消息。【注:想知道发了多少次广播吗?添加 NSNotificationCenter addObserverForName:object:queue:usingBlock,其中name与object置nil,看block被调用了几次。】

每个应用为基础应用消息发布-订阅对自身通知中心实例进行管理。但是鲜有人知的APICFNotificationCenterGetDistributedCenter的通知可以进行系统级别范围的通信。

为了获取通知,添加所要指定监听消息名的观察者到通知发布中心,当消息接收到的时候函数指针指向的函数将被执行一次:

  1. static void Callback(CFNotificationCenterRef center,
  2. void *observer,
  3. CFStringRef name,
  4. const void *object,
  5. CFDictionaryRef userInfo)
  6. {
  7. // …
  8. }
  9. CFNotificationCenterRef distributedCenter =
  10. CFNotificationCenterGetDistributedCenter();
  11. CFNotificationSuspensionBehavior behavior =
  12. CFNotificationSuspensionBehaviorDeliverImmediately;
  13. CFNotificationCenterAddObserver(distributedCenter,
  14. NULL,
  15. Callback,
  16. CFSTR(“notification.identifier”),
  17. NULL,
  18. behavior);

发送端代码更为简单,只要配置好ID,对象还有user info

  1. void *object;
  2. CFDictionaryRef userInfo;
  3. CFNotificationCenterRef distributedCenter =
  4. CFNotificationCenterGetDistributedCenter();
  5. CFNotificationCenterPostNotification(distributedCenter,
  6. CFSTR(“notification.identifier”),
  7. object,
  8. userInfo,
  9. true);

链接两个应用通信的方式中,分发式通知是最为简单的。用它来进行大量数据的传输是不明智的,但是对于轻量级信息同步,分发式通知堪称完美。

Distributed Objects

90年代中NeXT全盛时期,分发式对象(DO)是Cocoa框架中一个远程消息发送特性。尽管现在已经不再大范围的使用,在现代奇数层上IPC无障碍通信仍然并未实现。

使用DO分发一个对象仅仅是搭建一个NSConnection并将其注册为特殊(你分的清楚)的名字:

  1. @protocol Protocol;
  2. id <Protocol> vendedObject;
  3. NSConnection *connection = [[NSConnection alloc] init];
  4. [connection setRootObject:vendedObject];
  5. [connection registerName:@”server”];

另外一个应用将会也建立同样名字的并注册过的链接,然后立即获取一个原子代理当做原始对象。

  1. id proxy = [NSConnection rootProxyForConnectionWithRegisteredName:@”server” host:nil];
  2. [proxy setProtocolForProxy:@protocol(Protocol)];

只要分发对象代理收到消息了,一个通过NSConnection连接远程调用(RPC)将会根据发送对象进行对应的计算并且返回结果给代理。【注:原理是一个OS管理的共享的NSPortNameServer实例对这个带着名字的连接进行管控。】

分发式对象简单,透明,健壮。简直就是Cocoa中的标杆。。。

实际上,分布式对象不能像局部对象那样使用,那就是因为任何发送给代理的消息都可能抛出异常。不想其他语言,OC没有异常处理控制流程。所以对任何东西都进行@try/@catch也算是Cocoa大会很凄凉的补救了。

DO还有一个原因致其使用不便。在试图通过连接“marshal values”时,对象和原语的差距尤为明显。
此外,连接是完全加密的,和下方通信信道扩展性的缺乏致使其在大多数的使用中通信被迫中断。

下方是左列分布式对象用来指定其属性代理行为和方法参数的注解:

  • in:输入参数,后续不再引用
  • out:参数被引用作为返回值
  • inout:输入参数,引用作为返回值
  • const:常量参数
  • oneway:无障碍结果返回
  • bycopy:返回对象的拷贝
  • byref:返回对象的代理

AppleEvents & AppleScript

AppleEvents是经典Macintosh操作系统最持久的遗产。在System 7推出的AppleEvents允许应用程序在本地使用AppleScript或者使用程序链接的功能进行程序控制。现在AppleScript使用Cocoa Scripting Bridge,仍然是OS X应用进程间最直接的交互方式。【注:Mac系统的苹果时间管理中心为AppleEvents提供了原始低级传送机制,但是是在OS X的Mach端口基础之上的重实现】。

也就是说,使用起来这是简单而又古怪的技术之一。

AppleScript使用自然语言语法,设计初衷是没有涉及参数而更容易掌握。虽然与人交流更亲和了,但是写起来确实噩梦。

为了更好的了解人类自然性,这里有个栗子教你怎么让Safari在最前的窗口的激活栏打开一个URL。

  1. tell application "Safari"
  2. set the URL of the front document to "http://nshipster.com"
  3. end tell

在大部分情况下,AppleScript的语法自然语言的特性更多是不便不是优势。(吐槽。。。略略略)

即便是经验老道的OC开发者,不靠文档或者栗子写出AppleScript是不可能的任务。

幸运的是,Scripting Bridge为Cocoa应用提供了更友善的编程接口。

Cocoa Scripting Bridge

为了使用Scripting Bridge与应用进行交互,首先要先添加一个编程接口:

  1. $ sdef /Applications/Safari.app | sdp -fh --basename Safari

sdef为应用生成脚本定义文件。这些文件可以以管道输入道sdp并格式转成(在这里是)C头文件。这样的结果是添加该头文件到应用工程并提供第一类对象接口。

这里举个栗子来解释如何使用Cocoa Scripting Bridge:

  1. #import “Safari.h”
  2. SafariApplication *safari = [SBApplication applicationWithBundleIdentifier:@“com.apple.Safari”];
  3. for (SafariWindow *window in safari.windows) {
  4. if (window.visible) {
  5. window.currentTab.URL = [NSURL URLWithString:@“http://nshipster.com”];
  6. break;
  7. }
  8. }

对比AppleScript上面显得冗繁了点,但是却更容易集成到已存在的代码中去。在可读性上更优因为毕竟长得更像OC。

唉,AppleScript的星芒也正出现消退,在最近发布的OS X与iWork应用证答复减少它的戏份。从这点说,未必值得在你的应用中去添加这项(脚本)支持。

Pasteboard

剪贴板是OS X与iOS最常见的进程间通信机制。当用户跨应用拷贝了一段文字,图片,文档,这时候通过mach port的com.apple.pboard服务媒介进行从一个进程到另一个进程的数据交换。

OS X上是NSPasteboard,iOS上对应的是UIPasteboard。它们几乎是别无二致,但尽管大致一样,对比OS X iOS上提供了更简洁,更现代化却又不影响功效的API。

编写剪贴板代码几乎就跟在GUI应用上使用Edit > Copy操作一样简单:

  1. NSImage *image;
  2. NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  3. [pasteboard clearContents];
  4. [pasteboard writeObjects:@[image]];

因为剪贴动作太频繁了,所以要确认剪贴内容是否是你(应用)所需要得:

  1. NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  2. if ([pasteboard canReadObjectForClasses:@[[NSImage class]] options:nil]) {
  3. NSArray *contents = [pasteboard readObjectsForClasses:@[[NSImage class]] options: nil];
  4. NSImage *image = [contents firstObject];
  5. }

XPC

XPC是SDK中最先进的进程间通讯技术。它架构之初的目的在于避免长时间得运行过程,来适应有限的资源,在可能运行的时候才进行初始化。把XPC纳入应用而不做任何事情的想法是不现实的,但这样提供了更好的进程间的特权分离和故障隔离。

XPC作为NSTask替代品甚至更多。

2011推出以来,XPC为OS X上的应用沙盒提供基础设施,iOS上的远程试图控制器,还有两个平台上的应用扩展。它还广范围的用在系统框架和第一方应用:

  1. $ find /Applications -name \*.xpc

控制台输入上面的命令行你会知道XPC无处不在。在一般应用中同样的情形也在发生,比如图片或者视频转变服务,系统调用,网页服务加载,或是第三方的授权。

XPC负责进程间通讯的同时还负责该服务生命周期的管理。包括注册服务,启动,以及通过launchd解决服务之间的通讯。一个XPC服务可以根据需求地洞,或者在崩溃的时候重启,或者是空闲的时候终止。正因如此,服务可以完全被设计成无状态的,以便于在运行的任何时间点的突然终止都能做到影响不大。

作为被iOS还有OS X中backported所采用的安全模块,XPC服务默认运行在最为严格的环境:不能访问文件,不能访问网络,没有根权限升级。任何能做的事情就是对照被赋予的白名单列表。

XPC可以被libxpc C API访问,或者是NSXPCConnection OC API。【注:作者会用低级API去实现(纯C)】

XPC服务要么存在于应用的沙盒中亦或是使用launchd调用跑在后台。

服务调用带事件句柄的xpc_main来获取新的XPC连接。

  1. static void connection_handler(xpc_connection_t peer) {
  2. xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
  3. peer_event_handler(peer, event);
  4. });
  5. xpc_connection_resume(peer);
  6. }
  7. int main(int argc, const char *argv[]) {
  8. xpc_main(connection_handler);
  9. exit(EXIT_FAILURE);
  10. }

每个XPC连接是一对一的,意味着服务在不同的连接进行操作,每次调用xpc_connection_create就会创建一个新的链接。【注:类似BSD套接字中的API accept函数,服务在单个文件描述符进行监听来为范围内的链接创建额外描述符】:

  1. xpc_connection_t c = xpc_connection_create(“com.example.service”, NULL);
  2. xpc_connection_set_event_handler(c, ^(xpc_object_t event) {
  3. // …
  4. });
  5. xpc_connection_resume(c);

当一个消息发送到XPC链接,将自动的派发到一个由runtime管理的消息队列中。当链接的远端一旦开启的时候,消息将出队并被发送。

每个消息就是一个字典,字符串key和强类型值:

  1. xpc_dictionary_t message = xpc_dictionary_create(NULL, NULL, 0);
  2. xpc_dictionary_set_uint64(message, “foo”, 1);
  3. xpc_connection_send_message(c, message);
  4. xpc_release(message)

XPC对象对下列原始类型进行操作:

  • Data
  • Boolean
  • Double
  • String
  • Signed Integer
  • Unsigned Integer
  • Date
  • UUID
  • Array
  • Dictionary
  • Null

XPC提供了一个便捷的方法来从dispatch_data_t数据类型进行转换,这样从GCD到XPC的工作流程就简化了:

  1. void *buffer;
  2. size_t length;
  3. dispatch_data_t ddata =
  4. dispatch_data_create(buffer,
  5. length,
  6. DISPATCH_TARGET_QUEUE_DEFAULT,
  7. DISPATCH_DATA_DESTRUCTOR_MUNMAP);
  8. xpc_object_t xdata = xpc_data_create_with_dispatch_data(ddata);

服务注册

XPC可以注册成启动项任务,配置成匹配IOKit事件自动启动,BSD通知或者是CFDistributedNotifications。这些标准都指定在服务的launchd.plist文件里:
.launchd.plist

  1. <key>LaunchEvents</key>
  2. <dict>
  3. <key>com.apple.iokit.matching</key>
  4. <dict>
  5. <key>com.example.device-attach</key>
  6. <dict>
  7. <key>idProduct</key>
  8. <integer>2794</integer>
  9. <key>idVendor</key>
  10. <integer>725</integer>
  11. <key>IOProviderClass</key>
  12. <string>IOUSBDevice</string>
  13. <key>IOMatchLaunchStream</key>
  14. <true/>
  15. <key>ProcessType</key>
  16. <string>Adaptive</string>
  17. </dict>
  18. </dict>
  19. </dict>

最近一次对于launchd属性列表的修改是增加了ProcessType Key,其用来在高级层面上描述启动机构的预期目的。根据预描述行为期望,操作系统会响应调整CPU和I/O的阈值。

为了注册一个服务运行大概五分钟的时间,一套标准需要传送给xpc_activity_register

在Xcode中产看QString的数据

lldb中默认没有对于QString的支持,需要自己添加。

1. 在用户目录下创建~/.lldb文件夹,并在文件夹中创建`qstring.py`文件。qstring.py中内容为:

import lldb

def utf16string_summary(value, *rest):

d = value.GetChildMemberWithName(“d”)

length = d.GetChildMemberWithName(“size”).GetValueAsSigned()

offset = d.GetChildMemberWithName(“offset”).GetValueAsSigned()

address = d.GetValueAsUnsigned() + offset

if length == 0:

return ‘””‘

error = lldb.SBError()

# UTF-16, so we multiply length by 2

bytes = value.GetProcess().ReadMemory(address, length * 2, error)

if bytes is None:

return ‘””‘

return ‘”%s”‘ % (bytes.decode(‘utf-16’).encode(‘utf-8’))

def __lldb_init_module(debugger, *rest):

print “registering QString”

summary = lldb.SBTypeSummary.CreateWithFunctionName(“qstring.utf16string_summary”)

summary.SetOptions(lldb.eTypeOptionHideChildren)

debugger.GetDefaultCategory().AddTypeSummary( lldb.SBTypeNameSpecifier(“QString”, False), summary )

2. 创建 ~/.lldbinit文件,写入一条命令`command script import ~/.lldb/qstring.py`

3. 重启Xcode即可生效

参考

https://github.com/tgebarowski/lldb-scripts

QT样式表单

建立步骤:

 1、建立文本文件,写入样式表内容,更改文件后缀名为qss;

 2、在工程中新建资源文件*.qrc,添加前缀文件(/名称),将qss文件加入前缀文件中;

 3、通过传入路径/文件名的方式创建一个QFile对象,以readonly的方式打开,然后readAll,最后qApp->setStyleSheet就可以使qss生效。

QT的样式表单允许我们在对程序不做任何代码上的更改的情况下轻松改变应用程序的外观。

其思想来源于网页设计中的CSS,即可以将功能设计和美学设计分开。

它的语法和概念和HTML CSS也是差不多的。

其原理可简单理解为:QT内部存在一个CSS语法解析器,我们将我们的样式控制以CSS语法定义到外部文件,CSS语法解析器解析后在调用相应的功能模块以完成样式变化。(其实这部分功能我们完全可以通过代码实现,只是这么做既麻烦而且一旦更改会很不方便)

比如:我在CSS定义字体颜色和大小,那么CSS语法解析器解读出我的意图后,可能就会调用freetype模块来实现此功能。

好处:1.将功能设计和美学设计分开

2.CSS设计资源多,查找容易

说明:QT Style 样式语法虽然和CSS语法差不多,但是其功能是其子集,在使用QT style时需具备CSS语法知识

 

CSS语法学习:http://www.w3school.com.cn/h.asp

CSS 参考手册:http://www.w3school.com.cn/cssref/index.asp

基本语法

1.      样式表单由一系列样式规则组成。每条规则可以分成两部分:选择器和声明

         

         选择器表示规则作用到哪些控件上;声明则详细说明了是什么规则。

2.      Qt的样式表语法不区分大小写,所以color,Color,coLor,coloR都表示同样的颜色属性。但是指代类的类名的时候,是区分大小写的。

3.      多个选择器可以并列使用,它们之间用逗号隔开,例如:

         QPushButton,QLineEdit, QComboBox{ color: red }

4.      声明部分也可以有多个并列,之间用分号隔开。当我们要设置的选择器有多个属性的时候,就需要并列多个声明,例如:

         QPushButton{ color: red; background-color: white }

        这条样式规则让按钮的字体变成红色,同时背景色变成白色。

         

选择器类别

Qt样式表单支持所有在CSS2中定义的选择器类型,下面介绍几种最为常用的选择器定义。

全局选择器

*

选中所有的Widget

特定类型选择器

QPushButton

选中所有QPushButton以及它派生出来的子类的对象

属性选择器

QPushButton[flat=”false”]

选中所有flat属性为false的按钮

属性选择器

可用于QT中所有具有toString方法的属性,例如QPushButton的text、checked等属性。

当属性是一个QStringList时,可以用~=这个符号来匹配其中的某一项。

因为属性往往是动态的,当属性更改了的时候,需要调整样式表,通常做法是删除样式表,再重新加载

选择器类别

类选择器

QPushButton

选中所有QPushButton的对象,但不包括其子类

ID选择器

QPushButton#okButton

选中所有object name是okButton的QPushButton对象

子控件选择器

QDialog QPushButton

选中Qdialog上的所有QPushButton子控件(直接子控件,间接子控件

嫡子控件选择器

QDialog > QPushButton

选中所有Qdialog的直接孩子QPushButton

精细控制(子控件)

对于比较复杂的控件,往往由多个子控件构成,比如QComboBox则由文本框和下拉按钮构成,对于QT STYLE 允许我们分别对子控件进行样式设定,这就大大增强了样式的灵活性

举例:QComboBox的下拉点击按钮设置背景图片:

QComboBox::drop-down { image:url(dropdown.png) }

 

关于子控件更多信息与使用方法可以访问

http://qt-project.org/doc/qt-5/stylesheet-examples.html#customizing-qcombobox

伪状态控制

根据具体控件的状态不同,选择器也可以有不同的状态,依次对应控件在不同状态的现实效果

基本写法:

1.      伪状态和选择器类名之间,用一个冒号分隔。

QPushButton:hover { color: white }

2.      伪状态也可以反向选择。

例如:当我们要设置除了鼠标悬停状态外其他所有状态的字体颜色,则可以像下面这样设置:QRadioButton:!hover { color: red }

3.      伪状态也可以并列,之间用冒号连接,表示伪状态之间用AND计算:

QCheckBox:hover:checked { color: white }

QPushButton:hover:!pressed { color: blue; }

4.      伪状态之间可以用OR计算:

QCheckBox:hover, QCheckBox:checked { color:white }

5.      伪状态可以和子控件合起来使用:

QComboBox::drop-down:hover { image:url(dropdown_bright.png) }

STYLE基础知识的说明

我们控制STYLE主要是将STYLE添加到控件上,通过控制控件不同状态的STYLE以达到整体的显示效果

那对于控件,我将其分解为以下几部分来理解

Ø  从层次上来说:

控件可分为前景与背景

前景:多包含文件,图片等内容

背景:多包含图片,图形等内容

Ø  从结构上来说:

由于QT style是模拟CSS的布局结构,因此其满足CSS的盒子模型

从里到外的4个区域分别是:

1: content

2:  padding

3:  border

4:  margin

通过控制一个控件的前景,背景内容已经结构上的4个区域,我们就可以达到对一个控件为所欲为的控制

QT如何下使用QSS

1.       定义样式文件


2.       将样式文件添加到资源中

3.       代码端加载样式文件

CSS基本功能

CSS的强大在于组合功能的强大,这里只是简单介绍基本功能,将简单功能组合起来才能实现好看的效果。

CSS 背景属性(Background)

属性

描述

CSS

background

在一个声明中设置所有的背景属性。

1

background-attachment

设置背景图像是否固定或者随着页面的其余部分滚动。

1

background-color

设置元素的背景颜色。

1

background-image

设置元素的背景图像。

1

background-position

设置背景图像的开始位置。

1

background-repeat

设置是否及如何重复背景图像。

1

background-clip

规定背景的绘制区域。

3

background-origin

规定背景图片的定位区域。

3

background-size

规定背景图片的尺寸。

3

CSS 边框属性(Border 和 Outline)

属性

描述

CSS

border

在一个声明中设置所有的边框属性。

1

border-bottom

在一个声明中设置所有的下边框属性。

1

border-bottom-color

设置下边框的颜色。

2

border-bottom-style

设置下边框的样式。

2

border-bottom-width

设置下边框的宽度。

1

border-color

设置四条边框的颜色。

1

border-left

在一个声明中设置所有的左边框属性。

1

border-left-color

设置左边框的颜色。

2

border-left-style

设置左边框的样式。

2

border-left-width

设置左边框的宽度。

1

border-right

在一个声明中设置所有的右边框属性。

1

border-right-color

设置右边框的颜色。

2

border-right-style

设置右边框的样式。

2

border-right-width

设置右边框的宽度。

1

border-style

设置四条边框的样式。

1

border-top

在一个声明中设置所有的上边框属性。

1

border-top-color

设置上边框的颜色。

2

border-top-style

设置上边框的样式。

2

border-top-width

设置上边框的宽度。

1

border-width

设置四条边框的宽度。

1

outline

在一个声明中设置所有的轮廓属性。

2

outline-color

设置轮廓的颜色。

2

outline-style

设置轮廓的样式。

2

outline-width

设置轮廓的宽度。

2

border-bottom-left-radius

定义边框左下角的形状。

3

border-bottom-right-radius

定义边框右下角的形状。

3

border-image

简写属性,设置所有 border-image-* 属性。

3

border-image-outset

规定边框图像区域超出边框的量。

3

border-image-repeat

图像边框是否应平铺(repeated)、铺满(rounded)或拉伸(stretched)。

3

border-image-slice

规定图像边框的向内偏移。

3

border-image-source

规定用作边框的图片。

3

border-image-width

规定图片边框的宽度。

3

border-radius

简写属性,设置所有四个 border-*-radius 属性。

3

border-top-left-radius

定义边框左上角的形状。

3

border-top-right-radius

定义边框右下角的形状。

3

box-decoration-break

3

box-shadow

向方框添加一个或多个阴影。

3

Box 属性

属性

描述

CSS

overflow-x

如果内容溢出了元素内容区域,是否对内容的左/右边缘进行裁剪。

3

overflow-y

如果内容溢出了元素内容区域,是否对内容的上/下边缘进行裁剪。

3

overflow-style

规定溢出元素的首选滚动方法。

3

rotation

围绕由 rotation-point 属性定义的点对元素进行旋转。

3

rotation-point

定义距离上左边框边缘的偏移点。

3

CSS 字体属性(Font)

属性

描述

CSS

font

在一个声明中设置所有字体属性。

1

font-family

规定文本的字体系列。

1

font-size

规定文本的字体尺寸。

1

font-size-adjust

为元素规定 aspect 值。

2

font-stretch

收缩或拉伸当前的字体系列。

2

font-style

规定文本的字体样式。

1

font-variant

规定是否以小型大写字母的字体显示文本。

1

font-weight

规定字体的粗细。

1

CSS 外边距属性(Margin)

属性

描述

CSS

margin

在一个声明中设置所有外边距属性。

1

margin-bottom

设置元素的下外边距。

1

margin-left

设置元素的左外边距。

1

margin-right

设置元素的右外边距。

1

margin-top

设置元素的上外边距。

1

CSS 内边距属性(Padding)

属性

描述

CSS

padding

在一个声明中设置所有内边距属性。

1

padding-bottom

设置元素的下内边距。

1

padding-left

设置元素的左内边距。

1

padding-right

设置元素的右内边距。

1

padding-top

设置元素的上内边距。

1

CSS 定位属性(Positioning)

属性

描述

CSS

bottom

设置定位元素下外边距边界与其包含块下边界之间的偏移。

2

clear

规定元素的哪一侧不允许其他浮动元素。

1

clip

剪裁绝对定位元素。

2

cursor

规定要显示的光标的类型(形状)。

2

display

规定元素应该生成的框的类型。

1

float

规定框是否应该浮动。

1

left

设置定位元素左外边距边界与其包含块左边界之间的偏移。

2

overflow

规定当内容溢出元素框时发生的事情。

2

position

规定元素的定位类型。

2

right

设置定位元素右外边距边界与其包含块右边界之间的偏移。

2

top

设置定位元素的上外边距边界与其包含块上边界之间的偏移。

2

vertical-align

设置元素的垂直对齐方式。

1

visibility

规定元素是否可见。

2

z-index

设置元素的堆叠顺序。

CSS 文本属性(Text)

属性

描述

CSS

color

设置文本的颜色。

1

direction

规定文本的方向 / 书写方向。

2

letter-spacing

设置字符间距。

1

line-height

设置行高。

1

text-align

规定文本的水平对齐方式。

1

text-decoration

规定添加到文本的装饰效果。

1

text-indent

规定文本块首行的缩进。

1

text-shadow

规定添加到文本的阴影效果。

2

text-transform

控制文本的大小写。

1

unicode-bidi

设置文本方向。

2

white-space

规定如何处理元素中的空白。

1

word-spacing

设置单词间距。

1

hanging-punctuation

规定标点字符是否位于线框之外。

3

punctuation-trim

规定是否对标点字符进行修剪。

3

text-align-last

设置如何对齐最后一行或紧挨着强制换行符之前的行。

3

text-emphasis

向元素的文本应用重点标记以及重点标记的前景色。

3

text-justify

规定当 text-align 设置为 “justify” 时所使用的对齐方法。

3

text-outline

规定文本的轮廓。

3

text-overflow

规定当文本溢出包含元素时发生的事情。

3

text-shadow

向文本添加阴影。

3

text-wrap

规定文本的换行规则。

3

word-break

规定非中日韩文本的换行规则。

3

word-wrap

允许对长的不可分割的单词进行分割并换行到下一行。

3

网址:http://blog.csdn.net/tiankefeng19850520/article/details/27183403?utm_source=tuicool&utm_medium=referral

——————— 本文来自 yansmile1 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/yansmile1/article/details/52882965?utm_source=copy

MAC开发–关于暗色菜单栏体验差而审核被拒

第一次上架MAC APP到苹果商店,奈何被拒了又拒,内心是无比得绝望,这次又是一个万万没注意到的问题被拒绝了,拿出来给大家分享一下。

先上被拒绝的理由:
Design Preamble
The user interface of your app is not consistent with the macOS Human Interface Guidelines. Specifically:
We found that when Dark Mode is enabled, the menu bar extra icons aren’t visible.
Next Steps
Please revise your app to address all instances of this type of issue.

重点是,当菜单栏设置为暗色模式时,菜单栏中的icon图标不可见。

这时候我才注意到我们的icon只设计了黑色样式,当菜单栏切换到暗色时,就看不到图标了。于是让UI设计人员添加了一套白色的图标。以下是相关代码:

1、判断用户选择的菜单栏模式

NSString *statusMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
  if ([statusMode isEqualToString:@"Dark"]) {
        imageName = @"statusBarIcon_White";
    }else {
        imageName = @"statusBarIcon_Black";
    }

2、监听菜单栏模式的修改

[[NSDistributedNotificationCenter defaultCenter]addObserver:self selector:@selector(themeModeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];

Checking macOS and AppKit Versions

To check for new features provided by Cocoa frameworks at runtime, look for a given new class or method dynamically. Don’t use it if it isn’t there.

In Swift, you use #available:

if #available(macOS 10.14, *) {
    // Code for macOS 10.14 or later.
} else {
    // Code for versions earlier than 10.14.
}

Starting in Xcode 9, you use @available from Objective-C:

if (@available(macOS 10.14, *)) {
    // Code for macOS 10.14 or later
} else {
    // Code for versions earlier than 10.14.
}

You can also use the global constant NSAppKitVersionNumber (NSAppKitVersion.NSAppKitVersionNumber in Swift).

let current = NSAppKitVersion.current
 
if current < NSAppKitVersion.macOS10_9 {
    /* On a 10.9.x or earlier system */
} else if current <= NSAppKitVersion.macOS10_10 {
    /* On a 10.10 system */
} else if current <= NSAppKitVersion.macOS10_10_Max {
    /* on a 10.10.x system */
// ...
} else if current <= NSAppKitVersion.macOS10_13 {
    /* on a 10.13 or 10.13.x system */
} else {
    /* on a 10.14 or later system */
}

Unlike most AppKit software updates, macOS 10.10 software updates incremented the AppKit major version, which accounts for the specific treatment of NSAppKitVersionNumber10_10_Max in the example above. Other special cases or situations for version checking are discussed in the release notes as appropriate. Some individual headers may also declare an AppKit version number where some bug fix or functionality is available in a given update, for example:

static const NSAppKitVersion NSAppKitVersionWithSuchAndSuchBugFix = 1138.42;

【iOS】私有API的使用

API 的分类

iOS 中的 API 大致分为三种:Published API(公开的 API)、UnPublished API(未公开的 API)和 Private API(私有 API)。 我们日常使用的 API 都是公开的 API,存放在 Frameworks 框架中。而未公开的 API 是指虽然也存放在 Frameworks 框架中,但是却没有在苹果的官方文档中有使用说明、代码介绍等记录的 API。 私有 API 则是指存放在 PrivateFrameworks 框架中的 API。通常,这两者都被称作私有 API,不过在使用方法上还是有一定区别的。苹果明确规定上架 Appstore 的应用不能使用私有 API,不过自己私下玩一玩还是挺有意思的。私有 api 的头文件在 Xcode 中是无法查看的,需要使用class-dump导出,不过早有大神导出了完整的头文件供我们使用,大家可以前往 Github 查看。

UnPublished API(未公开 API)

未公开的 API 虽然也存放在 Frameworks 框架中,但是却没有在苹果的官方文档中有使用说明、代码介绍等记录。按苹果的说法,未公开的 API 还不够成熟,可能还会变动,等完全成型了之后就会变成公开的 API,但是目前不对其提供承诺,系统版本升级后可能会失效。下面用一个例子来说明未公开 API 的使用方法。在 MobileCoreServices.framework框架中有一个叫做LSApplicationWorkspace 的类,利用该类可以获取到手机上应用的各种信息,包括已安装列表,正在安装列表等等,如图:

屏幕快照 2017-08-04 下午5.32.51.png

接下来我们就尝试利用代码调用该 API,示例程序如下:

Class LSApplicationWorkspace_Class = NSClassFromString(@"LSApplicationWorkspace");
NSObject *workspace = [LSApplicationWorkspace_Class performSelector:NSSelectorFromString(@"defaultWorkspace")];
NSArray *appList = [workspace performSelector:NSSelectorFromString(@"allApplications")];
for (id app in appList) {
   NSLog(@"%@", [app performSelector:NSSelectorFromString(@"applicationIdentifier")]);
}

我们获取到的数组中存放的实际上是LSApplicationProxy类型的对象,该对象有一个名为 applicationIdentifier 的属性,如图所示:

屏幕快照 2017-08-04 下午5.36.51.png

调用此属性,即可得到应用的包名信息,如下图所示:

屏幕快照 2017-08-04 下午5.38.52.png

可以看到,未公开 API 的调用实际上只需要将类名、方法名等从字符串进行转化,随后利用 performSelector 方法进行调用即可,相当简单。

Private API(私有 API)

私有 API 是指存放在 PrivateFrameworks 框架中的 API。私有 API 的调用与未公开 API 唯一的差别在于调用私有 API 之前需要先加载私有 API 所在的库到内存当中。下面我们用MobileContainerManager.framework中的一个类MCMAppContainer来做介绍,利用该API可以根据包名来判断某APP是否存在,不过无法确定应用的状态为安装中或已安装,调用方法如下:

NSBundle *container = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"];
if ([container load]) {
    Class appContainer = NSClassFromString(@"MCMAppContainer");
    id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
    if (test) {
        NSLog(@"存在该应用");
    }
}

当然,还有另外一种加载方法,如下:

#import <dlfcn.h>

void *lib = dlopen("/System/Library/PrivateFrameworks/MobileContainerManager.framework", RTLD_LAZY);
if (lib) {
    Class appContainer = NSClassFromString(@"MCMAppContainer");
    id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
    if (test) {
       NSLog(@"存在该应用");
    }
    dlclose(lib);
}

绕过审核

虽然公开 API 中已经提供了大量封装好的方法,但是架不住产品经理的各种奇葩需求。工作过程中很有可能会遇到公开 API 解决不了问题的时候。这个时候我们就不得不求助于私有 API 了。可是一旦使用私有 API,上架 Appstore 又成为了一个难题。这里提供一种绕过审核的方法,不过不太提倡使用,被逼无奈的情况下可以尝试一下。当然,这种方法也还是会有审核时被查出的风险。

苹果审核时可能会通过检索关键词来检查私有 API 的使用情况,因此我们可以尝试先将类名、方法名、路径名等进行加密处理,当调用私有 API 时,将加密后的字符串传入解密方法中进行解密处理。如下所示:

//base64编码
- (NSString *)encodeString:(NSString *)string
{
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    NSString *encodedStr = [data base64EncodedStringWithOptions:0];
    return encodedStr;
}
//base64解码
- (NSString *)decodeString:(NSString *)string
{
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    NSString *decodedStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return decodedStr;
}
//调用私有api
- (void)testPrivateApi
{
    NSString *path = [self decodeString:@"L1N5c3RlbS9MaWJyYXJ5L1ByaXZhdGVGcmFtZXdvcmtzL01vYmlsZUNvbnRhaW5lck1hbmFnZXIuZnJhbWV3b3Jr"];
    NSBundle *container = [NSBundle bundleWithPath:path];
    if ([container load]) {
        Class appContainer = NSClassFromString([self decodeString:@"TUNNQXBwQ29udGFpbmVy"]);
        NSString *sel = [self decodeString:@"Y29udGFpbmVyV2l0aElkZW50aWZpZXI6ZXJyb3I6"];
        id test = [appContainer performSelector:NSSelectorFromString(sel) withObject:@"com.tencent.xin" withObject:nil];
        if (test) {
            NSLog(@"存在该应用");
        }
    } 
}

由于代码中没有出现类名、方法名、路径名等关键词,可以极大降低审核时被发现的可能性。当然,此方法仅供参考,不建议使用。

作者:焚雪残阳
链接:https://www.jianshu.com/p/c3dc77924a25
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

xCode工程的文件夹类型 folder References group

最近在处理一些工程时候,偶尔注意到,工程文件里面添加了一些新的样式图标。什么样子呢,先看图,赫赫。

工程中的蓝色文件夹和以前习惯的黄色文件夹是不是不太一样。
今天简单总结一下Folder References and Groups 参考文件夹和分组
1. Group 分组

在xCode的工程中,右键New Group, 即可创建,也就是黄色的文件夹。
工程中想要分类管理的文件,可以分门别类的放在不同的group里面
group本身不影响文件的物理存放,也就是实际存放位置,只是在Project文件中
2.Folder reference
在管理大量资源文件时候,利用folder reference可方便和实际文件夹构造保持一致.
下面是一个简单的创建过程
Step1: 导入文件时选择Reference

Step2:在代码中调用

这时候就不能直接用文件名调用了,需要指定相对文件夹的位置。
譬如说我们在group直接导入时,用的是

//Group情况
UIImage *image = [UIImage imageNamed:@"someImage.png"];
//Reference情况 I
UIImage *image = [UIImage imageNamed:@"Cats/Dogs/someImage.png"];
//Reference情况 II
NSString *fullPath = [[NSBundle mainBundle] pathForResource:@"someImageFile.png" ofType:nil
inDirectory:@"Cats/Dogs"];
UIImage *image = [UIImage imageWithContentsOfFile:fullPath];

注意的几个事情
1.导入文件时候,一个文件夹里面有多个子文件夹时,根据需要选择Group形式还是Reference形式导入。
group的好处时,一个逻辑结构引入,物理文件实际都在工程中直接用文件名调用即可。
尤其是在引入外部的Lib时候,需要选择该类型,否则编译时候出错概率会很大
2.folder reference的好处更像是在一个工程中可以引入不同的文件夹,文件名可以复用。