Skip to content

Commit

Permalink
Adding string conversions and related changes
Browse files Browse the repository at this point in the history
  • Loading branch information
gershnik committed Jan 17, 2024
1 parent b64a2f1 commit dfdab4d
Show file tree
Hide file tree
Showing 12 changed files with 736 additions and 61 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
by ObjectiveC runtime or `Block_copy` in plain C++.
* It is possible to use move-only callables.
* All of this is accomplished with NO dynamic memory allocation
- `NSStringCharAccess` now conforms completely to `std::ranges::random_access_range`
- `BoxUtil.h`: boxing now detects comparability and enables `compare:` not just via presence of operator `<=>` but also when only operators `<`, `==`, `<=` etc. are present.
- `BoxUtil.h`: generated ObjectiveC box classes now have names unique to each shared library/main executable, preventing collisions if multiple modules use boxing.

### Added
- `NSStringUtil.h`: added `makeNSString`, `makeCFString` and `makeStdString` conversion functions between C++ character ranges and ObjectiveC strings.

## [2.3] - 2024-01-09

### Added
Expand Down
100 changes: 69 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ An ever-growing collection of utilities to make coding on Apple platforms in C++
<!-- TOC depthfrom:2 -->

- [What's included?](#whats-included)
- [BlockUtil.h](#blockutilh)
- [CoDispatch.h](#codispatchh)
- [BoxUtil.h](#boxutilh)
- [NSObjectUtil.h](#nsobjectutilh)
- [NSStringUtil.h](#nsstringutilh)
- [NSNumberUtil.h](#nsnumberutilh)
- [XCTestUtil.h](#xctestutilh)
- [General notes](#general-notes)
- [Convert ANY C++ callable to a block](#convert-any-c-callable-to-a-block)
- [Coroutines that execute on GCD dispatch queues](#coroutines-that-execute-on-gcd-dispatch-queues)
- [Boxing of any C++ objects in ObjectiveC ones](#boxing-of-any-c-objects-in-objectivec-ones)
- [Comparators for ObjectiveC objects](#comparators-for-objectivec-objects)
- [Printing ObjectiveC objects to C++ streams and std::format](#printing-objectivec-objects-to-c-streams-and-stdformat)
- [Accessing NSString/CFString as a char16_t container](#accessing-nsstringcfstring-as-a-char16_t-container)
- [Conversions between NSString/CFString and char/char16_t/char32_t/char8_t/wchar_t ranges](#conversions-between-nsstringcfstring-and-charchar16_tchar32_tchar8_twchar_t-ranges)
- [XCTest assertions for C++ objects](#xctest-assertions-for-c-objects)

<!-- /TOC -->

Expand All @@ -22,9 +22,8 @@ The library is a collection of mostly independent header files. There is nothing

`sample` directory contains a sample that demonstrates the usage of main features.

The headers are as follows:

### `BlockUtil.h` ###
### Convert ANY C++ callable to a block ###

With modern Clang compiler you can seamlessly convert C++ lambdas to blocks like this:
```c++
Expand Down Expand Up @@ -124,9 +123,9 @@ dispatch_async(someQueue, [weakSelf = makeWeak(self)] () {
});
```

### `CoDispatch.h` ###
### Coroutines that execute on GCD dispatch queues ###

Allows you to use **asynchronous** C++ coroutines that execute on GCD dispatch queues. Yes there is [this library](https://github.com/alibaba/coobjc) but it is big, targeting Swift and ObjectiveC rather than C++/\[Objective\]C++ and has a library to integrate with. It also has more features, of course. Here you get basic powerful C++ coroutine support in a single not very large (~800 loc) header.
Header `CoDispatch.h` allows you to use **asynchronous** C++ coroutines that execute on GCD dispatch queues. Yes there is [this library](https://github.com/alibaba/coobjc) but it is big, targeting Swift and ObjectiveC rather than C++/\[Objective\]C++ and has a library to integrate with. It also has more features, of course. Here you get basic powerful C++ coroutine support in a single not very large (~800 loc) header.

Working with coroutines is discussed in greater detail in [a separate doc](doc/CoDispatch.md).

Expand Down Expand Up @@ -203,7 +202,7 @@ int main() {
This facility can also be used both from plain C++ (.cpp) and ObjectiveC++ (.mm) files.


### `BoxUtil.h` ###
### Boxing of any C++ objects in ObjectiveC ones ###

Sometimes you want to store a C++ object where an ObjectiveC object is expected. Perhaps there is
some `NSObject * tag` which you really want to put an `std::vector` in or something similar. You can,
Expand Down Expand Up @@ -265,29 +264,74 @@ assert([box(5) compare:box(6)] == NSOrderingAscending);
```

### `NSObjectUtil.h` ###
### Comparators for ObjectiveC objects ###

`NSObjectEqual` and `NSObjectHash` - functors that provide equality and hash code for any NSObject and allow them to be used as keys in `std::unordered_map` and `std::unordered_set` for example. These are implemented in terms of `isEqual` and `hash` methods of `NSObject`.
Header `NSObjectUtil.h` provides `NSObjectEqual` and `NSObjectHash` - functors that evaluate equality and hash code for any NSObject and allow them to be used as keys in `std::unordered_map` and `std::unordered_set` for example. These are implemented in terms of `isEqual` and `hash` methods of `NSObject`.

`operator<<` for any NSObject to print it to an `std::ostream`. This behaves exactly like `%@` formatting flag by delegating either to `descriptionWithLocale:` or to `description`.
Header `NSStringUtil.h` provides `NSStringLess` and `NSStringLocaleLess` comparators. These allow `NSString` objects to be used as keys in `std::map` or `std::set `as well as used in STL sorting and searching algorithms.

### `NSStringUtil.h` ###
Additionally it provides `NSStringEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToString`.

`NSStringLess` and `NSStringLocaleLess` comparators. These allow `NSString` objects to be used as keys in `std::map` or `std::set `as well as used in STL sorting and searching algorithms..
Header `NSNumberUtil.h` provides `NSNumberLess` comparator. This allows `NSNumber` objects to be used as keys in `std::map` or `std::set` as well as used in STL sorting and searching algorithms.

`NSStringEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToString`.
Additionally it provides `NSNumberEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToNumber`.

`operator<<` to print a `NSString` to an `std::ostream`. This outputs `UTF8String`.

`NSStringCharAccess` - a fast accessor for NSString characters via STL container interface. This uses approach similar to [`CFStringInlineBuffer`](https://developer.apple.com/documentation/corefoundation/cfstringinlinebuffer?language=objc) one.
For all comparators `nil`s are handled properly. A `nil` is equal to `nil` and is less than any non-`nil` object.

### Printing ObjectiveC objects to C++ streams and std::format ###

Header `NSObjectUtil.h` provides `operator<<` for any `NSObject` to print it to an `std::ostream`. This behaves similarly to `%@` formatting flag by delegating either to `descriptionWithLocale:` or to `description`.

Header `NSStringUtil.h` provides additional `operator<<` to print an `NSString` to an `std::ostream`. This outputs `UTF8String`.

Both headers also provide `std::formatter`s with the same functionality if `std::format` is available in the standard library and
`fmt::formatter` if a macro `NS_OBJECT_UTIL_USE_FMT` is defined. In the later case presence of `<fmt/format.h>` or `"fmt/format.h"` include file is required.


### Accessing NSString/CFString as a char16_t container ###

Header `NSStringUtil.h` provides `NSStringCharAccess` - a fast accessor for `NSString` characters (as `char16_t`) via an STL container interface. This uses approach similar to [`CFStringInlineBuffer`](https://developer.apple.com/documentation/corefoundation/cfstringinlinebuffer?language=objc) one. This facility can be used both from ObjectiveC++ and plain C++.

Here are some examples of usage

```cpp

for (char16_t c: NSStringCharAccess(@"abc")) {
...
}

std::ranges::for_each(NSStringCharAccess(@"abc") | std::views::take(2), [](char16_t c) {
...
});
```
Note that `NSStringCharAccess` is a _reference class_ (akin in spirit to `std::string_view`). It does not hold a strong reference to the `NSString`/`CFString` it uses and is only valid as long as that string exists.
### Conversions between `NSString`/`CFString` and `char`/`char16_t`/`char32_t`/`char8_t`/`wchar_t` ranges
Header `NSStringUtil.h` provides `makeNSString` and `makeCFString` functions that accept:
* Any contiguous range of Chars (including `std::basic_string_view`, `std::basic_string`, `std::span` etc. etc.)
* A pointer to a null-terminated C string of Chars
* An `std::initializer_list<Char>`
where Char can be any of `char`, `char16_t`, `char32_t`, `char8_t`, `wchar_t`
and converts it to `NSString`/`CFString`. They return `nil` on failure.
### `NSNumberUtil.h` ###
Conversions from `char16_t` are exact and can only fail when out of memory. Conversions from other formats will fail also when encoding is invalid. Conversions from `char` assume UTF-8 and from `wchar_t`, UTF-32.
`NSNumberLess` comparator. This allows `NSNumber` objects to be used as keys in `std::map` or `std::set` as well as used in STL sorting and searching algorithms.
To convert in the opposite direction the header provides `makeStdString<Char>` overloads. These accept:
`NSNumberEqual` comparator. This is more efficient than `NSObjectEqual` and is implemented in terms of `isEqualToNumber`.
* `NSString *`/`CFStringRef`, optional start position (0 by default) and optional length (whole string by default)
* A pair of `NSStringCharAccess` iterators
* Any range of `NSStringCharAccess` iterators
### XCTestUtil.h ###
They return an `std::basic_string<Char>`. A `nil` input produces an empty string. Similar to above conversions from `char16_t` are exact and conversions to other char types transcode from an appropriate UTF encoding. If the source `NSString *`/`CFStringRef` contains invalid UTF-16 the output is an empty string.
This functionality is available in both ObjectiveC++ and plain C++
### XCTest assertions for C++ objects ###
When using XCTest framework you might be tempted to use `XCTAssertEqual` and similar on C++ objects. While this works and works safely you will quickly discover that when the tests fail you get a less than useful failure message that shows _raw bytes_ of the C++ object instead of any kind of logical description. This happens because in order to obtain the textual description of the value `XCTAssertEqual` and friends stuff it into an `NSValue` and then query its description. And, as mentioned in [BoxUtil.h](#boxutilh) section, `NSValue` simply copies raw bytes of a C++ object.
Expand All @@ -309,10 +353,4 @@ That, in the case of failure, try to obtain description using the following meth
Thus if an object is printable using the typical means those will be automatically used. You can also make your own objects printable using either of the means above. The `testDescription` approach specifically exists to allow you to print something different for tests than in normal code.
### General notes ###

For all comparators `nil`s are handled properly. A `nil` is equal to `nil` and is less than any non-`nil` object.

Why would you want to have `NSObject`s as keys in `std::map` and such instead of using `NSDictionary`? Usually because you want the values to be a non ObjectiveC type, require efficiency or STL compatibility.

4 changes: 4 additions & 0 deletions include/objc-helpers/BlockUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
#ifndef HEADER_BLOCK_UTIL_INCLUDED
#define HEADER_BLOCK_UTIL_INCLUDED

#if !__cpp_concepts
#error This header requires C++20 mode or above with concepts support
#endif

#include <concepts>
#include <type_traits>
#include <cstdlib>
Expand Down
13 changes: 10 additions & 3 deletions include/objc-helpers/CoDispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
#ifndef HEADER_CO_DISPATCH_INCLUDED
#define HEADER_CO_DISPATCH_INCLUDED

#ifndef __cplusplus
#error This header requires C++
#endif

#include <version>

#if !__cpp_impl_coroutine || !__cpp_lib_coroutine
#error This header requires C++20 mode or above with coroutine support
#endif

#include <coroutine>
#include <variant>
#include <memory>
Expand All @@ -20,9 +30,6 @@
#include <Block.h>
#endif

#if !__cpp_impl_coroutine || !__cpp_lib_coroutine
#error This header requires C++20 mode or above with coroutine support
#endif

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-extension"
Expand Down
4 changes: 3 additions & 1 deletion include/objc-helpers/NSNumberUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "NSObjectUtil.h"

#ifdef __OBJC__

#import <Foundation/Foundation.h>

/**
Expand Down Expand Up @@ -45,6 +47,6 @@ struct NSNumberEqual
}
};


#endif //__OBJC__

#endif
4 changes: 4 additions & 0 deletions include/objc-helpers/NSObjectUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#ifndef HEADER_NS_OBJECT_UTIL_INCLUDED
#define HEADER_NS_OBJECT_UTIL_INCLUDED

#ifdef __OBJC__

#import <Foundation/Foundation.h>

#include <ostream>
Expand Down Expand Up @@ -114,4 +116,6 @@ struct fmt::formatter<T, std::enable_if_t<std::is_convertible_v<T, id<NSObject>>

#endif

#endif //__OBJC__

#endif
Loading

0 comments on commit dfdab4d

Please sign in to comment.