1
+ use core:: cmp;
1
2
use core:: ffi:: c_void;
2
3
use core:: fmt;
3
4
use core:: ptr:: NonNull ;
@@ -11,8 +12,9 @@ use objc2::msg_send;
11
12
use objc2:: rc:: DefaultId ;
12
13
use objc2:: rc:: { autoreleasepool, AutoreleasePool } ;
13
14
use objc2:: rc:: { Id , Shared } ;
15
+ use objc2:: runtime:: Bool ;
14
16
15
- use super :: { NSCopying , NSObject } ;
17
+ use crate :: { NSComparisonResult , NSCopying , NSObject } ;
16
18
17
19
#[ cfg( apple) ]
18
20
const UTF8_ENCODING : usize = 4 ;
@@ -24,25 +26,101 @@ const UTF8_ENCODING: i32 = 4;
24
26
const NSNotFound : ffi:: NSInteger = ffi:: NSIntegerMax ;
25
27
26
28
object ! {
29
+ /// A static, plain-text Unicode string object.
30
+ ///
31
+ /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring?language=objc).
27
32
unsafe pub struct NSString : NSObject ;
33
+ // TODO: Use isEqualToString: for comparison (instead of just isEqual:)
34
+ // The former is more performant
35
+
36
+ // TODO: Use more performant Debug implementation.
37
+
38
+ // TODO: Check if performance of NSSelectorFromString is worthwhile
28
39
}
29
40
30
41
// TODO: SAFETY
31
42
unsafe impl Sync for NSString { }
32
43
unsafe impl Send for NSString { }
33
44
34
45
impl NSString {
35
- unsafe_def_fn ! ( pub fn new -> Shared ) ;
46
+ unsafe_def_fn ! {
47
+ /// Construct an empty NSString.
48
+ pub fn new -> Shared ;
49
+ }
36
50
51
+ /// The number of UTF-8 code units in `self`.
52
+ #[ doc( alias = "lengthOfBytesUsingEncoding" ) ]
53
+ #[ doc( alias = "lengthOfBytesUsingEncoding:" ) ]
37
54
pub fn len ( & self ) -> usize {
38
55
unsafe { msg_send ! [ self , lengthOfBytesUsingEncoding: UTF8_ENCODING ] }
39
56
}
40
57
58
+ /// The number of UTF-16 code units in `self`.
59
+ ///
60
+ /// See also [`NSString::len`].
61
+ #[ doc( alias = "length" ) ]
62
+ // TODO: Finish this
63
+ fn len_utf16 ( & self ) -> usize {
64
+ unsafe { msg_send ! [ self , lengt] }
65
+ }
66
+
41
67
pub fn is_empty ( & self ) -> bool {
68
+ // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for
69
+ // other reasons, so this is not really correct!
42
70
self . len ( ) == 0
43
71
}
44
72
45
- /// TODO
73
+ /// Get the [`str`](`prim@str`) representation of this string if it can be
74
+ /// done efficiently.
75
+ ///
76
+ /// Returns [`None`] if the internal storage does not allow this to be
77
+ /// done efficiently. Use [`NSString::to_str`] or [`NSString::to_string`]
78
+ /// if performance is not an issue.
79
+ #[ doc( alias = "CFStringGetCStringPtr" ) ]
80
+ #[ allow( unused) ]
81
+ // TODO: Finish this
82
+ fn as_str_wip ( & self ) -> Option < & str > {
83
+ type CFStringEncoding = u32 ;
84
+ #[ allow( non_upper_case_globals) ]
85
+ // https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings/kcfstringencodingutf8?language=objc
86
+ const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100 ;
87
+ extern "C" {
88
+ // https://developer.apple.com/documentation/corefoundation/1542133-cfstringgetcstringptr?language=objc
89
+ fn CFStringGetCStringPtr ( s : & NSString , encoding : CFStringEncoding ) -> * const c_char ;
90
+ }
91
+ let bytes = unsafe { CFStringGetCStringPtr ( self , kCFStringEncodingUTF8) } ;
92
+ NonNull :: new ( bytes as * mut u8 ) . map ( |bytes| {
93
+ let len = self . len ( ) ;
94
+ let bytes: & [ u8 ] = unsafe { slice:: from_raw_parts ( bytes. as_ptr ( ) , len) } ;
95
+ str:: from_utf8 ( bytes) . unwrap ( )
96
+ } )
97
+ }
98
+
99
+ /// Get an [UTF-16] string slice if it can be done efficiently.
100
+ ///
101
+ /// Returns [`None`] if the internal storage of `self` does not allow this
102
+ /// to be returned efficiently.
103
+ ///
104
+ /// See [`as_str`](Self::as_str) for the UTF-8 equivalent.
105
+ ///
106
+ /// [UTF-16]: https://en.wikipedia.org/wiki/UTF-16
107
+ #[ allow( unused) ]
108
+ // TODO: Finish this
109
+ fn as_utf16 ( & self ) -> Option < & [ u16 ] > {
110
+ extern "C" {
111
+ // https://developer.apple.com/documentation/corefoundation/1542939-cfstringgetcharactersptr?language=objc
112
+ fn CFStringGetCharactersPtr ( s : & NSString ) -> * const u16 ;
113
+ }
114
+ let ptr = unsafe { CFStringGetCharactersPtr ( self ) } ;
115
+ NonNull :: new ( ptr as * mut u16 )
116
+ . map ( |ptr| unsafe { slice:: from_raw_parts ( ptr. as_ptr ( ) , self . len_utf16 ( ) ) } )
117
+ }
118
+
119
+ /// Get the [`str`](`prim@str`) representation of this.
120
+ ///
121
+ /// TODO: Further explain this.
122
+ ///
123
+ /// # Examples
46
124
///
47
125
/// ```compile_fail
48
126
/// # use objc2::rc::autoreleasepool;
@@ -61,6 +139,7 @@ impl NSString {
61
139
/// let ns_string = NSString::new();
62
140
/// let s = autoreleasepool(|pool| ns_string.as_str(pool));
63
141
/// ```
142
+ #[ doc( alias = "UTF8String" ) ]
64
143
pub fn as_str < ' r , ' s : ' r , ' p : ' r > ( & ' s self , pool : & ' p AutoreleasePool ) -> & ' r str {
65
144
// This is necessary until `auto` types stabilizes.
66
145
pool. __verify_is_inner ( ) ;
@@ -78,6 +157,8 @@ impl NSString {
78
157
//
79
158
// So the lifetime of the returned pointer is either the same as the
80
159
// NSString OR the lifetime of the innermost @autoreleasepool.
160
+ //
161
+ // https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc
81
162
let bytes: * const c_char = unsafe { msg_send ! [ self , UTF8String ] } ;
82
163
let bytes = bytes as * const u8 ;
83
164
let len = self . len ( ) ;
@@ -96,6 +177,11 @@ impl NSString {
96
177
str:: from_utf8 ( bytes) . unwrap ( )
97
178
}
98
179
180
+ // TODO: Allow usecases where the NUL byte from `UTF8String` is kept?
181
+
182
+ /// Creates an immutable `NSString` by copying the given string slice.
183
+ #[ doc( alias = "initWithBytes" ) ]
184
+ #[ doc( alias = "initWithBytes:length:encoding:" ) ]
99
185
pub fn from_str ( string : & str ) -> Id < Self , Shared > {
100
186
let cls = Self :: class ( ) ;
101
187
let bytes = string. as_ptr ( ) as * const c_void ;
@@ -110,8 +196,64 @@ impl NSString {
110
196
Id :: new ( NonNull :: new_unchecked ( obj) )
111
197
}
112
198
}
199
+
200
+ // TODO: initWithBytesNoCopy:, maybe add lifetime parameter to NSString?
201
+ // See https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L350-L381
202
+ // Might be quite difficult, as Objective-C code might assume the NSString
203
+ // is always alive?
204
+ // See https://github.com/drewcrawford/foundationr/blob/b27683417a35510e8e5d78a821f081905b803de6/src/nsstring.rs
205
+
206
+ /// Whether the given string matches the beginning characters of this
207
+ /// string.
208
+ ///
209
+ /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1410309-hasprefix?language=objc).
210
+ #[ doc( alias = "hasPrefix" ) ]
211
+ #[ doc( alias = "hasPrefix:" ) ]
212
+ pub fn has_prefix ( & self , prefix : & NSString ) -> bool {
213
+ let res: Bool = unsafe { msg_send ! [ self , hasPrefix: prefix] } ;
214
+ res. is_true ( )
215
+ }
216
+
217
+ /// Whether the given string matches the ending characters of this string.
218
+ ///
219
+ /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1416529-hassuffix?language=objc).
220
+ #[ doc( alias = "hasSuffix" ) ]
221
+ #[ doc( alias = "hasSuffix:" ) ]
222
+ pub fn has_suffix ( & self , suffix : & NSString ) -> bool {
223
+ let res: Bool = unsafe { msg_send ! [ self , hasSuffix: suffix] } ;
224
+ res. is_true ( )
225
+ }
226
+
227
+ // pub fn from_nsrange(range: NSRange) -> Id<Self, Shared>
228
+ // https://developer.apple.com/documentation/foundation/1415155-nsstringfromrange?language=objc
229
+ }
230
+
231
+ impl PartialOrd for NSString {
232
+ #[ inline]
233
+ fn partial_cmp ( & self , other : & Self ) -> Option < cmp:: Ordering > {
234
+ Some ( self . cmp ( other) )
235
+ }
236
+ }
237
+
238
+ impl Ord for NSString {
239
+ fn cmp ( & self , other : & Self ) -> cmp:: Ordering {
240
+ let res: NSComparisonResult = unsafe { msg_send ! [ self , compare: other] } ;
241
+ // TODO: Other comparison methods:
242
+ // - compare:options:
243
+ // - compare:options:range:
244
+ // - compare:options:range:locale:
245
+ // - localizedCompare:
246
+ // - caseInsensitiveCompare:
247
+ // - localizedCaseInsensitiveCompare:
248
+ // - localizedStandardCompare:
249
+ res. into ( )
250
+ }
113
251
}
114
252
253
+ // TODO: PartialEq and PartialOrd against &str
254
+ // See `fruity`'s implementation:
255
+ // https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163
256
+
115
257
impl DefaultId for NSString {
116
258
type Ownership = Shared ;
117
259
@@ -192,6 +334,16 @@ mod tests {
192
334
} ) ;
193
335
}
194
336
337
+ #[ test]
338
+ fn test_nul ( ) {
339
+ let expected = "\0 " ;
340
+ let s = NSString :: from_str ( expected) ;
341
+ assert_eq ! ( s. len( ) , expected. len( ) ) ;
342
+ autoreleasepool ( |pool| {
343
+ assert_eq ! ( s. as_str( pool) , expected) ;
344
+ } ) ;
345
+ }
346
+
195
347
#[ test]
196
348
fn test_interior_nul ( ) {
197
349
let expected = "Hello\0 World" ;
@@ -246,4 +398,55 @@ mod tests {
246
398
} ) ;
247
399
assert_eq ! ( ns_string. len( ) , expected. len( ) ) ;
248
400
}
401
+
402
+ #[ test]
403
+ fn test_hash ( ) {
404
+ use std:: collections:: hash_map:: DefaultHasher ;
405
+ use std:: hash:: Hash ;
406
+
407
+ let s1 = NSString :: from_str ( "example string goes here" ) ;
408
+ let s2 = NSString :: from_str ( "example string goes here" ) ;
409
+
410
+ let mut hashstate = DefaultHasher :: new ( ) ;
411
+ let mut hashstate2 = DefaultHasher :: new ( ) ;
412
+ assert_eq ! ( s1. hash( & mut hashstate) , s2. hash( & mut hashstate2) ) ;
413
+ }
414
+
415
+ #[ test]
416
+ fn test_prefix_suffix ( ) {
417
+ let s = NSString :: from_str ( "abcdef" ) ;
418
+ let prefix = NSString :: from_str ( "abc" ) ;
419
+ let suffix = NSString :: from_str ( "def" ) ;
420
+ assert ! ( s. has_prefix( & prefix) ) ;
421
+ assert ! ( s. has_suffix( & suffix) ) ;
422
+ assert ! ( !s. has_prefix( & suffix) ) ;
423
+ assert ! ( !s. has_suffix( & prefix) ) ;
424
+ }
425
+
426
+ #[ test]
427
+ fn test_cmp ( ) {
428
+ let s1 = NSString :: from_str ( "aa" ) ;
429
+ assert ! ( s1 <= s1) ;
430
+ assert ! ( s1 >= s1) ;
431
+ let s2 = NSString :: from_str ( "ab" ) ;
432
+ assert ! ( s1 < s2) ;
433
+ assert ! ( !( s1 > s2) ) ;
434
+ assert ! ( s1 <= s2) ;
435
+ assert ! ( !( s1 >= s2) ) ;
436
+ let s3 = NSString :: from_str ( "ba" ) ;
437
+ assert ! ( s1 < s3) ;
438
+ assert ! ( !( s1 > s3) ) ;
439
+ assert ! ( s1 <= s3) ;
440
+ assert ! ( !( s1 >= s3) ) ;
441
+ assert ! ( s2 < s3) ;
442
+ assert ! ( !( s2 > s3) ) ;
443
+ assert ! ( s2 <= s3) ;
444
+ assert ! ( !( s2 >= s3) ) ;
445
+
446
+ let s = NSString :: from_str ( "abc" ) ;
447
+ let shorter = NSString :: from_str ( "a" ) ;
448
+ let longer = NSString :: from_str ( "abcdef" ) ;
449
+ assert ! ( s > shorter) ;
450
+ assert ! ( s < longer) ;
451
+ }
249
452
}
0 commit comments